From 3831e66d2bcd4e458ba4a5dd6cfa5095636e4c7f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:32:13 +0000 Subject: [PATCH 001/105] fix(budget_reservation): don't reserve budget on token counting routes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../spend_tracking/budget_reservation.py | 12 ++++- .../proxy/test_budget_reservation.py | 47 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py index 58a85171cc7..7b62fd44d09 100644 --- a/litellm/proxy/spend_tracking/budget_reservation.py +++ b/litellm/proxy/spend_tracking/budget_reservation.py @@ -144,6 +144,16 @@ async def _apply_over_budget_reservation_policy( ) +_UNBILLED_ROUTES: Final[frozenset[str]] = frozenset({"/models", "/v1/models", "/utils/token_counter"}) +_UNBILLED_ROUTE_SUFFIXES: Final[tuple[str, ...]] = ("/v1/messages/count_tokens", ":countTokens") + + +def _is_unbilled_route(route: str) -> bool: + """Routes that never emit a cost-tracking callback. Reserving budget for them + is a permanent leak: nothing ever reconciles or releases the reservation.""" + return route in _UNBILLED_ROUTES or route.endswith(_UNBILLED_ROUTE_SUFFIXES) + + async def reserve_budget_for_request( request_body: dict, route: str, @@ -161,7 +171,7 @@ async def reserve_budget_for_request( ) -> dict | None: if valid_token is None or not RouteChecks.is_llm_api_route(route=route): return None - if route in {"/models", "/v1/models", "/utils/token_counter"}: + if _is_unbilled_route(route): return None if get_model_from_request(request_body, route, llm_router=llm_router) is None: return None diff --git a/tests/test_litellm/proxy/test_budget_reservation.py b/tests/test_litellm/proxy/test_budget_reservation.py index 34adb4d2091..7bdc73abf32 100644 --- a/tests/test_litellm/proxy/test_budget_reservation.py +++ b/tests/test_litellm/proxy/test_budget_reservation.py @@ -2583,3 +2583,50 @@ async def test_streaming_slow_path_processes_and_yields_chunk(spend_counter_stat assert received == [{"content": "hi"}] streaming_logging_obj.async_post_call_streaming_hook.assert_awaited_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "route", + [ + "/v1/messages/count_tokens", + "/anthropic/v1/messages/count_tokens", + "/v1beta/models/gemini-2.5-pro:countTokens", + "/models/gemini-2.5-pro:countTokens", + ], +) +async def test_token_counting_routes_never_reserve_budget(spend_counter_state, route): + """Token counting is free and never fires a cost callback, so a reservation + there is never reconciled and permanently bricks the key's spend counter.""" + counter_cache, key_cache = spend_counter_state + proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache) + valid_token = UserAPIKeyAuth( + token="key-count-tokens", + spend=0.0, + max_budget=0.01, + ) + + with patch( + "litellm.proxy.spend_tracking.budget_reservation.estimate_request_max_cost", + return_value=0.01, + ): + for _ in range(2): + assert ( + await reserve_budget_for_request( + request_body=_request_body(), + route=route, + 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, + ) + is None + ) + + assert counter_cache.in_memory_cache.get_cache(key="spend:key:key-count-tokens") is None + + # a real completion on the same key is still budget enforced + assert await _reserve(valid_token, 0.01, key_cache, proxy_logging_obj) is not None From f01309c5afaf576e15170699d94185bd01b4835c Mon Sep 17 00:00:00 2001 From: ZXT-zjbiliy <3240102335@zju.edu.cn> Date: Fri, 21 Aug 2026 13:56:30 +0800 Subject: [PATCH 002/105] fix(stream_chunk_builder): guard empty choices and missing role in build_base_response build_base_response() read the assistant role via first_chunk_with_choices["choices"][0]["delta"]["role"] with no bounds or key check, causing two failures: - IndexError when no chunk carries a non-empty "choices" array, because next() fell back to the first chunk whose "choices" may be [] - KeyError when the first choice's "delta" omits "role" or is {} Both surface as "litellm.APIError: Error building chunks for logging/streaming usage calculation". async_data_generator() writes that into the response stream, so the client's answer is truncated mid-stream with no data: [DONE], and the request never reaches SpendLogs. Observed in production on Anthropic streaming. Fall back to None, guard the array length, and default the role to "assistant". The loop directly below already guards with len(chunk["choices"]) > 0. --- .../streaming_chunk_builder_utils.py | 11 +- .../test_streaming_chunk_builder_utils.py | 103 ++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py index ee0518c4aec..59096cfaff7 100644 --- a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py @@ -302,8 +302,15 @@ class ChunkProcessor: model: Final = ChunkProcessor._get_model_from_chunks(chunks, first_chunk_model) system_fingerprint: Final = chunk.get("system_fingerprint", None) - first_chunk_with_choices: Final = next((c for c in chunks if c.get("choices")), chunk) - role: Final = first_chunk_with_choices["choices"][0]["delta"]["role"] + # Fall back to None rather than `chunk`: if no chunk carries a non-empty + # `choices` array, indexing [0] on the first chunk raises IndexError. + first_chunk_with_choices = next((c for c in chunks if c.get("choices")), None) + role: str = "assistant" + if first_chunk_with_choices is not None: + _choices = first_chunk_with_choices["choices"] + if len(_choices) > 0: + # `delta` may be absent or omit `role` (e.g. content-only deltas). + role = _choices[0].get("delta", {}).get("role") or "assistant" finish_reason = "stop" for chunk in chunks: if "choices" in chunk and len(chunk["choices"]) > 0: diff --git a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py index 0f21cce476b..aec189da653 100644 --- a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py @@ -1342,3 +1342,106 @@ def test_calculate_usage_fills_unknown_split_from_reasoning_estimate( assert usage.completion_tokens == 100 assert usage.completion_tokens_details.reasoning_tokens == expected_reasoning_tokens assert usage.completion_tokens_details.text_tokens == expected_text_tokens + + +def _empty_choices_chunk(**extra): + chunk = { + "id": "chatcmpl-empty-choices", + "object": "chat.completion.chunk", + "created": 1, + "model": "claude-opus-4-8", + "choices": [], + } + chunk.update(extra) + return chunk + + +@pytest.mark.parametrize( + "chunks", + [ + pytest.param( + [_empty_choices_chunk(), _empty_choices_chunk()], + id="all_chunks_have_empty_choices", + ), + pytest.param( + [ + _empty_choices_chunk(usage={"prompt_tokens": 10}), + _empty_choices_chunk(usage={"completion_tokens": 0}), + ], + id="usage_only_chunks", + ), + ], +) +def test_build_base_response_handles_empty_choices(chunks): + """Empty `choices` arrays must not raise IndexError. + + `next((c for c in chunks if c.get("choices")), chunk)` used to fall back to the + first chunk, whose `choices` may be `[]`, so `["choices"][0]` went out of range. + The resulting error is surfaced to the client mid-stream and the request never + reaches SpendLogs. + """ + processor = ChunkProcessor(chunks=list(chunks)) + + response = processor.build_base_response(list(chunks)) + + assert response.choices[0].message.role == "assistant" + + +@pytest.mark.parametrize( + "delta", + [ + pytest.param({"content": "Hello"}, id="delta_without_role"), + pytest.param({}, id="delta_empty_dict"), + ], +) +def test_build_base_response_handles_delta_without_role(delta): + """A `delta` that omits `role` must not raise KeyError.""" + chunks = [ + { + "id": "chatcmpl-no-role", + "object": "chat.completion.chunk", + "created": 1, + "model": "claude-opus-4-8", + "choices": [{"index": 0, "delta": delta, "finish_reason": None}], + } + ] + processor = ChunkProcessor(chunks=list(chunks)) + + response = processor.build_base_response(list(chunks)) + + assert response.choices[0].message.role == "assistant" + + +def test_build_base_response_still_reads_role_and_finish_reason(): + """Regression guard: well-formed chunks keep their role and finish_reason.""" + chunks = [ + _empty_choices_chunk(), + { + "id": "chatcmpl-normal", + "object": "chat.completion.chunk", + "created": 1, + "model": "claude-opus-4-8", + "choices": [ + { + "index": 0, + "delta": {"role": "assistant", "content": "Hi"}, + "finish_reason": None, + } + ], + }, + { + "id": "chatcmpl-normal", + "object": "chat.completion.chunk", + "created": 2, + "model": "claude-opus-4-8", + "choices": [ + {"index": 0, "delta": {"content": "!"}, "finish_reason": "stop"} + ], + }, + ] + processor = ChunkProcessor(chunks=list(chunks)) + + response = processor.build_base_response(list(chunks)) + + assert response.choices[0].message.role == "assistant" + assert response.choices[0].finish_reason == "stop" From 7015bf37bb3830dab79b3efd886eee9b5b7ffad6 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Sat, 5 Sep 2026 17:32:17 -0700 Subject: [PATCH 003/105] fix(proxy): apply team model aliases on the JWT auth path Team reads on the auth path never loaded the team's alias table, and the team-based JWT branch copied a hand-picked subset of team fields onto UserAPIKeyAuth, so aliases (and a few other team grants) never reached JWT callers: restricted teams 403'd alias requests and open teams 400'd them Load the alias relation where the team row is read and cached, project the team onto every team_* token field through one shared team_grants helper used by both JWT returns, and keep the relation when team model add/delete rewrites the cached team. ui_sso reuses the shared alias table model Resolves LIT-5858 Claude-Session: https://claude.ai/code/session_01EX13mWex6RaBo9PYnkAtFW --- basedpyright-code-budget.json | 2 +- litellm/proxy/auth/auth_checks.py | 9 +- litellm/proxy/auth/handle_jwt.py | 5 +- litellm/proxy/auth/team_grants.py | 122 +++++++++++++++++ litellm/proxy/auth/user_api_key_auth.py | 22 +-- .../management_endpoints/team_endpoints.py | 4 +- litellm/proxy/management_endpoints/ui_sso.py | 23 +--- .../proxy/auth/test_auth_checks.py | 64 +++++++++ .../proxy/auth/test_handle_jwt.py | 52 +++++++ .../proxy/auth/test_team_grants.py | 129 ++++++++++++++++++ .../proxy/auth/test_user_api_key_auth.py | 116 ++++++++++++++++ .../test_team_endpoints.py | 44 ++++++ type-discipline-budget.json | 2 +- 13 files changed, 547 insertions(+), 47 deletions(-) create mode 100644 litellm/proxy/auth/team_grants.py create mode 100644 tests/test_litellm/proxy/auth/test_team_grants.py diff --git a/basedpyright-code-budget.json b/basedpyright-code-budget.json index 0b0a61192e6..8bdc251c684 100644 --- a/basedpyright-code-budget.json +++ b/basedpyright-code-budget.json @@ -105,7 +105,7 @@ "limit": 109 }, "reportUnknownMemberType": { - "limit": 38271 + "limit": 38269 }, "reportUnknownParameterType": { "limit": 19584 diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index dc693317de0..a71a1993064 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -475,6 +475,7 @@ def _is_model_cost_zero(model: str | list[str] | None, llm_router: Router | None _NO_MODEL_INFO: Final[Mapping[str, object]] = MappingProxyType({}) +_TEAM_GRANT_RELATIONS: Final[Mapping[str, object]] = MappingProxyType({"litellm_model_table": True}) def _has_ptu_flat_cost(model: str, llm_router: "Router") -> bool: @@ -2858,7 +2859,9 @@ class TeamNotFoundError(HTTPException): async def _get_team_db_check( team_id: str, prisma_client: PrismaClient, team_id_upsert: bool | None = None ) -> "_PrismaTeamRow | None": - response = await _team_table(TeamRepository(prisma_client)).find_unique(where={"team_id": team_id}) + response = await _team_table(TeamRepository(prisma_client)).find_unique( + where={"team_id": team_id}, include=_TEAM_GRANT_RELATIONS + ) if response is None and team_id_upsert: from litellm.proxy.management_endpoints.team_endpoints import new_team @@ -3158,7 +3161,9 @@ async def get_team_object_by_alias( # Query database by team_alias try: - teams: Final = await _team_table(TeamRepository(prisma_client)).find_many(where={"team_alias": team_alias}) + teams: Final = await _team_table(TeamRepository(prisma_client)).find_many( + where={"team_alias": team_alias}, include=_TEAM_GRANT_RELATIONS + ) if not teams: raise HTTPException( diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 0795cee7409..69091ee8344 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -53,6 +53,7 @@ from litellm.proxy._types import ( ) from litellm.proxy.auth.auth_checks import can_team_access_model from litellm.proxy.auth.route_checks import RouteChecks +from litellm.proxy.auth.team_grants import team_model_aliases from litellm.proxy.common_utils.user_api_key_cache import ( UserApiKeyCache, get_management_object_ttl, @@ -1595,7 +1596,7 @@ class JWTAuthManager: model=requested_model, team_object=team_object, llm_router=llm_router, - team_model_aliases=None, + team_model_aliases=team_model_aliases(team_object), ) ): is_allowed = allowed_routes_check( @@ -2132,7 +2133,7 @@ class JWTAuthManager: model=requested_model, team_object=team_object, llm_router=llm_router, - team_model_aliases=None, + team_model_aliases=team_model_aliases(team_object), ) except ProxyException: continue diff --git a/litellm/proxy/auth/team_grants.py b/litellm/proxy/auth/team_grants.py new file mode 100644 index 00000000000..1196011dcdd --- /dev/null +++ b/litellm/proxy/auth/team_grants.py @@ -0,0 +1,122 @@ +"""Project a team row (plus the caller's membership in it) onto the ``team_*`` fields of ``UserAPIKeyAuth``. + +The virtual-key path gets these fields for free from the combined-view SQL join. Every other auth path +starts from a ``LiteLLM_TeamTable`` object instead and has to copy them over by hand, which is how JWT +callers kept losing grants (aliases, permissions, limits) one field at a time. Build the badge through +``team_grants`` and the two paths cannot drift. +""" + +from collections.abc import Mapping, Sequence +from types import MappingProxyType +from typing import Annotated, Final + +from pydantic import BaseModel, BeforeValidator, ConfigDict, TypeAdapter, ValidationError +from pydantic.main import IncEx +from typing_extensions import ReadOnly, TypedDict + +from litellm.proxy._types import ( + LiteLLM_ObjectPermissionTable, + LiteLLM_TeamMembership, + LiteLLM_TeamTable, + Member, +) + +_MODEL_ALIASES_ADAPTER: Final = TypeAdapter(dict[str, str]) +_JSON_COLUMNS: Final[Mapping[str, IncEx | bool]] = MappingProxyType( + {"metadata": True, "litellm_model_table": MappingProxyType({"model_aliases": True})} +) + + +def _decode_model_aliases(value: object) -> object: + """``LiteLLM_ModelTable.model_aliases`` is typed ``str | dict``; writers hand Prisma ``json.dumps(...)``, so take both.""" + if not isinstance(value, str): + return value + try: + return _MODEL_ALIASES_ADAPTER.validate_json(value) + except ValidationError: + return None + + +class TeamModelAliasTable(BaseModel): + model_config = ConfigDict(protected_namespaces=()) + + model_aliases: Annotated[Mapping[str, str] | None, BeforeValidator(_decode_model_aliases)] = None + + +class _TeamJsonColumns(BaseModel): + """The two loosely typed columns on ``LiteLLM_TeamTable``, re-read with the shape the badge needs.""" + + metadata: Mapping[str, object] | None = None + litellm_model_table: TeamModelAliasTable | None = None + + +class TeamGrants(TypedDict, total=False): + """Keyword arguments for ``UserAPIKeyAuth``. Empty when the caller has no team, so the model's own defaults apply.""" + + team_alias: ReadOnly[str | None] + team_tpm_limit: ReadOnly[int | None] + team_rpm_limit: ReadOnly[int | None] + team_max_budget: ReadOnly[float | None] + team_soft_budget: ReadOnly[float | None] + team_spend: ReadOnly[float | None] + team_models: ReadOnly[Sequence[str]] + team_blocked: ReadOnly[bool] + team_metadata: ReadOnly[Mapping[str, object] | None] + team_model_aliases: ReadOnly[Mapping[str, str] | None] + team_object_permission_id: ReadOnly[str | None] + team_object_permission: ReadOnly[LiteLLM_ObjectPermissionTable | None] + team_member: ReadOnly[Member | None] + team_member_spend: ReadOnly[float | None] + team_member_tpm_limit: ReadOnly[int | None] + team_member_rpm_limit: ReadOnly[int | None] + + +def _json_columns(team_object: LiteLLM_TeamTable) -> _TeamJsonColumns: + try: + return _TeamJsonColumns.model_validate(team_object.model_dump(include=_JSON_COLUMNS)) + except ValidationError: + return _TeamJsonColumns() + + +def team_model_aliases(team_object: LiteLLM_TeamTable | None) -> Mapping[str, str] | None: + if team_object is None: + return None + alias_table: Final = _json_columns(team_object).litellm_model_table + return alias_table.model_aliases if alias_table is not None else None + + +def team_grants( + team_object: LiteLLM_TeamTable | None, + team_membership: LiteLLM_TeamMembership | None, + user_id: str | None, +) -> TeamGrants: + if team_object is None: + return TeamGrants() + json_columns: Final = _json_columns(team_object) + return TeamGrants( + team_alias=team_object.team_alias, + team_tpm_limit=team_object.tpm_limit, + team_rpm_limit=team_object.rpm_limit, + team_max_budget=team_object.max_budget, + team_soft_budget=team_object.soft_budget, + team_spend=team_object.spend, + team_models=tuple(team_object.models), + team_blocked=team_object.blocked, + team_metadata=json_columns.metadata, + team_model_aliases=( + json_columns.litellm_model_table.model_aliases if json_columns.litellm_model_table is not None else None + ), + team_object_permission_id=team_object.object_permission_id, + team_object_permission=team_object.object_permission, + team_member=next( + (m for m in team_object.members_with_roles if user_id is not None and m.user_id == user_id), + None, + ), + team_member_spend=team_membership.spend if team_membership is not None else None, + team_member_tpm_limit=( + team_membership.safe_get_team_member_tpm_limit() if team_membership is not None else None + ), + team_member_rpm_limit=( + team_membership.safe_get_team_member_rpm_limit() if team_membership is not None else None + ), + ) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 93293db24c6..08dbe2508ec 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -82,6 +82,7 @@ from litellm.proxy.auth.oauth2_proxy_hook import handle_oauth2_proxy_request from litellm.proxy.auth.resolvers import CredentialRef, Principal from litellm.proxy.auth.resolvers.store import IdentityStore from litellm.proxy.auth.route_checks import RouteChecks +from litellm.proxy.auth.team_grants import team_grants from litellm.proxy.auth.trusted_proxy_utils import get_trusted_proxy_cidrs from litellm.proxy.common_utils.cache_coordinator import EventDrivenCacheCoordinator from litellm.proxy.common_utils.http_parsing_utils import ( @@ -1476,24 +1477,16 @@ async def _user_api_key_auth_builder( user_id=user_id, user_email=user_email, team_id=team_id, - team_alias=(team_object.team_alias if team_object is not None else None), - team_tpm_limit=(team_object.tpm_limit if team_object is not None else None), - team_rpm_limit=(team_object.rpm_limit if team_object is not None else None), - team_models=(team_object.models if team_object is not None else []), - team_metadata=(team_object.metadata if team_object is not None else None), org_id=org_id, end_user_id=end_user_id, parent_otel_span=parent_otel_span, jwt_claims=jwt_claims, + **team_grants(team_object=team_object, team_membership=team_membership, user_id=user_id), ) valid_token = UserAPIKeyAuth( api_key=None, team_id=team_id, - team_alias=(team_object.team_alias if team_object is not None else None), - team_tpm_limit=(team_object.tpm_limit if team_object is not None else None), - team_rpm_limit=(team_object.rpm_limit if team_object is not None else None), - team_models=(team_object.models if team_object is not None else []), user_role=( LitellmUserRoles(user_object.user_role) if user_object is not None and user_object.user_role is not None @@ -1507,17 +1500,8 @@ async def _user_api_key_auth_builder( user_tpm_limit=(user_object.tpm_limit if user_object is not None else None), user_rpm_limit=(user_object.rpm_limit if user_object is not None else None), user_model_max_budget=(user_object.model_max_budget if user_object is not None else None), - team_member_rpm_limit=( - team_membership.safe_get_team_member_rpm_limit() if team_membership is not None else None - ), - team_member_tpm_limit=( - team_membership.safe_get_team_member_tpm_limit() if team_membership is not None else None - ), - team_metadata=(team_object.metadata if team_object is not None else None), jwt_claims=jwt_claims, - ) - valid_token.team_object_permission = ( - team_object.object_permission if team_object is not None else None + **team_grants(team_object=team_object, team_membership=team_membership, user_id=user_id), ) # AUTO_REGISTER deferred from _resolve_jwt_to_virtual_key. diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 2ec68a10f65..c050368b3fe 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -5601,7 +5601,7 @@ async def team_model_add( updated_team: Final = await _team_db(prisma_client).update( where={"team_id": data.team_id}, data={"updated_at": datetime.now(timezone.utc)}, - include={"object_permission": True}, + include={"litellm_model_table": True, "object_permission": True}, ) if updated_team is None: raise HTTPException( @@ -5688,7 +5688,7 @@ async def team_model_delete( updated_team: Final = await _team_db(prisma_client).update( where={"team_id": data.team_id}, data={"models": updated_models}, - include={"object_permission": True}, + include={"litellm_model_table": True, "object_permission": True}, ) if updated_team is None: raise HTTPException( diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index 3e6434a5afd..c60888e298f 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -22,7 +22,6 @@ from html import escape from types import MappingProxyType from typing import ( TYPE_CHECKING, - Annotated, Any, Final, Literal, @@ -42,7 +41,7 @@ if TYPE_CHECKING: import jwt from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response, status from fastapi.responses import RedirectResponse -from pydantic import BaseModel, BeforeValidator, ConfigDict, TypeAdapter, ValidationError +from pydantic import BaseModel, TypeAdapter, ValidationError import litellm from litellm._logging import verbose_proxy_logger @@ -95,6 +94,7 @@ from litellm.proxy.auth.auth_utils import ( ) from litellm.proxy.auth.handle_jwt import JWTHandler from litellm.proxy.auth.ip_address_utils import IPAddressUtils +from litellm.proxy.auth.team_grants import TeamModelAliasTable from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_utils.admin_ui_utils import ( admin_ui_disabled, @@ -209,31 +209,14 @@ def _team_detail_db(repo: TeamRepository) -> "TableActions[_TeamDetailRow]": return repo.table -_MODEL_ALIASES_ADAPTER: Final = TypeAdapter(dict[str, str]) _SSO_TOKEN_CLAIMS_ADAPTER: Final = TypeAdapter(Mapping[str, object]) -def _decode_model_aliases(value: object) -> object: - """``/team/new`` stores team model aliases as a JSON-encoded string in the Json column.""" - if not isinstance(value, str): - return value - try: - return _MODEL_ALIASES_ADAPTER.validate_json(value) - except ValidationError: - return None - - -class _TeamModelAliasTable(BaseModel): - model_config = ConfigDict(protected_namespaces=()) - - model_aliases: Annotated[Mapping[str, str] | None, BeforeValidator(_decode_model_aliases)] = None - - class _TeamRowGrants(BaseModel): team_id: str team_alias: str | None = None models: tuple[str, ...] = () - litellm_model_table: _TeamModelAliasTable | None = None + litellm_model_table: TeamModelAliasTable | None = None class CliSsoTeamDetail(BaseModel): diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index 2284a05b2e9..5bfef2b6445 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -2374,6 +2374,44 @@ def _mock_prisma_for_team_lookup(find_unique): return mock_prisma_client +_TEAM_ALIAS_TABLE_ROW = {"id": 1, "model_aliases": '{"fast": "gpt-4o"}', "created_by": "admin", "updated_by": "admin"} + + +def _prisma_team_row(include): + """Mimics Prisma: the `litellm_model_table` relation rides on the row only when the query `include`s it.""" + columns = {"team_id": "team-aliases", "team_alias": "aliases", "models": ["gpt-4o"]} + row = ( + {**columns, "litellm_model_table": _TEAM_ALIAS_TABLE_ROW} + if (include or {}).get("litellm_model_table") + else columns + ) + return SimpleNamespace(dict=lambda: row, model_dump=lambda: row) + + +@pytest.mark.asyncio +async def test_get_team_object_loads_model_aliases_relation(): + """LIT-5858: the auth path read teams without `include`ing `litellm_model_table`, so every JWT + team came back with `model_aliases=None` and alias requests 403'd.""" + from litellm.proxy.auth.auth_checks import get_team_object + from litellm.proxy.auth.team_grants import team_model_aliases + + async def find_unique(where, include=None): + return _prisma_team_row(include) + + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=None) + mock_cache.async_set_cache = AsyncMock() + + team = await get_team_object( + team_id="team-aliases", + prisma_client=_mock_prisma_for_team_lookup(AsyncMock(side_effect=find_unique)), + user_api_key_cache=mock_cache, + check_db_only=True, + ) + + assert team_model_aliases(team) == {"fast": "gpt-4o"} + + @pytest.mark.asyncio async def test_get_team_object_distinguishes_absent_team_from_unreadable_row(): """A deleted team and a database that would not answer both surface as a 404, @@ -6195,6 +6233,32 @@ async def test_get_team_object_by_alias_db_fetch_returns_cached_obj(): assert result.models == ["gpt-4"] +@pytest.mark.asyncio +async def test_get_team_object_by_alias_loads_model_aliases_relation(): + """LIT-5858: same regression as `test_get_team_object_loads_model_aliases_relation`, for the + `team_alias_jwt_field` lookup.""" + from litellm.proxy.auth.auth_checks import get_team_object_by_alias + from litellm.proxy.auth.team_grants import team_model_aliases + + async def find_many(where, include=None): + return [_prisma_team_row(include)] + + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_teamtable.find_many = AsyncMock(side_effect=find_many) + + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=None) + mock_cache.async_set_cache = AsyncMock() + + team = await get_team_object_by_alias( + team_alias="aliases", + prisma_client=mock_prisma_client, + user_api_key_cache=mock_cache, + ) + + assert team_model_aliases(team) == {"fast": "gpt-4o"} + + @pytest.mark.asyncio async def test_get_org_object_by_alias_db_fetch_returns_validated_org(): from litellm.proxy._types import LiteLLM_OrganizationTable diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 99a0a4c0a8b..94226b5404d 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -13,6 +13,7 @@ from litellm.proxy._types import ( DEFAULT_JWKS_STALE_TTL, JWTLiteLLMRoleMap, LiteLLM_JWTAuth, + LiteLLM_ModelTable, LiteLLM_TeamMembership, LiteLLM_TeamTable, LiteLLM_UserTable, @@ -1255,6 +1256,57 @@ async def test_find_team_with_model_access_model_group(monkeypatch): assert team_obj.team_id == "team-1" +@pytest.mark.asyncio +@pytest.mark.parametrize( + "model_aliases", + ['{"fast": "gpt-4o"}', {"fast": "gpt-4o"}], + ids=["json-string", "dict"], +) +async def test_find_team_with_model_access_resolves_team_model_alias(monkeypatch, model_aliases): + """LIT-5858: a JWT team that grants `gpt-4o` under the alias `fast` must resolve a request + for `fast`. The JWT path used to pass `team_model_aliases=None`, so every alias request 403'd.""" + import sys + import types + + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + from litellm.router import Router + + router = Router(model_list=[{"model_name": "gpt-4o", "litellm_params": {"model": "gpt-4o"}}]) + proxy_server_module = types.ModuleType("proxy_server") + proxy_server_module.llm_router = router + monkeypatch.setitem(sys.modules, "litellm.proxy.proxy_server", proxy_server_module) + + team = LiteLLM_TeamTable( + team_id="team-aliases", + models=["gpt-4o"], + litellm_model_table=LiteLLM_ModelTable(model_aliases=model_aliases, created_by="admin", updated_by="admin"), + ) + + async def mock_get_team_object(*args, **kwargs): + return team + + monkeypatch.setattr("litellm.proxy.auth.handle_jwt.get_team_object", mock_get_team_object) + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + user_api_key_cache = DualCache() + + team_id, team_obj = await JWTAuthManager.find_team_with_model_access( + team_ids={"team-aliases"}, + requested_model="fast", + route="/chat/completions", + jwt_handler=jwt_handler, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=ProxyLogging(user_api_key_cache=user_api_key_cache), + ) + + assert team_id == "team-aliases" + assert team_obj is team + + @pytest.mark.asyncio async def test_find_team_with_model_access_v1_messages_default_routes(monkeypatch): """Regression for #31189: a single-team JWT that grants the requested model diff --git a/tests/test_litellm/proxy/auth/test_team_grants.py b/tests/test_litellm/proxy/auth/test_team_grants.py new file mode 100644 index 00000000000..447fc1c93a1 --- /dev/null +++ b/tests/test_litellm/proxy/auth/test_team_grants.py @@ -0,0 +1,129 @@ +import pytest + +from litellm.proxy._types import ( + LiteLLM_BudgetTable, + LiteLLM_ObjectPermissionTable, + LiteLLM_TeamMembership, + LiteLLM_TeamTable, + LiteLLM_VerificationTokenView, + Member, + UserAPIKeyAuth, +) +from litellm.models.team import LiteLLM_ModelTable +from litellm.proxy.auth.team_grants import team_grants, team_model_aliases + +TEAM_ID = "team-grants" +USER_ID = "user-in-team" +ALIASES = {"fast": "gpt-4o-mini", "smart": "gpt-4o"} + + +def _alias_table(model_aliases) -> LiteLLM_ModelTable: + return LiteLLM_ModelTable(model_aliases=model_aliases, created_by="admin", updated_by="admin") + + +def _full_team(model_aliases=ALIASES) -> LiteLLM_TeamTable: + return LiteLLM_TeamTable( + team_id=TEAM_ID, + team_alias="grants-team", + tpm_limit=1000, + rpm_limit=10, + max_budget=50.0, + soft_budget=25.0, + spend=12.5, + models=["gpt-4o", "gpt-4o-mini"], + blocked=True, + metadata={"tier": "gold"}, + litellm_model_table=_alias_table(model_aliases), + object_permission_id="op-1", + object_permission=LiteLLM_ObjectPermissionTable(object_permission_id="op-1", mcp_servers=["mcp-a"]), + members_with_roles=[ + Member(user_id="someone-else", role="user"), + Member(user_id=USER_ID, role="admin"), + ], + ) + + +def _membership() -> LiteLLM_TeamMembership: + return LiteLLM_TeamMembership( + user_id=USER_ID, + team_id=TEAM_ID, + spend=3.25, + litellm_budget_table=LiteLLM_BudgetTable(tpm_limit=500, rpm_limit=5), + ) + + +def test_team_grants_cover_every_team_field_the_key_path_gets(): + """Class guard for LIT-5858 and its siblings: every ``team_*`` column the combined-view SQL hands the + virtual-key path must come out of the projection too, with the team's actual value, so adding a column + to ``LiteLLM_VerificationTokenView`` without teaching ``team_grants`` fails here instead of in prod.""" + team = _full_team() + grants = team_grants(team_object=team, team_membership=_membership(), user_id=USER_ID) + token = UserAPIKeyAuth(team_id=TEAM_ID, **grants) + + view_team_fields = {name for name in LiteLLM_VerificationTokenView.model_fields if name.startswith("team_")} + assert view_team_fields - {"team_id"} <= set(grants) + assert all(grants[name] is not None for name in view_team_fields - {"team_id"}) + + assert token.team_alias == "grants-team" + assert token.team_tpm_limit == 1000 + assert token.team_rpm_limit == 10 + assert token.team_max_budget == 50.0 + assert token.team_soft_budget == 25.0 + assert token.team_spend == 12.5 + assert token.team_models == ["gpt-4o", "gpt-4o-mini"] + assert token.team_blocked is True + assert token.team_metadata == {"tier": "gold"} + assert token.team_model_aliases == ALIASES + assert token.team_object_permission_id == "op-1" + assert token.team_object_permission is not None + assert token.team_object_permission.mcp_servers == ["mcp-a"] + assert token.team_member == Member(user_id=USER_ID, role="admin") + assert token.team_member_spend == 3.25 + assert token.team_member_tpm_limit == 500 + assert token.team_member_rpm_limit == 5 + + +def test_team_grants_without_team_leave_token_defaults(): + token = UserAPIKeyAuth(**team_grants(team_object=None, team_membership=None, user_id=USER_ID)) + assert token == UserAPIKeyAuth() + + +@pytest.mark.parametrize( + "stored_aliases", + [ALIASES, '{"fast": "gpt-4o-mini", "smart": "gpt-4o"}'], + ids=["json-object", "json-string-as-written-by-team-new"], +) +def test_team_model_aliases_decode_both_storage_shapes(stored_aliases): + team = _full_team(model_aliases=stored_aliases) + assert team_model_aliases(team) == ALIASES + assert team_grants(team_object=team, team_membership=None, user_id=None)["team_model_aliases"] == ALIASES + + +@pytest.mark.parametrize("stored_aliases", [None, "not json", '["a", "b"]', {"fast": 3}], ids=str) +def test_team_model_aliases_treat_unusable_column_as_no_aliases(stored_aliases): + team = _full_team(model_aliases=stored_aliases) + assert team_model_aliases(team) is None + assert team_grants(team_object=team, team_membership=None, user_id=None)["team_model_aliases"] is None + + +def test_team_model_aliases_none_without_relation_loaded(): + team = _full_team() + team.litellm_model_table = None + assert team_model_aliases(team) is None + assert team_model_aliases(None) is None + + +def test_team_member_is_the_callers_row_only(): + team = _full_team() + assert team_grants(team_object=team, team_membership=None, user_id="someone-else")["team_member"] == Member( + user_id="someone-else", role="user" + ) + assert team_grants(team_object=team, team_membership=None, user_id="stranger")["team_member"] is None + assert team_grants(team_object=team, team_membership=None, user_id=None)["team_member"] is None + + +def test_membership_limits_absent_without_membership_row(): + grants = team_grants(team_object=_full_team(), team_membership=None, user_id=USER_ID) + assert grants["team_member_spend"] is None + assert grants["team_member_tpm_limit"] is None + assert grants["team_member_rpm_limit"] is None diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index d44f96d95bf..78ffbb0db23 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -6872,3 +6872,119 @@ class TestLitellmReceivedAtStamping: assert result == earlier assert request.state.litellm_received_at == earlier + + +@pytest.mark.asyncio +@pytest.mark.parametrize("is_proxy_admin", [False, True], ids=["standard-return", "proxy-admin-return"]) +async def test_jwt_builder_returns_every_team_grant_the_key_path_gets(is_proxy_admin): + """LIT-5858: the team-based JWT path hand-built ``UserAPIKeyAuth`` from a short list of team fields, so the + team's model aliases (and on the admin return, its object permission) never reached the token and alias + requests 403'd. Both returns now go through ``team_grants``; pin the fields that used to be dropped.""" + import litellm.proxy.proxy_server as _proxy_server_mod + from fastapi import Request + from starlette.datastructures import URL + + from litellm.models.team import LiteLLM_ModelTable + from litellm.proxy._types import ( + LiteLLM_ObjectPermissionTable, + LiteLLM_TeamMembership, + LiteLLM_TeamTable, + Member, + ) + + class _AcceptEveryJwt(JWTHandler): + def is_jwt(self, token: str) -> bool: + return True + + jwt_handler = _AcceptEveryJwt() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + + team = LiteLLM_TeamTable( + team_id="team-jwt-aliases", + team_alias="jwt-aliases", + models=["gpt-4o"], + max_budget=40.0, + spend=4.0, + blocked=False, + metadata={"tier": "gold"}, + litellm_model_table=LiteLLM_ModelTable( + model_aliases='{"fast": "gpt-4o"}', created_by="admin", updated_by="admin" + ), + object_permission_id="op-jwt", + object_permission=LiteLLM_ObjectPermissionTable(object_permission_id="op-jwt", mcp_servers=["mcp-a"]), + members_with_roles=[Member(user_id="jwt-user", role="admin")], + ) + membership = LiteLLM_TeamMembership(user_id="jwt-user", team_id="team-jwt-aliases", spend=1.5) + builder_result = { + "is_proxy_admin": is_proxy_admin, + "team_object": team, + "user_object": None, + "end_user_object": None, + "org_object": None, + "token": "jwt", + "team_id": "team-jwt-aliases", + "user_id": "jwt-user", + "user_email": "jwt-user@example.com", + "end_user_id": None, + "org_id": None, + "team_membership": membership, + "jwt_claims": {"sub": "jwt-user"}, + } + + mock_proxy_logging_obj = MagicMock() + mock_proxy_logging_obj.internal_usage_cache = MagicMock() + mock_proxy_logging_obj.internal_usage_cache.dual_cache = AsyncMock() + mock_proxy_logging_obj.post_call_failure_hook = AsyncMock(return_value=None) + attrs = { + "prisma_client": MagicMock(), + "user_api_key_cache": DualCache(), + "proxy_logging_obj": mock_proxy_logging_obj, + "master_key": "sk-master-key", + "general_settings": {"enable_jwt_auth": True}, + "llm_model_list": [], + "llm_router": None, + "open_telemetry_logger": None, + "model_max_budget_limiter": MagicMock(), + "user_custom_auth": None, + "jwt_handler": jwt_handler, + "premium_user": True, + "litellm_proxy_admin_name": "admin", + } + originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs} + try: + for k, v in attrs.items(): + setattr(_proxy_server_mod, k, v) + request = Request(scope={"type": "http", "headers": [], "method": "POST"}) + request._url = URL(url="/chat/completions") + with patch( # test-quality-ok: auth_builder is the claim-resolution seam; the regression is how its result is projected onto the token + "litellm.proxy.auth.user_api_key_auth.JWTAuthManager.auth_builder", + new_callable=AsyncMock, + return_value=builder_result, + ): + token = await _user_api_key_auth_builder( + request=request, + api_key="Bearer header.payload.signature", + azure_api_key_header="", + anthropic_api_key_header=None, + google_ai_studio_api_key_header=None, + azure_apim_header=None, + request_data={}, + ) + finally: + for k, v in originals.items(): + setattr(_proxy_server_mod, k, v) + + assert token.team_id == "team-jwt-aliases" + assert token.user_role == (LitellmUserRoles.PROXY_ADMIN if is_proxy_admin else LitellmUserRoles.INTERNAL_USER) + assert token.team_model_aliases == {"fast": "gpt-4o"} + assert token.team_object_permission is not None + assert token.team_object_permission.mcp_servers == ["mcp-a"] + assert token.team_object_permission_id == "op-jwt" + assert token.team_alias == "jwt-aliases" + assert token.team_models == ["gpt-4o"] + assert token.team_max_budget == 40.0 + assert token.team_spend == 4.0 + assert token.team_metadata == {"tier": "gold"} + assert token.team_member == Member(user_id="jwt-user", role="admin") + assert token.team_member_spend == 1.5 + assert token.jwt_claims == {"sub": "jwt-user"} 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 051e6bed4fd..2f6561046b1 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -2205,6 +2205,50 @@ async def test_team_model_add_delete_refresh_team_cache(endpoint_name): assert update_call_kwargs.get("include", {}).get("object_permission") is True +@pytest.mark.asyncio +@pytest.mark.parametrize("endpoint_name", ["team_model_add", "team_model_delete"]) +async def test_team_model_add_delete_keep_model_aliases_in_team_cache(endpoint_name, monkeypatch): + """LIT-5858: Prisma only returns `litellm_model_table` when the `update` asks for it, so the refreshed + cache entry lost the team's model aliases and JWT alias requests 403'd until the next DB read.""" + from litellm.proxy._types import TeamModelAddRequest, TeamModelDeleteRequest + from litellm.proxy.auth.team_grants import team_model_aliases + from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache + from litellm.proxy.management_endpoints.team_endpoints import team_model_add, team_model_delete + + columns = {"team_id": "team-1234", "models": ["gpt-4o", "openai/*"]} + alias_table = {"id": 1, "model_aliases": '{"fast": "gpt-4o"}', "created_by": "admin", "updated_by": "admin"} + + async def update(where, data, include=None): + row = {**columns, "litellm_model_table": alias_table} if (include or {}).get("litellm_model_table") else columns + return SimpleNamespace(team_id="team-1234", model_dump=lambda: row) + + prisma_client = MagicMock() + prisma_client.db.litellm_teamtable.find_unique = AsyncMock(return_value=SimpleNamespace(model_dump=lambda: columns)) + prisma_client.db.litellm_teamtable.update = AsyncMock(side_effect=update) + prisma_client.db.execute_raw = AsyncMock(return_value=None) + cache = UserApiKeyCache() + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", prisma_client) + monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", cache) + monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", None) + + admin = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin") + if endpoint_name == "team_model_add": + await team_model_add( + data=TeamModelAddRequest(team_id="team-1234", models=["team-byok-1"]), + http_request=MagicMock(), + user_api_key_dict=admin, + ) + else: + await team_model_delete( + data=TeamModelDeleteRequest(team_id="team-1234", models=["openai/*"]), + http_request=MagicMock(), + user_api_key_dict=admin, + ) + + cached_team = await cache.async_get_cache(key="team_id:team-1234", model_type=LiteLLM_TeamTableCachedObj) + assert team_model_aliases(cached_team) == {"fast": "gpt-4o"} + + @pytest.mark.asyncio @pytest.mark.parametrize( "endpoint_name", diff --git a/type-discipline-budget.json b/type-discipline-budget.json index e7186dfe186..7db8ed501f8 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -3,7 +3,7 @@ "limit": 22180 }, "LIT002": { - "limit": 26729 + "limit": 26727 }, "LIT003": { "limit": 261 From 6c21be619441dbd24879e1f8a4b897c6dbd667fe Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 12:15:06 -0700 Subject: [PATCH 004/105] fix(e2e): clean up batch files reliably and expire Azure inputs --- tests/e2e/batches/COVERAGE.md | 18 ++ tests/e2e/batches/batch_cleanup.py | 90 +++++++++ tests/e2e/batches/batch_client.py | 16 +- tests/e2e/batches/capabilities.py | 4 + tests/e2e/batches/conftest.py | 10 +- tests/e2e/batches/test_batch_cleanup.py | 187 ++++++++++++++++++ tests/e2e/batches/test_batches_e2e.py | 80 ++++---- .../test_managed_files_enforcement_e2e.py | 3 +- tests/e2e/lifecycle.py | 23 ++- 9 files changed, 381 insertions(+), 50 deletions(-) create mode 100644 tests/e2e/batches/batch_cleanup.py create mode 100644 tests/e2e/batches/test_batch_cleanup.py diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index 8a7b68511ec..899530dde2b 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -120,6 +120,24 @@ create traverse gateway -> gateway -> OpenAI (LIT-5347, PR #36240). The pin: nested managed ids round-trip retrieve. This self-chaining only needs the proxy to reach its own `PROXY_BASE_URL`, which holds both locally and on the e2e stage. +## Cleanup + +Batch teardown cancels active batches before deleting their input files and keys. +Raw file IDs from both `model_param` and `provider_fallback` uploads use the upload +provider when deleted. Model-encoded and managed file IDs route themselves + +File deletion and batch cancellation check their responses and retry transient +failures up to three times. Teardown attempts every registered cleanup before +reporting failures as test errors. Already deleted files and batches that are +terminal are safe to clean up again. Cancellation polls for up to ten minutes +before input deletion, because accepting cancellation does not finish it + +Azure input uploads request `expires_after` anchored to `created_at` with +`seconds=1209600`, and the lifecycle tests check the returned expiry. This is a +fallback for interrupted runs: immediate deletion remains the normal cleanup. +Azure's minimum supported native expiry is 14 days, so a three-day expiry cannot +be requested through its Files API + ## Terminal state + cost write-back (cross-run marker baton) The 24h completion window rules out submit-and-wait inside one run, so diff --git a/tests/e2e/batches/batch_cleanup.py b/tests/e2e/batches/batch_cleanup.py new file mode 100644 index 00000000000..a5b5e9bba37 --- /dev/null +++ b/tests/e2e/batches/batch_cleanup.py @@ -0,0 +1,90 @@ +from collections.abc import Callable +from time import monotonic, sleep +from typing import Final, Protocol + +from pydantic import BaseModel + +from batch_client import BatchObject, FileDeleteResponse +from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApiError + +CLEANUP_DELAYS: Final = (1.0, 2.0, 4.0) +BATCH_TERMINAL_STATUSES: Final = frozenset({"completed", "failed", "expired", "cancelled"}) +BATCH_CANCEL_TIMEOUT_SECONDS: Final = 600.0 +BATCH_CANCEL_POLL_SECONDS: Final = 10.0 + + +class BatchCleanupClient(Protocol): + def delete_file(self, file_id: str, *, key: str, provider: str | None = None) -> Result[FileDeleteResponse]: ... + + def retrieve_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: ... + + def cancel_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: ... + + +def cleanup_result[R: BaseModel]( + action: Callable[[], Result[R]], *, wait: Callable[[float], None] = sleep +) -> Result[R]: + for delay, result in ((delay, action()) for delay in CLEANUP_DELAYS): + match result: + case NetworkError() | RateLimitedError(): + wait(delay) + case UnknownApiError(status_code=code) if code in {408, 429, 500, 502, 503, 504}: + wait(delay) + case _: + return result + return action() + + +def _require_cleanup_success[R: BaseModel](result: Result[R], operation: str) -> R: + match result: + case Success(data=data): + return data + case UnknownApiError(status_code=code): + raise AssertionError(f"{operation} failed: HTTP {code}") + case _: + raise AssertionError(f"{operation} failed: {result.kind}") + + +def cleanup_file(client: BatchCleanupClient, file_id: str, *, key: str, provider: str | None = None) -> None: + result: Final = cleanup_result(lambda: client.delete_file(file_id, key=key, provider=provider)) + if isinstance(result, UnknownApiError) and result.status_code == 404: + return + deleted: Final = _require_cleanup_success(result, f"Delete file {file_id}") + assert deleted.deleted, f"Delete file {file_id} did not confirm deletion" + + +def cleanup_batch( + client: BatchCleanupClient, + batch_id: str, + *, + key: str, + provider: str | None = None, + wait: Callable[[float], None] = sleep, + clock: Callable[[], float] = monotonic, +) -> None: + fetched: Final = _require_cleanup_success( + cleanup_result(lambda: client.retrieve_batch(batch_id, key=key, provider=provider)), + f"Retrieve batch {batch_id} for cleanup", + ) + if fetched.status in BATCH_TERMINAL_STATUSES: + return + if fetched.status != "cancelling": + result: Final = cleanup_result(lambda: client.cancel_batch(batch_id, key=key, provider=provider)) + if not (isinstance(result, UnknownApiError) and result.status_code in {400, 409}): + cancelled: Final = _require_cleanup_success(result, f"Cancel batch {batch_id}") + assert cancelled.status in BATCH_TERMINAL_STATUSES | {"cancelling"}, ( + f"Cancel batch {batch_id} left status {cancelled.status}" + ) + deadline: Final = clock() + BATCH_CANCEL_TIMEOUT_SECONDS + while True: + current = _require_cleanup_success( + cleanup_result(lambda: client.retrieve_batch(batch_id, key=key, provider=provider)), + f"Retrieve batch {batch_id} after cancellation", + ) + if current.status in BATCH_TERMINAL_STATUSES: + return + assert current.status == "cancelling", f"Cancel batch {batch_id} left status {current.status}" + assert clock() < deadline, ( + f"Batch {batch_id} cancellation did not finish within {BATCH_CANCEL_TIMEOUT_SECONDS}s" + ) + wait(BATCH_CANCEL_POLL_SECONDS) diff --git a/tests/e2e/batches/batch_client.py b/tests/e2e/batches/batch_client.py index 31e49f22450..84a902b0b11 100644 --- a/tests/e2e/batches/batch_client.py +++ b/tests/e2e/batches/batch_client.py @@ -13,8 +13,9 @@ co-located here because only this suite uses them. from __future__ import annotations from dataclasses import dataclass +from typing import Final, Literal -from pydantic import BaseModel +from pydantic import BaseModel, Field from proxy_client import ProxyClient from e2e_http import ( @@ -27,6 +28,18 @@ from e2e_http import ( from models import LiteLLMParamsBody UPLOAD_FILENAME = "batch_input.jsonl" +AZURE_FILE_EXPIRY_SECONDS: Final = 14 * 24 * 60 * 60 + + +class ExpiringFileUploadForm(FileUploadForm): + expires_after_anchor: Literal["created_at"] = Field(default="created_at", alias="expires_after[anchor]") + expires_after_seconds: int = Field(default=AZURE_FILE_EXPIRY_SECONDS, alias="expires_after[seconds]") + + +def batch_upload_form(provider: str, *, target_model_names: str | None = None) -> FileUploadForm: + if provider == "azure": + return ExpiringFileUploadForm(target_model_names=target_model_names) + return FileUploadForm(target_model_names=target_model_names) class FileObject(BaseModel): @@ -37,6 +50,7 @@ class FileObject(BaseModel): bytes: int | None = None status: str | None = None created_at: int | None = None + expires_at: int | None = None class FileList(BaseModel): diff --git a/tests/e2e/batches/capabilities.py b/tests/e2e/batches/capabilities.py index 1bcea0a61ee..17749c2fb87 100644 --- a/tests/e2e/batches/capabilities.py +++ b/tests/e2e/batches/capabilities.py @@ -108,6 +108,10 @@ class Capability: def id(self) -> str: return f"{self.provider}-{self.scenario}" + @property + def file_provider(self) -> str | None: + return self.provider if self.scenario in {"model_param", "provider_fallback"} else None + @property def jsonl_model(self) -> str: # Always the provider deployment name. Unified routes via diff --git a/tests/e2e/batches/conftest.py b/tests/e2e/batches/conftest.py index 3b133fab680..91a365b6b92 100644 --- a/tests/e2e/batches/conftest.py +++ b/tests/e2e/batches/conftest.py @@ -13,7 +13,7 @@ the proxy config. from __future__ import annotations import os -from typing import Iterator +from typing import Final, Iterator import pytest @@ -21,6 +21,7 @@ from batch_client import BatchClient, build_client from capabilities import PROVIDERS from e2e_config import MANAGED_FILES_OPT_IN_ENV from e2e_http import NoBody +from lifecycle import ResourceManager from proxy_client import ProxyClient @@ -52,6 +53,13 @@ def client(proxy: ProxyClient) -> BatchClient: return build_client(proxy) +@pytest.fixture +def resources(client: BatchClient) -> Iterator[ResourceManager]: + manager: Final = ResourceManager(client=client.proxy, strict_cleanup=True) + yield manager + manager.teardown() + + @pytest.fixture(scope="session") def batch_deployments(client: BatchClient) -> Iterator[None]: probe = client.proxy.probe("/health/liveliness", params=NoBody()) diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py new file mode 100644 index 00000000000..8ebfd750360 --- /dev/null +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -0,0 +1,187 @@ +from builtins import ExceptionGroup +from collections.abc import Iterator +from dataclasses import dataclass, field +from typing import Final + +import pytest + +from batch_cleanup import BATCH_CANCEL_TIMEOUT_SECONDS, CLEANUP_DELAYS, cleanup_batch, cleanup_file, cleanup_result +from batch_client import AZURE_FILE_EXPIRY_SECONDS, BatchObject, FileDeleteResponse, batch_upload_form +from capabilities import CAPABILITIES, Capability +from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApiError +from lifecycle import ResourceManager +from models import KeyGenerateBody + + +@dataclass +class CleanupClient: + files: Iterator[Result[FileDeleteResponse]] = field(default_factory=lambda: iter(())) + batches: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) + cancellations: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) + calls: list[str] = field(default_factory=list) + + def delete_file(self, file_id: str, *, key: str, provider: str | None = None) -> Result[FileDeleteResponse]: + self.calls.append(f"delete {provider} {file_id}") + return next(self.files) + + def retrieve_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: + self.calls.append(f"retrieve {provider} {batch_id}") + return next(self.batches) + + def cancel_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: + self.calls.append(f"cancel {provider} {batch_id}") + return next(self.cancellations) + + def generate_key(self, body: KeyGenerateBody) -> str: + return "test-key" + + def delete_key(self, key: str) -> None: + self.calls.append(f"delete key {key}") + + def delete_customers(self, user_ids: list[str]) -> None: + self.calls.append(f"delete customers {user_ids}") + + +def batch(status: str) -> Success[BatchObject]: + return Success(status_code=200, data=BatchObject(id="batch-1", status=status)) + + +def deleted_file(*, deleted: bool = True) -> Success[FileDeleteResponse]: + return Success(status_code=200, data=FileDeleteResponse(id="file-1", deleted=deleted)) + + +class TestFileCleanup: + @pytest.mark.parametrize("cap", CAPABILITIES, ids=[cap.id for cap in CAPABILITIES]) + def test_deletes_raw_files_through_the_upload_provider(self, cap: Capability) -> None: + client: Final = CleanupClient(files=iter((deleted_file(),))) + cleanup_file(client, "file-1", key="test-key", provider=cap.file_provider) + expected_provider: Final = cap.provider if cap.scenario in {"model_param", "provider_fallback"} else None + assert client.calls == [f"delete {expected_provider} file-1"] + + def test_failed_delete_is_reported_after_remaining_resources_are_cleaned(self) -> None: + client: Final = CleanupClient(files=iter((UnknownApiError(status_code=403, body="secret response"),))) + manager: Final = ResourceManager(client=client, strict_cleanup=True) + key: Final = manager.key() + manager.defer(lambda: cleanup_file(client, "file-1", key=key, provider="azure")) + with pytest.raises(ExceptionGroup) as caught: + manager.teardown() + assert client.calls == ["delete azure file-1", "delete key test-key"] + assert len(caught.value.exceptions) == 1 + assert str(caught.value.exceptions[0]) == "Delete file file-1 failed: HTTP 403" + + def test_success_response_must_confirm_deletion(self) -> None: + client: Final = CleanupClient(files=iter((deleted_file(deleted=False),))) + with pytest.raises(AssertionError, match="did not confirm deletion"): + cleanup_file(client, "file-1", key="test-key") + + def test_cleanup_is_idempotent_when_file_is_already_deleted(self) -> None: + client: Final = CleanupClient(files=iter((UnknownApiError(status_code=404, body="missing"),))) + cleanup_file(client, "file-1", key="test-key", provider="azure") + assert client.calls == ["delete azure file-1"] + + def test_default_resource_cleanup_keeps_existing_best_effort_behavior(self) -> None: + client: Final = CleanupClient(files=iter((UnknownApiError(status_code=403, body="forbidden"),))) + manager: Final = ResourceManager(client=client) + key: Final = manager.key() + manager.defer(lambda: cleanup_file(client, "file-1", key=key)) + manager.teardown() + assert client.calls == ["delete None file-1", "delete key test-key"] + + +class TestCleanupRetries: + @pytest.mark.parametrize( + "failure", + [NetworkError(message="offline"), RateLimitedError(), UnknownApiError(status_code=503, body="unavailable")], + ) + def test_transient_error_retries_and_returns_success(self, failure: Result[FileDeleteResponse]) -> None: + outcomes: Final = iter((failure, deleted_file())) + delays: Final[list[float]] = [] + result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays.append) + assert isinstance(result, Success) and result.data.deleted + assert delays == [1.0] + + def test_persistent_error_has_bounded_retries(self) -> None: + failure: Final = UnknownApiError(status_code=503, body="unavailable") + outcomes: Final[Iterator[Result[FileDeleteResponse]]] = iter((failure,) * (len(CLEANUP_DELAYS) + 1)) + delays: Final[list[float]] = [] + result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays.append) + assert result is failure + assert tuple(delays) == CLEANUP_DELAYS + assert next(outcomes, None) is None + + def test_permanent_error_is_not_retried(self) -> None: + failure: Final = UnknownApiError(status_code=403, body="forbidden") + outcomes: Final = iter((failure, deleted_file())) + delays: Final[list[float]] = [] + assert cleanup_result(lambda: next(outcomes), wait=delays.append) is failure + assert delays == [] + assert isinstance(next(outcomes), Success) + + +class TestBatchCancellation: + def test_cancelling_batch_is_polled_until_terminal_without_cancelling_again(self) -> None: + client: Final = CleanupClient(batches=iter((batch("cancelling"), batch("cancelling"), batch("cancelled")))) + delays: Final[list[float]] = [] + cleanup_batch(client, "batch-1", key="test-key", wait=delays.append) + assert client.calls == ["retrieve None batch-1"] * 3 + assert delays == [10.0] + + def test_cancellation_timeout_is_reported_but_file_and_key_cleanup_still_run(self) -> None: + client: Final = CleanupClient( + batches=iter((batch("cancelling"), batch("cancelling"))), files=iter((deleted_file(),)) + ) + ticks: Final = iter((0.0, BATCH_CANCEL_TIMEOUT_SECONDS)) + manager: Final = ResourceManager(client=client, strict_cleanup=True) + key: Final = manager.key() + manager.defer(lambda: cleanup_file(client, "file-1", key=key)) + manager.defer(lambda: cleanup_batch(client, "batch-1", key=key, clock=lambda: next(ticks))) + with pytest.raises(ExceptionGroup) as caught: + manager.teardown() + assert "cancellation did not finish" in str(caught.value.exceptions[0]) + assert client.calls == [ + "retrieve None batch-1", + "retrieve None batch-1", + "delete None file-1", + "delete key test-key", + ] + + @pytest.mark.parametrize("status", ["completed", "failed", "expired", "cancelled"]) + def test_inactive_batch_needs_no_cancellation(self, status: str) -> None: + client: Final = CleanupClient(batches=iter((batch(status),))) + cleanup_batch(client, "batch-1", key="test-key") + assert client.calls == ["retrieve None batch-1"] + + def test_active_batch_is_cancelled_through_its_provider(self) -> None: + client: Final = CleanupClient( + batches=iter((batch("in_progress"), batch("cancelled"))), cancellations=iter((batch("cancelling"),)) + ) + cleanup_batch(client, "batch-1", key="test-key", provider="azure") + assert client.calls == ["retrieve azure batch-1", "cancel azure batch-1", "retrieve azure batch-1"] + + @pytest.mark.parametrize("status", ["completed", "in_progress"]) + def test_cancellation_conflict_is_accepted_only_when_batch_became_inactive(self, status: str) -> None: + client: Final = CleanupClient( + batches=iter((batch("in_progress"), batch(status))), + cancellations=iter((UnknownApiError(status_code=409, body="conflict"),)), + ) + if status == "completed": + cleanup_batch(client, "batch-1", key="test-key") + else: + with pytest.raises(AssertionError, match="Cancel batch batch-1 left status in_progress"): + cleanup_batch(client, "batch-1", key="test-key") + assert client.calls == ["retrieve None batch-1", "cancel None batch-1", "retrieve None batch-1"] + + +class TestAzureFileExpiry: + def test_azure_form_serializes_native_expiry_for_the_proxy(self) -> None: + form: Final = batch_upload_form("azure", target_model_names="azure-test") + assert form.model_dump(by_alias=True, exclude_none=True) == { + "purpose": "batch", + "target_model_names": "azure-test", + "expires_after[anchor]": "created_at", + "expires_after[seconds]": AZURE_FILE_EXPIRY_SECONDS, + } + + @pytest.mark.parametrize("provider", ["openai", "vertex_ai", "bedrock"]) + def test_other_providers_keep_their_existing_upload_fields(self, provider: str) -> None: + assert batch_upload_form(provider).model_dump(by_alias=True, exclude_none=True) == {"purpose": "batch"} diff --git a/tests/e2e/batches/test_batches_e2e.py b/tests/e2e/batches/test_batches_e2e.py index ed7cf656d01..1b4a6ed266f 100644 --- a/tests/e2e/batches/test_batches_e2e.py +++ b/tests/e2e/batches/test_batches_e2e.py @@ -21,14 +21,16 @@ import os import re import time from datetime import datetime, timedelta, timezone -from typing import Callable import pytest from pydantic import BaseModel from e2e_config import PROXY_BASE_URL, unique_marker +from batch_cleanup import cleanup_batch, cleanup_file from batch_client import ( + AZURE_FILE_EXPIRY_SECONDS, + batch_upload_form, UPLOAD_FILENAME, BatchClient, BatchCreateBody, @@ -155,19 +157,19 @@ def upload_for_scenario( if cap.scenario == "encoded": return client.upload_file( content=content, - form=FileUploadForm(purpose="batch"), + form=batch_upload_form(cap.provider), model=cap.model, key=key, ) if cap.scenario == "unified": return client.upload_file( content=content, - form=FileUploadForm(purpose="batch", target_model_names=cap.model), + form=batch_upload_form(cap.provider, target_model_names=cap.model), key=key, ) return client.upload_file( content=content, - form=FileUploadForm(purpose="batch"), + form=batch_upload_form(cap.provider), key=key, provider=cap.provider, ) @@ -188,20 +190,11 @@ def create_for_scenario( def op_provider(cap: Capability) -> str | None: - """provider_fallback ids are raw, so retrieve/cancel/list/delete need the provider + """provider_fallback batch ids are raw, so retrieve/cancel/list need the provider hint; the other scenarios encode it into the id and route automatically.""" return cap.provider if cap.scenario == "provider_fallback" else None -def quietly(action: Callable[[], object]) -> Callable[[], None]: - """Adapt a value-returning call into a best-effort cleanup the teardown can run.""" - - def run() -> None: - action() - - return run - - def assert_file_object(file: FileObject, *, provider: str) -> None: assert file.object == "file", f"file.object={file.object!r}" assert file.purpose == "batch", f"file.purpose={file.purpose!r}" @@ -209,6 +202,10 @@ def assert_file_object(file: FileObject, *, provider: str) -> None: if provider != "bedrock": assert file.bytes > 0, f"file.bytes={file.bytes!r}" assert file.status, "file.status missing" + if provider == "azure": + assert file.expires_at is not None, "Azure batch input has no automatic expiry" + assert file.created_at is not None + assert file.expires_at - file.created_at == AZURE_FILE_EXPIRY_SECONDS assert ( file.created_at is not None and file.created_at > 0 ), "file.created_at missing" @@ -249,7 +246,7 @@ def test_batch_lifecycle( file = unwrap(upload_for_scenario(client, cap, render_jsonl(cap.jsonl_model), key)) resources.defer( - quietly(lambda: client.delete_file(file.id, key=key, provider=provider)) + lambda: cleanup_file(client, file.id, key=key, provider=cap.file_provider) ) assert_file_object(file, provider=cap.provider) assert matches_id_shape( @@ -260,7 +257,7 @@ def test_batch_lifecycle( require_successful_call(created) batch = BatchObject.model_validate_json(created.body) resources.defer( - quietly(lambda: client.cancel_batch(batch.id, key=key, provider=provider)) + lambda: cleanup_batch(client, batch.id, key=key, provider=provider) ) assert batch.id, f"create returned no batch id (body={created.body[:200]})" @@ -339,7 +336,7 @@ def test_batch_key_model_access_denied( denied_upload = client.upload_file( content=render_jsonl(AZURE_BATCH_MODEL), - form=FileUploadForm(purpose="batch"), + form=batch_upload_form("azure"), model=AZURE_BATCH_MODEL, key=key, ) @@ -356,7 +353,7 @@ def test_batch_key_model_access_denied( ) ).id resources.defer( - quietly(lambda: client.delete_file(raw_file, key=key, provider="openai")) + lambda: cleanup_file(client, raw_file, key=key, provider="openai") ) denied_create = client.create_batch( @@ -383,6 +380,7 @@ def test_file_upload_and_delete_outputs( key=key, ) ) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert_file_object(file, provider="openai") deleted = unwrap(client.delete_file(file.id, key=key)) @@ -458,12 +456,12 @@ def test_rate_limited_batch_create_leaves_no_unattributed_spend_row( key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, 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))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) _ = client.proxy.poll_logs_for_key(key, min_rows=1) @@ -517,7 +515,7 @@ class TestBatchFileContent: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert file.id downloaded = client.proxy.transport.download( @@ -559,11 +557,11 @@ class TestBatchFileContent: file = unwrap( client.upload_file( content=payload, - form=FileUploadForm(purpose="batch", target_model_names=provider.model), + form=batch_upload_form(provider.name, target_model_names=provider.model), key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert_file_object(file, provider=provider.name) assert is_managed_id(file.id), ( f"{provider.name}: unified upload must return a managed file id, got {file.id!r}" @@ -626,7 +624,7 @@ class TestOpenAIFiles: ) ) resources.defer( - quietly(lambda: client.delete_file(file.id, key=key, provider="openai")) + lambda: cleanup_file(client, file.id, key=key, provider="openai") ) listed = unwrap(client.list_files(key=key)) @@ -690,7 +688,7 @@ class TestOpenAIFiles: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) fetched = unwrap(client.retrieve_file(file.id, key=key)) assert fetched.id == file.id, "retrieve must echo the uploaded file id" @@ -760,7 +758,7 @@ class TestBatchRateLimitErrorMapping: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) created = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) @@ -813,7 +811,7 @@ class TestBatchEnqueuedTokenLimit: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) return file def _generate_enqueued_key( @@ -861,7 +859,7 @@ class TestBatchEnqueuedTokenLimit: ) require_successful_call(created) batch = BatchObject.model_validate_json(created.body) - resources.defer(quietly(lambda: client.cancel_batch(batch.id, key=key))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) @pytest.mark.covers( "quota_management.ratelimit.batch_enqueued_tokens.blocks_when_exhausted", @@ -904,7 +902,7 @@ class TestBatchEnqueuedTokenLimit: first = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) require_successful_call(first) first_batch = BatchObject.model_validate_json(first.body) - resources.defer(quietly(lambda: client.cancel_batch(first_batch.id, key=key))) + resources.defer(lambda: cleanup_batch(client, first_batch.id, key=key)) blocked = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) assert blocked.status_code == 429, ( @@ -928,7 +926,7 @@ class TestBatchEnqueuedTokenLimit: ) require_successful_call(retried) retry_batch = BatchObject.model_validate_json(retried.body) - resources.defer(quietly(lambda: client.cancel_batch(retry_batch.id, key=key))) + resources.defer(lambda: cleanup_batch(client, retry_batch.id, key=key)) ASSUME_ROLE_RAW_MODEL = "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0" @@ -984,13 +982,13 @@ class TestBedrockBatchAssumeRole: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert_file_object(file, provider="bedrock") 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))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) assert batch.id, f"assume-role create returned no batch id: {created.body[:200]}" assert is_managed_id(batch.id), ( @@ -1044,7 +1042,7 @@ class TestGeminiFiles: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert_file_object(file, provider="gemini") assert file.id, "gemini file upload returned no id" @@ -1099,13 +1097,13 @@ class TestHostedVllmBatch: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert_file_object(file, provider="hosted_vllm") 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))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) assert batch.id, f"hosted_vllm create returned no batch id: {created.body[:200]}" assert batch.status in CREATED_BATCH_STATUSES, ( @@ -1192,7 +1190,7 @@ class TestBatchFailurePaths: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) created = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) require_successful_call(created) @@ -1243,12 +1241,12 @@ class TestBatchFailurePaths: file = unwrap( client.upload_file( content=render_jsonl(AZURE_BATCH_RAW_MODEL), - form=FileUploadForm(purpose="batch"), + form=batch_upload_form("azure"), model=AZURE_BATCH_MODEL, key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert decoded_model_from_id(file.id) == AZURE_BATCH_MODEL, ( f"upload did not encode the azure deployment into the file id: {file.id!r}" ) @@ -1258,7 +1256,7 @@ class TestBatchFailurePaths: ) require_successful_call(created) batch = BatchObject.model_validate_json(created.body) - resources.defer(quietly(lambda: client.cancel_batch(batch.id, key=key))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) assert decoded_model_from_id(batch.id) == AZURE_BATCH_MODEL, ( "create with a foreign encoded file id must route by the file's embedded model, " @@ -1307,7 +1305,7 @@ class TestBatchSecondHop: key=key, ) ) - resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + resources.defer(lambda: cleanup_file(client, file.id, key=key)) assert is_managed_id(file.id), ( f"second-hop unified upload must return a managed file id, got {file.id!r}" ) @@ -1315,7 +1313,7 @@ class TestBatchSecondHop: 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))) + resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) assert is_managed_id(batch.id), ( f"second-hop create must return a managed batch id, got {batch.id!r}" diff --git a/tests/e2e/batches/test_managed_files_enforcement_e2e.py b/tests/e2e/batches/test_managed_files_enforcement_e2e.py index 7ad0b16adc3..4f703cf0fdc 100644 --- a/tests/e2e/batches/test_managed_files_enforcement_e2e.py +++ b/tests/e2e/batches/test_managed_files_enforcement_e2e.py @@ -21,6 +21,7 @@ from typing import Iterator import pytest from batch_client import BatchClient, FileObject +from batch_cleanup import cleanup_file from capabilities import batch_model_name, is_managed_id, openai_batch_params from e2e_config import unique_marker from e2e_http import FileUploadForm, Result, UnknownApiError, unwrap @@ -108,7 +109,7 @@ def test_cross_user_managed_id_denied_owner_allowed( key=owner_key, ) ) - resources.defer(lambda: client.delete_file(uploaded.id, key=owner_key)) + resources.defer(lambda: cleanup_file(client, uploaded.id, key=owner_key)) assert is_managed_id(uploaded.id), f"expected a managed unified file id, got {uploaded.id}" denied = client.retrieve_file(uploaded.id, key=other_key) diff --git a/tests/e2e/lifecycle.py b/tests/e2e/lifecycle.py index c9a67ebdb8c..eb9704d4dcb 100644 --- a/tests/e2e/lifecycle.py +++ b/tests/e2e/lifecycle.py @@ -8,8 +8,9 @@ ResourceManager; the test registers a cleanup for every resource it creates, and the fixture's teardown releases them all even when the test body raises. """ +from builtins import ExceptionGroup from dataclasses import dataclass, field -from typing import Callable, List, Protocol, runtime_checkable +from typing import Callable, Final, List, Protocol, runtime_checkable from proxy_client import ProxyClient from models import KeyGenerateBody @@ -52,6 +53,7 @@ class ResourceManager: """ client: ResourceClient + strict_cleanup: bool = False _cleanups: List[Callable[[], object]] = field( default_factory=list ) # mutable-ok: append-only teardown registry @@ -82,8 +84,17 @@ class ResourceManager: return customer_id def teardown(self) -> None: - for cleanup in reversed(self._cleanups): - try: - cleanup() - except Exception: - pass # best-effort: a failed cleanup must not block the rest + failures: Final = tuple( + failure for cleanup in reversed(self._cleanups) + if (failure := _run_cleanup(cleanup)) is not None + ) + if failures and self.strict_cleanup: + raise ExceptionGroup("Resource cleanup failed", failures) + + +def _run_cleanup(cleanup: Callable[[], object]) -> Exception | None: + try: + cleanup() + except Exception as exc: + return exc + return None From a56c60e8924f6f956200c816142fb0cb994fa3ed Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 12:27:51 -0700 Subject: [PATCH 005/105] fix(e2e): wait for managed batch cancellation before deleting inputs --- tests/e2e/batches/COVERAGE.md | 5 +++-- tests/e2e/batches/batch_cleanup.py | 10 +++++++++- tests/e2e/batches/test_batch_cleanup.py | 14 ++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index 899530dde2b..69ba9d781ec 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -129,8 +129,9 @@ provider when deleted. Model-encoded and managed file IDs route themselves File deletion and batch cancellation check their responses and retry transient failures up to three times. Teardown attempts every registered cleanup before reporting failures as test errors. Already deleted files and batches that are -terminal are safe to clean up again. Cancellation polls for up to ten minutes -before input deletion, because accepting cancellation does not finish it +terminal are safe to clean up again. Managed batch cancellation polls for up to eleven minutes +before input deletion: the ten-minute provider window plus a propagation margin. +Raw and model-encoded inputs can be deleted after cancellation is accepted Azure input uploads request `expires_after` anchored to `created_at` with `seconds=1209600`, and the lifecycle tests check the returned expiry. This is a diff --git a/tests/e2e/batches/batch_cleanup.py b/tests/e2e/batches/batch_cleanup.py index a5b5e9bba37..dd79c776758 100644 --- a/tests/e2e/batches/batch_cleanup.py +++ b/tests/e2e/batches/batch_cleanup.py @@ -5,11 +5,12 @@ from typing import Final, Protocol from pydantic import BaseModel from batch_client import BatchObject, FileDeleteResponse +from capabilities import is_managed_id from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApiError CLEANUP_DELAYS: Final = (1.0, 2.0, 4.0) BATCH_TERMINAL_STATUSES: Final = frozenset({"completed", "failed", "expired", "cancelled"}) -BATCH_CANCEL_TIMEOUT_SECONDS: Final = 600.0 +BATCH_CANCEL_TIMEOUT_SECONDS: Final = 660.0 BATCH_CANCEL_POLL_SECONDS: Final = 10.0 @@ -62,12 +63,15 @@ def cleanup_batch( wait: Callable[[float], None] = sleep, clock: Callable[[], float] = monotonic, ) -> None: + needs_terminal_state: Final = is_managed_id(batch_id) fetched: Final = _require_cleanup_success( cleanup_result(lambda: client.retrieve_batch(batch_id, key=key, provider=provider)), f"Retrieve batch {batch_id} for cleanup", ) if fetched.status in BATCH_TERMINAL_STATUSES: return + if fetched.status == "cancelling" and not needs_terminal_state: + return if fetched.status != "cancelling": result: Final = cleanup_result(lambda: client.cancel_batch(batch_id, key=key, provider=provider)) if not (isinstance(result, UnknownApiError) and result.status_code in {400, 409}): @@ -75,6 +79,8 @@ def cleanup_batch( assert cancelled.status in BATCH_TERMINAL_STATUSES | {"cancelling"}, ( f"Cancel batch {batch_id} left status {cancelled.status}" ) + if not needs_terminal_state: + return deadline: Final = clock() + BATCH_CANCEL_TIMEOUT_SECONDS while True: current = _require_cleanup_success( @@ -84,6 +90,8 @@ def cleanup_batch( if current.status in BATCH_TERMINAL_STATUSES: return assert current.status == "cancelling", f"Cancel batch {batch_id} left status {current.status}" + if not needs_terminal_state: + return assert clock() < deadline, ( f"Batch {batch_id} cancellation did not finish within {BATCH_CANCEL_TIMEOUT_SECONDS}s" ) diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py index 8ebfd750360..9715370d9b7 100644 --- a/tests/e2e/batches/test_batch_cleanup.py +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -12,6 +12,8 @@ from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApi from lifecycle import ResourceManager from models import KeyGenerateBody +MANAGED_BATCH_ID: Final = "bGl0ZWxsbV9wcm94eTtiYXRjaC0x" + @dataclass class CleanupClient: @@ -122,8 +124,8 @@ class TestBatchCancellation: def test_cancelling_batch_is_polled_until_terminal_without_cancelling_again(self) -> None: client: Final = CleanupClient(batches=iter((batch("cancelling"), batch("cancelling"), batch("cancelled")))) delays: Final[list[float]] = [] - cleanup_batch(client, "batch-1", key="test-key", wait=delays.append) - assert client.calls == ["retrieve None batch-1"] * 3 + cleanup_batch(client, MANAGED_BATCH_ID, key="test-key", wait=delays.append) + assert client.calls == [f"retrieve None {MANAGED_BATCH_ID}"] * 3 assert delays == [10.0] def test_cancellation_timeout_is_reported_but_file_and_key_cleanup_still_run(self) -> None: @@ -134,13 +136,13 @@ class TestBatchCancellation: manager: Final = ResourceManager(client=client, strict_cleanup=True) key: Final = manager.key() manager.defer(lambda: cleanup_file(client, "file-1", key=key)) - manager.defer(lambda: cleanup_batch(client, "batch-1", key=key, clock=lambda: next(ticks))) + manager.defer(lambda: cleanup_batch(client, MANAGED_BATCH_ID, key=key, clock=lambda: next(ticks))) with pytest.raises(ExceptionGroup) as caught: manager.teardown() assert "cancellation did not finish" in str(caught.value.exceptions[0]) assert client.calls == [ - "retrieve None batch-1", - "retrieve None batch-1", + f"retrieve None {MANAGED_BATCH_ID}", + f"retrieve None {MANAGED_BATCH_ID}", "delete None file-1", "delete key test-key", ] @@ -156,7 +158,7 @@ class TestBatchCancellation: batches=iter((batch("in_progress"), batch("cancelled"))), cancellations=iter((batch("cancelling"),)) ) cleanup_batch(client, "batch-1", key="test-key", provider="azure") - assert client.calls == ["retrieve azure batch-1", "cancel azure batch-1", "retrieve azure batch-1"] + assert client.calls == ["retrieve azure batch-1", "cancel azure batch-1"] @pytest.mark.parametrize("status", ["completed", "in_progress"]) def test_cancellation_conflict_is_accepted_only_when_batch_became_inactive(self, status: str) -> None: From 6bdf206cc5228e85b47c908a2f4977374656289b Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 12:48:07 -0700 Subject: [PATCH 006/105] fix(e2e): accept managed file deletion responses --- tests/e2e/batches/COVERAGE.md | 3 +++ tests/e2e/batches/batch_cleanup.py | 4 +++- tests/e2e/batches/batch_client.py | 2 +- tests/e2e/batches/test_batch_cleanup.py | 15 +++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index 69ba9d781ec..f95ea1f2649 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -139,6 +139,9 @@ fallback for interrupted runs: immediate deletion remains the normal cleanup. Azure's minimum supported native expiry is 14 days, so a three-day expiry cannot be requested through its Files API +The Azure entry in `files_settings` must use `api_version: 2025-04-01-preview` +for raw uploads to honor expiry, matching the batch deployment's API version + ## Terminal state + cost write-back (cross-run marker baton) The 24h completion window rules out submit-and-wait inside one run, so diff --git a/tests/e2e/batches/batch_cleanup.py b/tests/e2e/batches/batch_cleanup.py index dd79c776758..3fd6802f696 100644 --- a/tests/e2e/batches/batch_cleanup.py +++ b/tests/e2e/batches/batch_cleanup.py @@ -51,7 +51,9 @@ def cleanup_file(client: BatchCleanupClient, file_id: str, *, key: str, provider if isinstance(result, UnknownApiError) and result.status_code == 404: return deleted: Final = _require_cleanup_success(result, f"Delete file {file_id}") - assert deleted.deleted, f"Delete file {file_id} did not confirm deletion" + assert deleted.deleted is True or ( + deleted.deleted is None and is_managed_id(file_id) and deleted.id == file_id and deleted.object == "file" + ), f"Delete file {file_id} did not confirm deletion" def cleanup_batch( diff --git a/tests/e2e/batches/batch_client.py b/tests/e2e/batches/batch_client.py index 84a902b0b11..c9c77e1f12e 100644 --- a/tests/e2e/batches/batch_client.py +++ b/tests/e2e/batches/batch_client.py @@ -99,7 +99,7 @@ class BatchList(BaseModel): class FileDeleteResponse(BaseModel): id: str object: str | None = None - deleted: bool + deleted: bool | None = None class BatchCreateBody(BaseModel): diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py index 9715370d9b7..15dead6d36d 100644 --- a/tests/e2e/batches/test_batch_cleanup.py +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -12,6 +12,7 @@ from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApi from lifecycle import ResourceManager from models import KeyGenerateBody +MANAGED_FILE_ID: Final = "bGl0ZWxsbV9wcm94eTtmaWxlLTE=" MANAGED_BATCH_ID: Final = "bGl0ZWxsbV9wcm94eTtiYXRjaC0x" @@ -53,6 +54,20 @@ def deleted_file(*, deleted: bool = True) -> Success[FileDeleteResponse]: class TestFileCleanup: + def test_managed_delete_accepts_the_deleted_file_object(self) -> None: + response: Final = Success( + status_code=200, data=FileDeleteResponse.model_validate({"id": MANAGED_FILE_ID, "object": "file"}) + ) + client: Final = CleanupClient(files=iter((response,))) + cleanup_file(client, MANAGED_FILE_ID, key="test-key") + assert client.calls == [f"delete None {MANAGED_FILE_ID}"] + + @pytest.mark.parametrize("file_id", ["file-1", MANAGED_FILE_ID]) + def test_a_success_status_without_a_deletion_confirmation_is_rejected(self, file_id: str) -> None: + client: Final = CleanupClient(files=iter((Success(status_code=200, data=FileDeleteResponse(id=file_id)),))) + with pytest.raises(AssertionError, match="did not confirm deletion"): + cleanup_file(client, file_id, key="test-key") + @pytest.mark.parametrize("cap", CAPABILITIES, ids=[cap.id for cap in CAPABILITIES]) def test_deletes_raw_files_through_the_upload_provider(self, cap: Capability) -> None: client: Final = CleanupClient(files=iter((deleted_file(),))) From a096dd615c71e40be9473338ffeb8d1c17284f51 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 14:39:25 -0700 Subject: [PATCH 007/105] refactor(e2e): use immutable batch cleanup test expectations --- tests/e2e/batches/batch_cleanup.py | 7 +- tests/e2e/batches/test_batch_cleanup.py | 137 ++++++++++++++++-------- 2 files changed, 96 insertions(+), 48 deletions(-) diff --git a/tests/e2e/batches/batch_cleanup.py b/tests/e2e/batches/batch_cleanup.py index 3fd6802f696..722df0c29bc 100644 --- a/tests/e2e/batches/batch_cleanup.py +++ b/tests/e2e/batches/batch_cleanup.py @@ -1,4 +1,5 @@ from collections.abc import Callable +from itertools import count from time import monotonic, sleep from typing import Final, Protocol @@ -84,11 +85,13 @@ def cleanup_batch( if not needs_terminal_state: return deadline: Final = clock() + BATCH_CANCEL_TIMEOUT_SECONDS - while True: - current = _require_cleanup_success( + for current in ( + _require_cleanup_success( cleanup_result(lambda: client.retrieve_batch(batch_id, key=key, provider=provider)), f"Retrieve batch {batch_id} after cancellation", ) + for _ in count() + ): if current.status in BATCH_TERMINAL_STATUSES: return assert current.status == "cancelling", f"Cancel batch {batch_id} left status {current.status}" diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py index 15dead6d36d..66b7079ccc2 100644 --- a/tests/e2e/batches/test_batch_cleanup.py +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -16,33 +16,44 @@ MANAGED_FILE_ID: Final = "bGl0ZWxsbV9wcm94eTtmaWxlLTE=" MANAGED_BATCH_ID: Final = "bGl0ZWxsbV9wcm94eTtiYXRjaC0x" -@dataclass +@dataclass(frozen=True, slots=True) +class ExpectedCalls[T]: + values: Iterator[T] + + def __call__(self, value: T) -> None: + assert next(self.values, None) == value + + def assert_done(self) -> None: + assert tuple(self.values) == () + + +@dataclass(frozen=True, slots=True) class CleanupClient: + calls: ExpectedCalls[str] files: Iterator[Result[FileDeleteResponse]] = field(default_factory=lambda: iter(())) batches: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) cancellations: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) - calls: list[str] = field(default_factory=list) def delete_file(self, file_id: str, *, key: str, provider: str | None = None) -> Result[FileDeleteResponse]: - self.calls.append(f"delete {provider} {file_id}") + self.calls(f"delete {provider} {file_id}") return next(self.files) def retrieve_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: - self.calls.append(f"retrieve {provider} {batch_id}") + self.calls(f"retrieve {provider} {batch_id}") return next(self.batches) def cancel_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: - self.calls.append(f"cancel {provider} {batch_id}") + self.calls(f"cancel {provider} {batch_id}") return next(self.cancellations) def generate_key(self, body: KeyGenerateBody) -> str: return "test-key" def delete_key(self, key: str) -> None: - self.calls.append(f"delete key {key}") + self.calls(f"delete key {key}") def delete_customers(self, user_ids: list[str]) -> None: - self.calls.append(f"delete customers {user_ids}") + self.calls(f"delete customers {user_ids}") def batch(status: str) -> Success[BatchObject]: @@ -58,51 +69,71 @@ class TestFileCleanup: response: Final = Success( status_code=200, data=FileDeleteResponse.model_validate({"id": MANAGED_FILE_ID, "object": "file"}) ) - client: Final = CleanupClient(files=iter((response,))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter((f"delete None {MANAGED_FILE_ID}",))), files=iter((response,)) + ) cleanup_file(client, MANAGED_FILE_ID, key="test-key") - assert client.calls == [f"delete None {MANAGED_FILE_ID}"] + client.calls.assert_done() @pytest.mark.parametrize("file_id", ["file-1", MANAGED_FILE_ID]) def test_a_success_status_without_a_deletion_confirmation_is_rejected(self, file_id: str) -> None: - client: Final = CleanupClient(files=iter((Success(status_code=200, data=FileDeleteResponse(id=file_id)),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter((f"delete None {file_id}",))), + files=iter((Success(status_code=200, data=FileDeleteResponse(id=file_id)),)), + ) with pytest.raises(AssertionError, match="did not confirm deletion"): cleanup_file(client, file_id, key="test-key") + client.calls.assert_done() @pytest.mark.parametrize("cap", CAPABILITIES, ids=[cap.id for cap in CAPABILITIES]) def test_deletes_raw_files_through_the_upload_provider(self, cap: Capability) -> None: - client: Final = CleanupClient(files=iter((deleted_file(),))) - cleanup_file(client, "file-1", key="test-key", provider=cap.file_provider) expected_provider: Final = cap.provider if cap.scenario in {"model_param", "provider_fallback"} else None - assert client.calls == [f"delete {expected_provider} file-1"] + client: Final = CleanupClient( + calls=ExpectedCalls(iter((f"delete {expected_provider} file-1",))), files=iter((deleted_file(),)) + ) + cleanup_file(client, "file-1", key="test-key", provider=cap.file_provider) + client.calls.assert_done() def test_failed_delete_is_reported_after_remaining_resources_are_cleaned(self) -> None: - client: Final = CleanupClient(files=iter((UnknownApiError(status_code=403, body="secret response"),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter(("delete azure file-1", "delete key test-key"))), + files=iter((UnknownApiError(status_code=403, body="secret response"),)), + ) manager: Final = ResourceManager(client=client, strict_cleanup=True) key: Final = manager.key() manager.defer(lambda: cleanup_file(client, "file-1", key=key, provider="azure")) with pytest.raises(ExceptionGroup) as caught: manager.teardown() - assert client.calls == ["delete azure file-1", "delete key test-key"] + client.calls.assert_done() assert len(caught.value.exceptions) == 1 assert str(caught.value.exceptions[0]) == "Delete file file-1 failed: HTTP 403" def test_success_response_must_confirm_deletion(self) -> None: - client: Final = CleanupClient(files=iter((deleted_file(deleted=False),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter(("delete None file-1",))), files=iter((deleted_file(deleted=False),)) + ) with pytest.raises(AssertionError, match="did not confirm deletion"): cleanup_file(client, "file-1", key="test-key") + client.calls.assert_done() def test_cleanup_is_idempotent_when_file_is_already_deleted(self) -> None: - client: Final = CleanupClient(files=iter((UnknownApiError(status_code=404, body="missing"),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter(("delete azure file-1",))), + files=iter((UnknownApiError(status_code=404, body="missing"),)), + ) cleanup_file(client, "file-1", key="test-key", provider="azure") - assert client.calls == ["delete azure file-1"] + client.calls.assert_done() def test_default_resource_cleanup_keeps_existing_best_effort_behavior(self) -> None: - client: Final = CleanupClient(files=iter((UnknownApiError(status_code=403, body="forbidden"),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter(("delete None file-1", "delete key test-key"))), + files=iter((UnknownApiError(status_code=403, body="forbidden"),)), + ) manager: Final = ResourceManager(client=client) key: Final = manager.key() manager.defer(lambda: cleanup_file(client, "file-1", key=key)) manager.teardown() - assert client.calls == ["delete None file-1", "delete key test-key"] + client.calls.assert_done() class TestCleanupRetries: @@ -112,40 +143,54 @@ class TestCleanupRetries: ) def test_transient_error_retries_and_returns_success(self, failure: Result[FileDeleteResponse]) -> None: outcomes: Final = iter((failure, deleted_file())) - delays: Final[list[float]] = [] - result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays.append) + delays: Final = ExpectedCalls(iter((1.0,))) + result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays) assert isinstance(result, Success) and result.data.deleted - assert delays == [1.0] + delays.assert_done() def test_persistent_error_has_bounded_retries(self) -> None: failure: Final = UnknownApiError(status_code=503, body="unavailable") outcomes: Final[Iterator[Result[FileDeleteResponse]]] = iter((failure,) * (len(CLEANUP_DELAYS) + 1)) - delays: Final[list[float]] = [] - result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays.append) + delays: Final = ExpectedCalls(iter(CLEANUP_DELAYS)) + result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays) assert result is failure - assert tuple(delays) == CLEANUP_DELAYS + delays.assert_done() assert next(outcomes, None) is None def test_permanent_error_is_not_retried(self) -> None: failure: Final = UnknownApiError(status_code=403, body="forbidden") outcomes: Final = iter((failure, deleted_file())) - delays: Final[list[float]] = [] - assert cleanup_result(lambda: next(outcomes), wait=delays.append) is failure - assert delays == [] + delays: Final = ExpectedCalls[float](iter(())) + assert cleanup_result(lambda: next(outcomes), wait=delays) is failure + delays.assert_done() assert isinstance(next(outcomes), Success) class TestBatchCancellation: def test_cancelling_batch_is_polled_until_terminal_without_cancelling_again(self) -> None: - client: Final = CleanupClient(batches=iter((batch("cancelling"), batch("cancelling"), batch("cancelled")))) - delays: Final[list[float]] = [] - cleanup_batch(client, MANAGED_BATCH_ID, key="test-key", wait=delays.append) - assert client.calls == [f"retrieve None {MANAGED_BATCH_ID}"] * 3 - assert delays == [10.0] + client: Final = CleanupClient( + calls=ExpectedCalls(iter((f"retrieve None {MANAGED_BATCH_ID}",) * 3)), + batches=iter((batch("cancelling"), batch("cancelling"), batch("cancelled"))), + ) + delays: Final = ExpectedCalls(iter((10.0,))) + cleanup_batch(client, MANAGED_BATCH_ID, key="test-key", wait=delays) + client.calls.assert_done() + delays.assert_done() def test_cancellation_timeout_is_reported_but_file_and_key_cleanup_still_run(self) -> None: client: Final = CleanupClient( - batches=iter((batch("cancelling"), batch("cancelling"))), files=iter((deleted_file(),)) + calls=ExpectedCalls( + iter( + ( + f"retrieve None {MANAGED_BATCH_ID}", + f"retrieve None {MANAGED_BATCH_ID}", + "delete None file-1", + "delete key test-key", + ) + ) + ), + batches=iter((batch("cancelling"), batch("cancelling"))), + files=iter((deleted_file(),)), ) ticks: Final = iter((0.0, BATCH_CANCEL_TIMEOUT_SECONDS)) manager: Final = ResourceManager(client=client, strict_cleanup=True) @@ -155,29 +200,29 @@ class TestBatchCancellation: with pytest.raises(ExceptionGroup) as caught: manager.teardown() assert "cancellation did not finish" in str(caught.value.exceptions[0]) - assert client.calls == [ - f"retrieve None {MANAGED_BATCH_ID}", - f"retrieve None {MANAGED_BATCH_ID}", - "delete None file-1", - "delete key test-key", - ] + client.calls.assert_done() @pytest.mark.parametrize("status", ["completed", "failed", "expired", "cancelled"]) def test_inactive_batch_needs_no_cancellation(self, status: str) -> None: - client: Final = CleanupClient(batches=iter((batch(status),))) + client: Final = CleanupClient( + calls=ExpectedCalls(iter(("retrieve None batch-1",))), batches=iter((batch(status),)) + ) cleanup_batch(client, "batch-1", key="test-key") - assert client.calls == ["retrieve None batch-1"] + client.calls.assert_done() def test_active_batch_is_cancelled_through_its_provider(self) -> None: client: Final = CleanupClient( - batches=iter((batch("in_progress"), batch("cancelled"))), cancellations=iter((batch("cancelling"),)) + calls=ExpectedCalls(iter(("retrieve azure batch-1", "cancel azure batch-1"))), + batches=iter((batch("in_progress"), batch("cancelled"))), + cancellations=iter((batch("cancelling"),)), ) cleanup_batch(client, "batch-1", key="test-key", provider="azure") - assert client.calls == ["retrieve azure batch-1", "cancel azure batch-1"] + client.calls.assert_done() @pytest.mark.parametrize("status", ["completed", "in_progress"]) def test_cancellation_conflict_is_accepted_only_when_batch_became_inactive(self, status: str) -> None: client: Final = CleanupClient( + calls=ExpectedCalls(iter(("retrieve None batch-1", "cancel None batch-1", "retrieve None batch-1"))), batches=iter((batch("in_progress"), batch(status))), cancellations=iter((UnknownApiError(status_code=409, body="conflict"),)), ) @@ -186,7 +231,7 @@ class TestBatchCancellation: else: with pytest.raises(AssertionError, match="Cancel batch batch-1 left status in_progress"): cleanup_batch(client, "batch-1", key="test-key") - assert client.calls == ["retrieve None batch-1", "cancel None batch-1", "retrieve None batch-1"] + client.calls.assert_done() class TestAzureFileExpiry: From c84131b81ab6feb552e23c5996559159e8810066 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 7 Sep 2026 16:00:20 -0700 Subject: [PATCH 008/105] feat(proxy): price cache and reasoning tokens in /cost/estimate POST /cost/estimate now accepts cache_read_input_tokens, cache_creation_input_tokens and reasoning_tokens, bills them at the model's cache and reasoning rates, and reports each share per request, per day and per month next to the rates it used. Custom-priced deployments also get cache and reasoning lines in the cost breakdown now, so the estimate and the spend logs reconcile with their totals instead of showing zero for those tokens. Requested by a customer (Pylon #7365). Claude-Session: https://claude.ai/code/session_011Tn3657NkV6ojLqewL64Kb --- litellm/cost_calculator.py | 1 + .../litellm_core_utils/llm_cost_calc/utils.py | 68 +++-- litellm/proxy/_types.py | 36 +++ .../cost_tracking_settings.py | 253 +++++++++++------- .../llm_cost_calc/test_llm_cost_calc_utils.py | 64 +++++ .../test_cost_tracking_settings.py | 187 ++++++++++++- tests/test_litellm/test_cost_calculator.py | 50 ++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 103 ++++++- 8 files changed, 641 insertions(+), 121 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9a9d2ceda03..a1181ee4124 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -1746,6 +1746,7 @@ def completion_cost( service_tier=service_tier, data_residency=data_residency, vertex_location=vertex_location, + custom_cost_per_token=custom_cost_per_token, ) _reasoning_cost = _token_type_breakdown.reasoning_cost _cache_read_cost = _token_type_breakdown.cache_read_cost diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 68dc27ec25e..8d53c8da3e6 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -19,6 +19,7 @@ from litellm.types.utils import ( CacheCreationTokenDetails, CallTypes, CompletionTokensDetailsWrapper, + CostPerToken, DataResidency, ImageResponse, ModelInfo, @@ -1307,6 +1308,40 @@ class TokenTypeCostBreakdown: cache_creation_cost: float +def _reasoning_token_count(usage: Usage) -> int: + parsed: Final = ( + parse_completion_tokens_details(usage)["reasoning_tokens"] if usage.completion_tokens_details is not None else 0 + ) + return parsed or _coerce_token_count(getattr(usage, "reasoning_tokens", 0)) + + +def _cache_token_counts(usage: Usage) -> tuple[int, int, CacheCreationTokenDetails | None]: + """(cache read tokens, cache creation tokens, cache creation details): read from prompt_tokens_details + first, then the private top-level counters the Usage constructor mirrors cache tokens onto for + providers/callers that bypass the details.""" + parsed: Final = parse_prompt_tokens_details(usage) if usage.prompt_tokens_details is not None else None + parsed_read: Final = parsed["cache_hit_tokens"] if parsed is not None else 0 + parsed_creation: Final = parsed["cache_creation_tokens"] if parsed is not None else 0 + return ( + parsed_read or _coerce_token_count(getattr(usage, "_cache_read_input_tokens", 0)), + parsed_creation or _coerce_token_count(getattr(usage, "_cache_creation_input_tokens", 0)), + parsed["cache_creation_token_details"] if parsed is not None else None, + ) + + +def _custom_pricing_token_type_breakdown(usage: Usage, custom_cost_per_token: CostPerToken) -> TokenTypeCostBreakdown: + """Flat custom pricing has no tiers, uplifts or reasoning rate: cache tokens bill at the configured + cache rates (else the input rate) and reasoning at the output rate, as _cost_per_token_custom_pricing_helper does.""" + input_rate: Final = custom_cost_per_token["input_cost_per_token"] + cache_read_tokens, cache_creation_tokens, _ = _cache_token_counts(usage) + return TokenTypeCostBreakdown( + reasoning_cost=float(_reasoning_token_count(usage)) * custom_cost_per_token["output_cost_per_token"], + cache_read_cost=float(cache_read_tokens) * custom_cost_per_token.get("cache_read_input_token_cost", input_rate), + cache_creation_cost=float(cache_creation_tokens) + * custom_cost_per_token.get("cache_creation_input_token_cost", input_rate), + ) + + def get_token_type_cost_breakdown( model: str, custom_llm_provider: str | None, @@ -1315,6 +1350,7 @@ def get_token_type_cost_breakdown( data_residency: str | None = None, vertex_location: str | None = None, current_time: datetime | None = None, + custom_cost_per_token: CostPerToken | None = None, ) -> TokenTypeCostBreakdown: """ Provider-agnostic cost of reasoning and cache tokens, derived from the usage @@ -1325,9 +1361,13 @@ def get_token_type_cost_breakdown( land on ``prompt_tokens_details`` (via the Usage constructor and provider transformations) and reasoning tokens on ``completion_tokens_details``. It reuses the same rate-resolution primitives as the total-cost path so the breakdown can - never drift from the totals. Returns zeros (never raises) when the model or its - pricing cannot be resolved. + never drift from the totals. A deployment billed by ``custom_cost_per_token`` is + priced from those flat rates instead of the cost map, for the same reason. + Returns zeros (never raises) when the model or its pricing cannot be resolved. """ + if custom_cost_per_token is not None: + return _custom_pricing_token_type_breakdown(usage=usage, custom_cost_per_token=custom_cost_per_token) + try: model_info: Final = get_model_info(model=model, custom_llm_provider=custom_llm_provider) except Exception: @@ -1348,12 +1388,6 @@ def get_token_type_cost_breakdown( threshold_is_inclusive=_uses_inclusive_token_thresholds(custom_llm_provider), ) - reasoning_tokens = ( - parse_completion_tokens_details(usage)["reasoning_tokens"] if usage.completion_tokens_details is not None else 0 - ) - if not reasoning_tokens: - reasoning_tokens = _coerce_token_count(getattr(usage, "reasoning_tokens", 0)) - reasoning_rate: Final = _resolve_billed_reasoning_rate( model_info=model_info, usage=usage, @@ -1361,23 +1395,9 @@ def get_token_type_cost_breakdown( completion_base_cost=completion_base_cost, current_time=billing_time, ) - reasoning_cost = float(reasoning_tokens) * reasoning_rate - - cache_read_tokens = 0 - cache_creation_tokens = 0 - cache_creation_token_details: CacheCreationTokenDetails | None = None - if usage.prompt_tokens_details is not None: - prompt_tokens_details: Final = parse_prompt_tokens_details(usage) - cache_read_tokens = prompt_tokens_details["cache_hit_tokens"] - cache_creation_tokens = prompt_tokens_details["cache_creation_tokens"] - cache_creation_token_details = prompt_tokens_details["cache_creation_token_details"] - # Fall back to the private top-level counters the Usage constructor mirrors cache - # tokens onto, so providers/callers that bypass prompt_tokens_details are covered. - if not cache_read_tokens: - cache_read_tokens = _coerce_token_count(getattr(usage, "_cache_read_input_tokens", 0)) - if not cache_creation_tokens: - cache_creation_tokens = _coerce_token_count(getattr(usage, "_cache_creation_input_tokens", 0)) + reasoning_cost = float(_reasoning_token_count(usage)) * reasoning_rate + cache_read_tokens, cache_creation_tokens, cache_creation_token_details = _cache_token_counts(usage) cache_read_cost = float(cache_read_tokens) * cache_read_cost_rate cache_creation_cost = calculate_cache_writing_cost( cache_creation_tokens=cache_creation_tokens, diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index abce10690e5..65b7d409d7c 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -5137,9 +5137,26 @@ class CostEstimateRequest(LiteLLMPydanticObjectBase): model: str = Field(description="Model name (from /model_group/info)") input_tokens: int = Field(description="Expected input tokens per request", ge=0) output_tokens: int = Field(description="Expected output tokens per request", ge=0) + cache_read_input_tokens: int = Field( + default=0, description="Input tokens read from the prompt cache; counted within input_tokens", ge=0 + ) + cache_creation_input_tokens: int = Field( + default=0, description="Input tokens written to the prompt cache; counted within input_tokens", ge=0 + ) + reasoning_tokens: int = Field( + default=0, description="Reasoning tokens the model emits; counted within output_tokens", ge=0 + ) num_requests_per_day: int | None = Field(default=None, description="Number of requests per day", ge=0) num_requests_per_month: int | None = Field(default=None, description="Number of requests per month", ge=0) + @model_validator(mode="after") + def validate_token_subsets(self) -> "CostEstimateRequest": + if self.cache_read_input_tokens + self.cache_creation_input_tokens > self.input_tokens: + raise ValueError("cache_read_input_tokens plus cache_creation_input_tokens cannot exceed input_tokens") + if self.reasoning_tokens > self.output_tokens: + raise ValueError("reasoning_tokens cannot exceed output_tokens") + return self + class CostEstimateResponse(LiteLLMPydanticObjectBase): """Response body for /cost/estimate endpoint.""" @@ -5147,6 +5164,9 @@ class CostEstimateResponse(LiteLLMPydanticObjectBase): model: str input_tokens: int output_tokens: int + cache_read_input_tokens: int = 0 + cache_creation_input_tokens: int = 0 + reasoning_tokens: int = 0 num_requests_per_day: int | None = None num_requests_per_month: int | None = None # Per-request costs @@ -5154,17 +5174,33 @@ class CostEstimateResponse(LiteLLMPydanticObjectBase): input_cost_per_request: float = Field(description="Input token cost per request (before margin)") output_cost_per_request: float = Field(description="Output token cost per request (before margin)") margin_cost_per_request: float = Field(default=0.0, description="Margin/fee added per request") + cache_read_cost_per_request: float = Field(default=0.0, description="Cache-read share of input_cost_per_request") + cache_creation_cost_per_request: float = Field( + default=0.0, description="Cache-write share of input_cost_per_request" + ) + reasoning_cost_per_request: float = Field(default=0.0, description="Reasoning share of output_cost_per_request") # Daily costs (if num_requests_per_day provided) daily_cost: float | None = Field(default=None, description="Total daily cost (includes margin)") daily_input_cost: float | None = Field(default=None, description="Daily input token cost") daily_output_cost: float | None = Field(default=None, description="Daily output token cost") daily_margin_cost: float | None = Field(default=None, description="Daily margin/fee") + daily_cache_read_cost: float | None = Field(default=None, description="Cache-read share of daily_input_cost") + daily_cache_creation_cost: float | None = Field(default=None, description="Cache-write share of daily_input_cost") + daily_reasoning_cost: float | None = Field(default=None, description="Reasoning share of daily_output_cost") # Monthly costs (if num_requests_per_month provided) monthly_cost: float | None = Field(default=None, description="Total monthly cost (includes margin)") monthly_input_cost: float | None = Field(default=None, description="Monthly input token cost") monthly_output_cost: float | None = Field(default=None, description="Monthly output token cost") monthly_margin_cost: float | None = Field(default=None, description="Monthly margin/fee") + monthly_cache_read_cost: float | None = Field(default=None, description="Cache-read share of monthly_input_cost") + monthly_cache_creation_cost: float | None = Field( + default=None, description="Cache-write share of monthly_input_cost" + ) + monthly_reasoning_cost: float | None = Field(default=None, description="Reasoning share of monthly_output_cost") # Pricing info input_cost_per_token: float | None = None output_cost_per_token: float | None = None + cache_read_input_token_cost: float | None = Field(default=None, description="Rate billed per cache-read token") + cache_creation_input_token_cost: float | None = Field(default=None, description="Rate billed per cache-write token") + output_cost_per_reasoning_token: float | None = Field(default=None, description="Rate billed per reasoning token") provider: str | None = None diff --git a/litellm/proxy/management_endpoints/cost_tracking_settings.py b/litellm/proxy/management_endpoints/cost_tracking_settings.py index 204051c3715..51f31a757cd 100644 --- a/litellm/proxy/management_endpoints/cost_tracking_settings.py +++ b/litellm/proxy/management_endpoints/cost_tracking_settings.py @@ -27,7 +27,15 @@ from litellm.proxy._types import ( UserAPIKeyAuth, ) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth -from litellm.types.utils import CostPerToken, LlmProvidersSet, ModelInfo +from litellm.types.utils import ( + CostBreakdown, + CostPerToken, + LlmProvidersSet, + ModelInfo, + ModelResponse, + PromptTokensDetailsWrapper, + Usage, +) router: Final = APIRouter() @@ -46,13 +54,15 @@ def _configured_price(key: str, sources: tuple[Mapping[str, object], ...]) -> fl def _extract_custom_pricing( - litellm_params: Mapping[str, object], model_info: Mapping[str, object] + litellm_params: Mapping[str, object], model_info: Mapping[str, object], builtin: ModelInfo | None ) -> CostPerToken | None: """ Pull per-token pricing configured on a deployment so on-prem / self-hosted models (absent from the public cost map) still estimate a real cost. Pricing may live on ``litellm_params`` or ``model_info``; ``litellm_params`` - wins, matching the router's cost-map registration precedence. + wins, matching the router's cost-map registration precedence. Cache rates the + deployment leaves unset come from the backend model's built-in entry, then its + own input rate, again matching what the router registers for live billing. """ sources: Final = (litellm_params, model_info) input_price: Final = _configured_price("input_cost_per_token", sources) @@ -61,9 +71,15 @@ def _extract_custom_pricing( if input_price is None and output_price is None: return None + input_rate: Final = input_price or 0.0 + cache_sources: Final = sources if builtin is None else (*sources, builtin) + cache_read_price: Final = _configured_price("cache_read_input_token_cost", cache_sources) + cache_creation_price: Final = _configured_price("cache_creation_input_token_cost", cache_sources) return CostPerToken( - input_cost_per_token=input_price or 0.0, + input_cost_per_token=input_rate, output_cost_per_token=output_price or 0.0, + cache_read_input_token_cost=input_rate if cache_read_price is None else cache_read_price, + cache_creation_input_token_cost=input_rate if cache_creation_price is None else cache_creation_price, ) @@ -98,17 +114,14 @@ def _resolve_model_for_cost_lookup(model: str) -> ResolvedCostModel: model_info: Final = first_deployment.get("model_info", {}) custom_llm_provider: Final = litellm_params.get("custom_llm_provider") provider: Final = str(custom_llm_provider) if custom_llm_provider is not None else None - custom_cost_per_token: Final = _extract_custom_pricing(litellm_params, model_info) - - # Check base_model first (needed for Azure custom deployment names) + # base_model wins (needed for Azure custom deployment names) base_model: Final = model_info.get("base_model") or litellm_params.get("base_model") - if base_model: - verbose_proxy_logger.debug("Resolved model '%s' to base_model '%s' from router", model, base_model) - return ResolvedCostModel(str(base_model), provider, custom_cost_per_token) - - resolved_model: Final = litellm_params.get("model") + resolved_model: Final = base_model or litellm_params.get("model") if resolved_model: verbose_proxy_logger.debug("Resolved model '%s' to '%s' from router", model, resolved_model) + custom_cost_per_token: Final = _extract_custom_pricing( + litellm_params, model_info, _lookup_model_info(str(resolved_model)) + ) return ResolvedCostModel(str(resolved_model), provider, custom_cost_per_token) except Exception as e: verbose_proxy_logger.debug("Could not resolve model '%s' from router: %s", model, e) @@ -117,19 +130,97 @@ def _resolve_model_for_cost_lookup(model: str) -> ResolvedCostModel: return ResolvedCostModel(model, None, None) -def _calculate_period_costs(num_requests, cost_per_request, input_cost, output_cost, margin_cost): - """ - Calculate costs for a given number of requests. +@dataclass(frozen=True, slots=True) +class CostLines: + """Cost of one request split the way the spend logs split it: the cache lines are + shares of input_cost and the reasoning line is a share of output_cost.""" - Returns tuple of (total_cost, input_cost, output_cost, margin_cost) or all None if num_requests is None/0. - """ - if not num_requests: - return None, None, None, None - return ( - cost_per_request * num_requests, - input_cost * num_requests, - output_cost * num_requests, - margin_cost * num_requests, + total_cost: float + input_cost: float + output_cost: float + margin_cost: float + cache_read_cost: float + cache_creation_cost: float + reasoning_cost: float + + def times(self, num_requests: int | None) -> "CostLines | None": + if not num_requests: + return None + return CostLines( + total_cost=self.total_cost * num_requests, + input_cost=self.input_cost * num_requests, + output_cost=self.output_cost * num_requests, + margin_cost=self.margin_cost * num_requests, + cache_read_cost=self.cache_read_cost * num_requests, + cache_creation_cost=self.cache_creation_cost * num_requests, + reasoning_cost=self.reasoning_cost * num_requests, + ) + + +def _cost_lines(cost_per_request: float, cost_breakdown: CostBreakdown | None) -> CostLines: + breakdown: Final = cost_breakdown if cost_breakdown is not None else CostBreakdown() + return CostLines( + total_cost=cost_per_request, + input_cost=breakdown.get("input_cost", 0.0), + output_cost=breakdown.get("output_cost", 0.0), + margin_cost=breakdown.get("margin_total_amount", 0.0), + cache_read_cost=breakdown.get("cache_read_cost", 0.0), + cache_creation_cost=breakdown.get("cache_creation_cost", 0.0), + reasoning_cost=breakdown.get("reasoning_cost", 0.0), + ) + + +@dataclass(frozen=True, slots=True) +class EffectiveTokenRates: + input_cost_per_token: float | None + output_cost_per_token: float | None + cache_read_input_token_cost: float | None + cache_creation_input_token_cost: float | None + output_cost_per_reasoning_token: float | None + + +def _custom_token_rates(custom_cost_per_token: CostPerToken) -> EffectiveTokenRates: + input_rate: Final = custom_cost_per_token["input_cost_per_token"] + output_rate: Final = custom_cost_per_token["output_cost_per_token"] + return EffectiveTokenRates( + input_cost_per_token=input_rate, + output_cost_per_token=output_rate, + cache_read_input_token_cost=custom_cost_per_token.get("cache_read_input_token_cost", input_rate), + cache_creation_input_token_cost=custom_cost_per_token.get("cache_creation_input_token_cost", input_rate), + output_cost_per_reasoning_token=output_rate, + ) + + +def _cost_map_token_rates(model_info: ModelInfo | None) -> EffectiveTokenRates: + """Base rates the cost calculator bills flat usage at: a cost-map model without a cache price + bills cache tokens at zero, and one without a reasoning price bills reasoning at the output rate.""" + if model_info is None: + return EffectiveTokenRates(None, None, None, None, None) + sources: Final = (model_info,) + output_rate: Final = _configured_price("output_cost_per_token", sources) + reasoning_rate: Final = _configured_price("output_cost_per_reasoning_token", sources) + return EffectiveTokenRates( + input_cost_per_token=_configured_price("input_cost_per_token", sources), + output_cost_per_token=output_rate, + cache_read_input_token_cost=_configured_price("cache_read_input_token_cost", sources) or 0.0, + cache_creation_input_token_cost=_configured_price("cache_creation_input_token_cost", sources) or 0.0, + output_cost_per_reasoning_token=output_rate if reasoning_rate is None else reasoning_rate, + ) + + +def _usage_for_estimate(request: CostEstimateRequest) -> Usage: + cache_tokens: Final = request.cache_read_input_tokens + request.cache_creation_input_tokens + return Usage( + prompt_tokens=request.input_tokens, + completion_tokens=request.output_tokens, + total_tokens=request.input_tokens + request.output_tokens, + reasoning_tokens=request.reasoning_tokens, + prompt_tokens_details=PromptTokensDetailsWrapper( + cached_tokens=request.cache_read_input_tokens, + cache_creation_tokens=request.cache_creation_input_tokens, + ) + if cache_tokens + else None, ) @@ -530,11 +621,14 @@ async def estimate_cost( - model: Model name (e.g., "gpt-4", "claude-3-opus") - input_tokens: Expected input tokens per request - output_tokens: Expected output tokens per request + - cache_read_input_tokens: Cache-read tokens per request, counted within input_tokens (optional) + - cache_creation_input_tokens: Cache-write tokens per request, counted within input_tokens (optional) + - reasoning_tokens: Reasoning tokens per request, counted within output_tokens (optional) - num_requests_per_day: Number of requests per day (optional) - num_requests_per_month: Number of requests per month (optional) Returns cost breakdown including: - - Per-request costs (input, output, margin) + - Per-request costs (input, output, margin, plus the cache-read, cache-write and reasoning shares) - Daily costs (if num_requests_per_day provided) - Monthly costs (if num_requests_per_month provided) @@ -543,14 +637,15 @@ async def estimate_cost( { "model": "gpt-4", "input_tokens": 1000, + "cache_read_input_tokens": 800, "output_tokens": 500, + "reasoning_tokens": 200, "num_requests_per_day": 100, "num_requests_per_month": 3000 } ``` """ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj - from litellm.types.utils import ModelResponse, Usage # Resolve model name (handles router aliases like 'e-model-router' -> 'azure_ai/gpt-4') resolved: Final = _resolve_model_for_cost_lookup(request.model) @@ -559,15 +654,7 @@ async def estimate_cost( verbose_proxy_logger.debug("Cost estimate: request.model='%s' resolved to '%s'", request.model, resolved_model) - # Create a mock response with usage for completion_cost - mock_response: Final = ModelResponse( - model=resolved_model, - usage=Usage( - prompt_tokens=request.input_tokens, - completion_tokens=request.output_tokens, - total_tokens=request.input_tokens + request.output_tokens, - ), - ) + mock_response: Final = ModelResponse(model=resolved_model, usage=_usage_for_estimate(request)) # Create a logging object to capture cost breakdown litellm_logging_obj: Final = LiteLLMLoggingObj( @@ -597,75 +684,53 @@ async def estimate_cost( }, ) - # Get cost breakdown from the logging object - cost_breakdown: Final = litellm_logging_obj.cost_breakdown - - input_cost: Final = cost_breakdown.get("input_cost", 0.0) if cost_breakdown else 0.0 - output_cost: Final = cost_breakdown.get("output_cost", 0.0) if cost_breakdown else 0.0 - margin_cost: Final = cost_breakdown.get("margin_total_amount", 0.0) if cost_breakdown else 0.0 + per_request: Final = _cost_lines(cost_per_request, litellm_logging_obj.cost_breakdown) + daily: Final = per_request.times(request.num_requests_per_day) + monthly: Final = per_request.times(request.num_requests_per_month) model_info: Final = _lookup_model_info(resolved_model) - mapped_input_price: Final = model_info.get("input_cost_per_token") if model_info is not None else None - mapped_output_price: Final = model_info.get("output_cost_per_token") if model_info is not None else None + rates: Final = ( + _custom_token_rates(resolved.custom_cost_per_token) + if resolved.custom_cost_per_token is not None + else _cost_map_token_rates(model_info) + ) mapped_provider: Final = model_info.get("litellm_provider") if model_info is not None else None - - input_cost_per_token: Final = ( - resolved.custom_cost_per_token["input_cost_per_token"] - if resolved.custom_cost_per_token is not None - else mapped_input_price - ) - output_cost_per_token: Final = ( - resolved.custom_cost_per_token["output_cost_per_token"] - if resolved.custom_cost_per_token is not None - else mapped_output_price - ) custom_llm_provider: Final = mapped_provider if mapped_provider is not None else resolved_provider - # Calculate daily and monthly costs - ( - daily_cost, - daily_input_cost, - daily_output_cost, - daily_margin_cost, - ) = _calculate_period_costs( - num_requests=request.num_requests_per_day, - cost_per_request=cost_per_request, - input_cost=input_cost, - output_cost=output_cost, - margin_cost=margin_cost, - ) - ( - monthly_cost, - monthly_input_cost, - monthly_output_cost, - monthly_margin_cost, - ) = _calculate_period_costs( - num_requests=request.num_requests_per_month, - cost_per_request=cost_per_request, - input_cost=input_cost, - output_cost=output_cost, - margin_cost=margin_cost, - ) - return CostEstimateResponse( model=request.model, input_tokens=request.input_tokens, output_tokens=request.output_tokens, + cache_read_input_tokens=request.cache_read_input_tokens, + cache_creation_input_tokens=request.cache_creation_input_tokens, + reasoning_tokens=request.reasoning_tokens, num_requests_per_day=request.num_requests_per_day, num_requests_per_month=request.num_requests_per_month, - cost_per_request=cost_per_request, - input_cost_per_request=input_cost, - output_cost_per_request=output_cost, - margin_cost_per_request=margin_cost, - daily_cost=daily_cost, - daily_input_cost=daily_input_cost, - daily_output_cost=daily_output_cost, - daily_margin_cost=daily_margin_cost, - monthly_cost=monthly_cost, - monthly_input_cost=monthly_input_cost, - monthly_output_cost=monthly_output_cost, - monthly_margin_cost=monthly_margin_cost, - input_cost_per_token=input_cost_per_token, - output_cost_per_token=output_cost_per_token, + cost_per_request=per_request.total_cost, + input_cost_per_request=per_request.input_cost, + output_cost_per_request=per_request.output_cost, + margin_cost_per_request=per_request.margin_cost, + cache_read_cost_per_request=per_request.cache_read_cost, + cache_creation_cost_per_request=per_request.cache_creation_cost, + reasoning_cost_per_request=per_request.reasoning_cost, + daily_cost=daily.total_cost if daily is not None else None, + daily_input_cost=daily.input_cost if daily is not None else None, + daily_output_cost=daily.output_cost if daily is not None else None, + daily_margin_cost=daily.margin_cost if daily is not None else None, + daily_cache_read_cost=daily.cache_read_cost if daily is not None else None, + daily_cache_creation_cost=daily.cache_creation_cost if daily is not None else None, + daily_reasoning_cost=daily.reasoning_cost if daily is not None else None, + monthly_cost=monthly.total_cost if monthly is not None else None, + monthly_input_cost=monthly.input_cost if monthly is not None else None, + monthly_output_cost=monthly.output_cost if monthly is not None else None, + monthly_margin_cost=monthly.margin_cost if monthly is not None else None, + monthly_cache_read_cost=monthly.cache_read_cost if monthly is not None else None, + monthly_cache_creation_cost=monthly.cache_creation_cost if monthly is not None else None, + monthly_reasoning_cost=monthly.reasoning_cost if monthly is not None else None, + input_cost_per_token=rates.input_cost_per_token, + output_cost_per_token=rates.output_cost_per_token, + cache_read_input_token_cost=rates.cache_read_input_token_cost, + cache_creation_input_token_cost=rates.cache_creation_input_token_cost, + output_cost_per_reasoning_token=rates.output_cost_per_reasoning_token, provider=custom_llm_provider, ) diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 59f0938e338..7065003839b 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -3874,6 +3874,70 @@ def test_token_type_cost_breakdown_reconciles_with_generic_total(_local_model_co assert text_input_cost + breakdown.cache_read_cost == pytest.approx(prompt_cost) +def _custom_priced_usage() -> Usage: + return Usage( + prompt_tokens=1000, + completion_tokens=500, + total_tokens=1500, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=800, cache_creation_tokens=100), + completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=200), + ) + + +def test_token_type_cost_breakdown_prices_custom_pricing_from_its_flat_rates(): + """ + A custom-priced deployment, usually absent from the cost map, used to get zero cache and + reasoning lines while its total already billed cache tokens at the custom cache rates. + The lines must come from the same flat rates: a configured cache rate, else the input + rate for cache tokens and the output rate for reasoning tokens. + """ + from litellm.types.utils import CostPerToken + + breakdown = get_token_type_cost_breakdown( + model="openai/onprem-model", + custom_llm_provider="openai", + usage=_custom_priced_usage(), + custom_cost_per_token=CostPerToken( + input_cost_per_token=1e-6, output_cost_per_token=2e-6, cache_read_input_token_cost=1e-7 + ), + ) + + assert breakdown.cache_read_cost == pytest.approx(800 * 1e-7) + assert breakdown.cache_creation_cost == pytest.approx(100 * 1e-6) + assert breakdown.reasoning_cost == pytest.approx(200 * 2e-6) + + +def test_token_type_cost_breakdown_reconciles_with_custom_pricing_totals(): + from litellm.cost_calculator import cost_per_token + from litellm.types.utils import CostPerToken + + usage = _custom_priced_usage() + custom_cost_per_token = CostPerToken( + input_cost_per_token=1e-6, + output_cost_per_token=2e-6, + cache_read_input_token_cost=1e-7, + cache_creation_input_token_cost=1.25e-6, + ) + + prompt_cost, completion_cost = cost_per_token( + model="openai/onprem-model", + custom_llm_provider="openai", + prompt_tokens=1000, + completion_tokens=500, + usage_object=usage, + custom_cost_per_token=custom_cost_per_token, + ) + breakdown = get_token_type_cost_breakdown( + model="openai/onprem-model", + custom_llm_provider="openai", + usage=usage, + custom_cost_per_token=custom_cost_per_token, + ) + + assert 100 * 1e-6 + breakdown.cache_read_cost + breakdown.cache_creation_cost == pytest.approx(prompt_cost) + assert 300 * 2e-6 + breakdown.reasoning_cost == pytest.approx(completion_cost) + + def test_token_type_cost_breakdown_zero_without_special_tokens(_local_model_cost_map): usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150) diff --git a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py index ec62cc47018..6d3ef2ffea9 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py @@ -8,9 +8,11 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi.testclient import TestClient +from pydantic import ValidationError import litellm +from litellm.proxy._types import CostEstimateRequest from litellm.proxy.management_endpoints.cost_tracking_settings import router from litellm.proxy.proxy_server import app @@ -789,13 +791,13 @@ INPUT_TOKENS = 1000 OUTPUT_TOKENS = 500 -def _router_pricing(**pricing: float) -> MagicMock: +def _router_pricing(model: str = AN_UNDERLYING_MODEL, **pricing: float) -> MagicMock: mock_router = MagicMock() mock_router.get_model_list.return_value = [ { "model_name": AN_ALIAS, "litellm_params": { - "model": AN_UNDERLYING_MODEL, + "model": model, "custom_llm_provider": "openai", **pricing, }, @@ -909,3 +911,184 @@ class TestEstimateCostPeriodTotals: assert response.cost_per_request == pytest.approx(0.0022) assert response.daily_margin_cost == pytest.approx(0.02) assert response.daily_cost == pytest.approx(0.22) + + +CACHE_READ_TOKENS = 800 +CACHE_CREATION_TOKENS = 100 +REASONING_TOKENS = 200 +TEXT_INPUT_TOKENS = INPUT_TOKENS - CACHE_READ_TOKENS - CACHE_CREATION_TOKENS +TEXT_OUTPUT_TOKENS = OUTPUT_TOKENS - REASONING_TOKENS + + +async def _estimate_with_cache_and_reasoning(mock_router: MagicMock | None, model: str = AN_ALIAS, **overrides: int): + return await _estimate( + mock_router, + model=model, + cache_read_input_tokens=CACHE_READ_TOKENS, + cache_creation_input_tokens=CACHE_CREATION_TOKENS, + reasoning_tokens=REASONING_TOKENS, + **overrides, + ) + + +class TestEstimateCostCacheAndReasoningTokens: + @pytest.mark.asyncio + async def test_a_mapped_model_bills_cache_and_reasoning_tokens_at_their_own_rates(self, monkeypatch): + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + { + "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, + "output_cost_per_reasoning_token": 1e-5, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + + response = await _estimate_with_cache_and_reasoning(None, model=A_MAPPED_MODEL, num_requests_per_day=10) + + assert response.cache_read_cost_per_request == pytest.approx(CACHE_READ_TOKENS * 3e-7) + assert response.cache_creation_cost_per_request == pytest.approx(CACHE_CREATION_TOKENS * 3.75e-6) + assert response.reasoning_cost_per_request == pytest.approx(REASONING_TOKENS * 1e-5) + assert response.input_cost_per_request == pytest.approx( + TEXT_INPUT_TOKENS * 3e-6 + CACHE_READ_TOKENS * 3e-7 + CACHE_CREATION_TOKENS * 3.75e-6 + ) + assert response.output_cost_per_request == pytest.approx(TEXT_OUTPUT_TOKENS * 15e-6 + REASONING_TOKENS * 1e-5) + assert response.cost_per_request == pytest.approx( + response.input_cost_per_request + response.output_cost_per_request + ) + assert response.daily_cache_read_cost == pytest.approx(10 * CACHE_READ_TOKENS * 3e-7) + assert response.daily_cache_creation_cost == pytest.approx(10 * CACHE_CREATION_TOKENS * 3.75e-6) + assert response.daily_reasoning_cost == pytest.approx(10 * REASONING_TOKENS * 1e-5) + assert response.monthly_cache_read_cost is None + assert response.cache_read_input_token_cost == pytest.approx(3e-7) + assert response.cache_creation_input_token_cost == pytest.approx(3.75e-6) + assert response.output_cost_per_reasoning_token == pytest.approx(1e-5) + assert ( + response.cache_read_input_tokens, + response.cache_creation_input_tokens, + response.reasoning_tokens, + ) == (CACHE_READ_TOKENS, CACHE_CREATION_TOKENS, REASONING_TOKENS) + + @pytest.mark.asyncio + async def test_a_model_without_cache_or_reasoning_prices_estimates_what_the_proxy_bills(self, monkeypatch): + """The cost calculator bills cache tokens of a cost-map model without cache prices at zero + and its reasoning tokens at the output rate. The estimate reports those effective rates.""" + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + {"input_cost_per_token": 5e-6, "output_cost_per_token": 6e-6, "litellm_provider": "openai", "mode": "chat"}, + ) + + response = await _estimate_with_cache_and_reasoning(None, model=A_MAPPED_MODEL) + + assert response.cache_read_cost_per_request == 0.0 + assert response.cache_creation_cost_per_request == 0.0 + assert response.reasoning_cost_per_request == pytest.approx(REASONING_TOKENS * 6e-6) + assert response.input_cost_per_request == pytest.approx(TEXT_INPUT_TOKENS * 5e-6) + assert response.cost_per_request == pytest.approx(TEXT_INPUT_TOKENS * 5e-6 + OUTPUT_TOKENS * 6e-6) + assert response.cache_read_input_token_cost == 0.0 + assert response.cache_creation_input_token_cost == 0.0 + assert response.output_cost_per_reasoning_token == pytest.approx(6e-6) + + @pytest.mark.asyncio + async def test_a_request_without_cache_or_reasoning_tokens_estimates_as_before(self, monkeypatch): + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + { + "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, + "output_cost_per_reasoning_token": 1e-5, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + + response = await _estimate(None, model=A_MAPPED_MODEL, num_requests_per_day=10) + + assert response.cost_per_request == pytest.approx(INPUT_TOKENS * 3e-6 + OUTPUT_TOKENS * 15e-6) + assert response.cache_read_cost_per_request == 0.0 + assert response.cache_creation_cost_per_request == 0.0 + assert response.reasoning_cost_per_request == 0.0 + assert response.daily_cache_read_cost == 0.0 + assert response.daily_reasoning_cost == 0.0 + + @pytest.mark.asyncio + async def test_a_custom_priced_deployment_bills_cache_and_reasoning_tokens_from_its_flat_rates(self): + response = await _estimate_with_cache_and_reasoning( + _router_pricing(input_cost_per_token=1e-6, output_cost_per_token=2e-6, cache_read_input_token_cost=1e-7) + ) + + assert response.cache_read_cost_per_request == pytest.approx(CACHE_READ_TOKENS * 1e-7) + assert response.cache_creation_cost_per_request == pytest.approx(CACHE_CREATION_TOKENS * 1e-6) + assert response.reasoning_cost_per_request == pytest.approx(REASONING_TOKENS * 2e-6) + assert response.cost_per_request == pytest.approx( + TEXT_INPUT_TOKENS * 1e-6 + CACHE_READ_TOKENS * 1e-7 + CACHE_CREATION_TOKENS * 1e-6 + OUTPUT_TOKENS * 2e-6 + ) + assert response.cache_read_input_token_cost == pytest.approx(1e-7) + assert response.cache_creation_input_token_cost == pytest.approx(1e-6) + assert response.output_cost_per_reasoning_token == pytest.approx(2e-6) + + @pytest.mark.asyncio + async def test_a_custom_priced_deployment_of_a_mapped_model_inherits_its_built_in_cache_rates(self, monkeypatch): + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + { + "input_cost_per_token": 5e-6, + "output_cost_per_token": 6e-6, + "cache_read_input_token_cost": 5e-7, + "cache_creation_input_token_cost": 6.25e-6, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + + response = await _estimate_with_cache_and_reasoning( + _router_pricing(model=A_MAPPED_MODEL, input_cost_per_token=1e-6, output_cost_per_token=2e-6) + ) + + assert response.cache_read_cost_per_request == pytest.approx(CACHE_READ_TOKENS * 5e-7) + assert response.cache_creation_cost_per_request == pytest.approx(CACHE_CREATION_TOKENS * 6.25e-6) + assert response.input_cost_per_request == pytest.approx( + TEXT_INPUT_TOKENS * 1e-6 + CACHE_READ_TOKENS * 5e-7 + CACHE_CREATION_TOKENS * 6.25e-6 + ) + assert response.cache_read_input_token_cost == pytest.approx(5e-7) + assert response.cache_creation_input_token_cost == pytest.approx(6.25e-6) + + +class TestCostEstimateRequestTokenSubsets: + def test_cache_tokens_beyond_the_input_tokens_are_rejected(self): + with pytest.raises(ValidationError, match="cannot exceed input_tokens"): + CostEstimateRequest( + model=AN_ALIAS, + input_tokens=INPUT_TOKENS, + output_tokens=OUTPUT_TOKENS, + cache_read_input_tokens=INPUT_TOKENS, + cache_creation_input_tokens=1, + ) + + def test_reasoning_tokens_beyond_the_output_tokens_are_rejected(self): + with pytest.raises(ValidationError, match="cannot exceed output_tokens"): + CostEstimateRequest( + model=AN_ALIAS, + input_tokens=INPUT_TOKENS, + output_tokens=OUTPUT_TOKENS, + reasoning_tokens=OUTPUT_TOKENS + 1, + ) + + def test_the_endpoint_answers_422_when_cache_tokens_exceed_input_tokens(self): + response = client.post( + "/cost/estimate", + headers={"Authorization": "Bearer sk-1234"}, + json={"model": AN_ALIAS, "input_tokens": 1000, "output_tokens": 100, "cache_read_input_tokens": 8000}, + ) + + assert response.status_code == 422 + assert "cannot exceed input_tokens" in response.text diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index f8fa2231597..910587d3dc5 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3736,6 +3736,56 @@ def test_completion_cost_logs_reasoning_and_cache_breakdown(_local_model_cost_ma assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx(100 * 3e-08) +def test_completion_cost_logs_cache_and_reasoning_breakdown_for_custom_pricing(): + """ + A custom-priced deployment bills cache tokens at its custom cache rates, but the + breakdown stored for the spend logs carried no cache or reasoning lines for it. + """ + from datetime import datetime + + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import CompletionTokensDetailsWrapper, CostPerToken + + logging_obj = Logging( + model="openai/onprem-model", + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.now(), + litellm_call_id="custom-pricing-breakdown", + function_id="f", + ) + response = ModelResponse( + model="openai/onprem-model", + usage=Usage( + prompt_tokens=1000, + completion_tokens=500, + total_tokens=1500, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=800, cache_creation_tokens=100), + completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=200), + ), + ) + + total = completion_cost( + completion_response=response, + model="openai/onprem-model", + custom_llm_provider="openai", + custom_cost_per_token=CostPerToken( + input_cost_per_token=1e-6, + output_cost_per_token=2e-6, + cache_read_input_token_cost=1e-7, + cache_creation_input_token_cost=1.25e-6, + ), + litellm_logging_obj=logging_obj, + ) + + assert logging_obj.cost_breakdown is not None + assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx(800 * 1e-7) + assert logging_obj.cost_breakdown["cache_creation_cost"] == pytest.approx(100 * 1.25e-6) + assert logging_obj.cost_breakdown["reasoning_cost"] == pytest.approx(200 * 2e-6) + assert total == pytest.approx(100 * 1e-6 + 800 * 1e-7 + 100 * 1.25e-6 + 500 * 2e-6) + + def test_cost_per_token_per_second_pricing(monkeypatch): """ Models priced by duration (input/output_cost_per_second) with no per-token rates diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6fb08445aff..6a9c86cc1b5 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -3301,11 +3301,14 @@ export interface paths { * - model: Model name (e.g., "gpt-4", "claude-3-opus") * - input_tokens: Expected input tokens per request * - output_tokens: Expected output tokens per request + * - cache_read_input_tokens: Cache-read tokens per request, counted within input_tokens (optional) + * - cache_creation_input_tokens: Cache-write tokens per request, counted within input_tokens (optional) + * - reasoning_tokens: Reasoning tokens per request, counted within output_tokens (optional) * - num_requests_per_day: Number of requests per day (optional) * - num_requests_per_month: Number of requests per month (optional) * * Returns cost breakdown including: - * - Per-request costs (input, output, margin) + * - Per-request costs (input, output, margin, plus the cache-read, cache-write and reasoning shares) * - Daily costs (if num_requests_per_day provided) * - Monthly costs (if num_requests_per_month provided) * @@ -3314,7 +3317,9 @@ export interface paths { * { * "model": "gpt-4", * "input_tokens": 1000, + * "cache_read_input_tokens": 800, * "output_tokens": 500, + * "reasoning_tokens": 200, * "num_requests_per_day": 100, * "num_requests_per_month": 3000 * } @@ -26392,6 +26397,18 @@ export interface components { * @description Request body for /cost/estimate endpoint. */ CostEstimateRequest: { + /** + * Cache Creation Input Tokens + * @description Input tokens written to the prompt cache; counted within input_tokens + * @default 0 + */ + cache_creation_input_tokens: number; + /** + * Cache Read Input Tokens + * @description Input tokens read from the prompt cache; counted within input_tokens + * @default 0 + */ + cache_read_input_tokens: number; /** * Input Tokens * @description Expected input tokens per request @@ -26417,17 +26434,65 @@ export interface components { * @description Expected output tokens per request */ output_tokens: number; + /** + * Reasoning Tokens + * @description Reasoning tokens the model emits; counted within output_tokens + * @default 0 + */ + reasoning_tokens: number; }; /** * CostEstimateResponse * @description Response body for /cost/estimate endpoint. */ CostEstimateResponse: { + /** + * Cache Creation Cost Per Request + * @description Cache-write share of input_cost_per_request + * @default 0 + */ + cache_creation_cost_per_request: number; + /** + * Cache Creation Input Token Cost + * @description Rate billed per cache-write token + */ + cache_creation_input_token_cost?: number | null; + /** + * Cache Creation Input Tokens + * @default 0 + */ + cache_creation_input_tokens: number; + /** + * Cache Read Cost Per Request + * @description Cache-read share of input_cost_per_request + * @default 0 + */ + cache_read_cost_per_request: number; + /** + * Cache Read Input Token Cost + * @description Rate billed per cache-read token + */ + cache_read_input_token_cost?: number | null; + /** + * Cache Read Input Tokens + * @default 0 + */ + cache_read_input_tokens: number; /** * Cost Per Request * @description Total cost per request (includes margin) */ cost_per_request: number; + /** + * Daily Cache Creation Cost + * @description Cache-write share of daily_input_cost + */ + daily_cache_creation_cost?: number | null; + /** + * Daily Cache Read Cost + * @description Cache-read share of daily_input_cost + */ + daily_cache_read_cost?: number | null; /** * Daily Cost * @description Total daily cost (includes margin) @@ -26448,6 +26513,11 @@ export interface components { * @description Daily output token cost */ daily_output_cost?: number | null; + /** + * Daily Reasoning Cost + * @description Reasoning share of daily_output_cost + */ + daily_reasoning_cost?: number | null; /** * Input Cost Per Request * @description Input token cost per request (before margin) @@ -26465,6 +26535,16 @@ export interface components { margin_cost_per_request: number; /** Model */ model: string; + /** + * Monthly Cache Creation Cost + * @description Cache-write share of monthly_input_cost + */ + monthly_cache_creation_cost?: number | null; + /** + * Monthly Cache Read Cost + * @description Cache-read share of monthly_input_cost + */ + monthly_cache_read_cost?: number | null; /** * Monthly Cost * @description Total monthly cost (includes margin) @@ -26485,10 +26565,20 @@ export interface components { * @description Monthly output token cost */ monthly_output_cost?: number | null; + /** + * Monthly Reasoning Cost + * @description Reasoning share of monthly_output_cost + */ + monthly_reasoning_cost?: number | null; /** Num Requests Per Day */ num_requests_per_day?: number | null; /** Num Requests Per Month */ num_requests_per_month?: number | null; + /** + * Output Cost Per Reasoning Token + * @description Rate billed per reasoning token + */ + output_cost_per_reasoning_token?: number | null; /** * Output Cost Per Request * @description Output token cost per request (before margin) @@ -26500,6 +26590,17 @@ export interface components { output_tokens: number; /** Provider */ provider?: string | null; + /** + * Reasoning Cost Per Request + * @description Reasoning share of output_cost_per_request + * @default 0 + */ + reasoning_cost_per_request: number; + /** + * Reasoning Tokens + * @default 0 + */ + reasoning_tokens: number; }; /** CreateCredentialItem */ CreateCredentialItem: { From 7bff9bf9a2594d3cd5dd6a80a44e4e3b1cd38f61 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 16:22:32 -0700 Subject: [PATCH 009/105] fix(batches): handle provider cancellation and file cleanup gaps --- litellm/llms/bedrock/files/transformation.py | 70 ++++++++------ tests/e2e/batches/COVERAGE.md | 7 +- tests/e2e/batches/batch_cleanup.py | 63 +++++++++--- tests/e2e/batches/test_batch_cleanup.py | 69 +++++++++++++- tests/e2e/batches/test_batches_e2e.py | 14 +-- .../test_bedrock_files_transformation.py | 95 ++++++++++++++++--- 6 files changed, 258 insertions(+), 60 deletions(-) diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index 33b27943ad8..90b539ff37c 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -7,7 +7,7 @@ from contextlib import suppress from functools import cache from itertools import chain from types import MappingProxyType -from typing import Any, Final, TypeAlias, TypedDict +from typing import Any, Final, Literal, TypeAlias, TypedDict from urllib.parse import unquote import httpx @@ -60,11 +60,8 @@ from litellm.utils import get_llm_provider from ..base_aws_llm import BaseAWSLLM from ..common_utils import BedrockError, merge_bedrock_aws_request_params, resolve_s3_encryption_key_id -# litellm_params key used to hand the SigV4-signed GET headers from -# `transform_file_content_request` to `validate_environment` (the only hook -# the shared file-content HTTP handler exposes for setting request headers). -# Same pattern as the `upload_url` handoff in `transform_create_file_request`. -S3_SIGNED_GET_HEADERS_PARAM: Final = "_s3_signed_get_headers" +S3_SIGNED_REQUEST_HEADERS_PARAM: Final = "_s3_signed_request_headers" +S3_DELETE_FILE_ID_PARAM: Final = "_s3_delete_file_id" # litellm_params key carrying the size of the body uploaded to S3, handed from # `transform_create_file_request` to `transform_create_file_response`. @@ -291,7 +288,7 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): ) -> dict: result: Final[dict[str, object]] = {} result.update(headers) - signed_headers: Final = litellm_params.pop(S3_SIGNED_GET_HEADERS_PARAM, None) + signed_headers: Final = litellm_params.pop(S3_SIGNED_REQUEST_HEADERS_PARAM, None) if isinstance(signed_headers, Mapping): result.update(signed_headers) # any-ok: untyped handoff headers # otherwise no extra headers - AWS credentials are handled by BaseAWSLLM @@ -1187,18 +1184,31 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): def transform_delete_file_request( self, file_id: str, - optional_params: dict, - litellm_params: dict, - ) -> tuple[str, dict]: - raise NotImplementedError("BedrockFilesConfig does not support file deletion") + optional_params: Mapping[str, object], + litellm_params: MutableMapping[str, object], + ) -> tuple[str, dict[str, str]]: + request: Final = self._transform_s3_file_request( + file_id=file_id, method="DELETE", optional_params=optional_params, litellm_params=litellm_params + ) + litellm_params[S3_DELETE_FILE_ID_PARAM] = file_id + return request def transform_delete_file_response( self, raw_response: httpx.Response, logging_obj: LiteLLMLoggingObj, - litellm_params: dict, + litellm_params: Mapping[str, object], ) -> FileDeleted: - raise NotImplementedError("BedrockFilesConfig does not support file deletion") + if raw_response.status_code != 204: + raise BedrockError( + status_code=raw_response.status_code if raw_response.status_code >= 400 else 502, + message=raw_response.text or f"S3 file deletion returned HTTP {raw_response.status_code}", + headers=raw_response.headers, + ) + file_id: Final = litellm_params.get(S3_DELETE_FILE_ID_PARAM) + if not isinstance(file_id, str) or not file_id: + raise ValueError("Missing file id for Bedrock file deletion response") + return FileDeleted(id=file_id, deleted=True, object="file") def transform_list_files_request( self, @@ -1233,6 +1243,18 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): if not file_id: raise ValueError("file_id is required for Bedrock file content retrieval") + return self._transform_s3_file_request( + file_id=file_id, method="GET", optional_params=optional_params, litellm_params=litellm_params + ) + + def _transform_s3_file_request( + self, + *, + file_id: str, + method: Literal["GET", "DELETE"], + optional_params: Mapping[str, object], + litellm_params: MutableMapping[str, object], + ) -> tuple[str, dict[str, str]]: s3_uri: Final = extract_s3_uri_from_file_id(file_id) bucket_name, object_key = _validate_file_id_against_configured_buckets( s3_uri=s3_uri, @@ -1240,40 +1262,32 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): allow_legacy_cloud_file_ids=should_allow_legacy_cloud_file_ids(litellm_params), ) - # The shared file-content handler passes optional_params={}, so AWS - # credentials/region arrive via litellm_params here (unlike the upload - # path). s3_region_name wins over aws_region_name, same priority as - # get_complete_file_url above. - merged_params: Final[dict[str, object]] = {} - merged_params.update(litellm_params) - merged_params.update(optional_params) - request_params: Final = _BedrockS3RequestParams.model_validate(merged_params) + request_params: Final = _BedrockS3RequestParams.model_validate({**litellm_params, **optional_params}) region_preference: Final = request_params.s3_region_name or request_params.aws_region_name region_params: Final[dict[str, str | None]] = {"aws_region_name": region_preference} aws_region_name: Final = self._get_aws_region_name(optional_params=region_params, model="") - s3_endpoint_url = ( + s3_endpoint_url: Final = ( request_params.s3_endpoint_url or f"https://s3.{aws_region_name}.{get_aws_dns_suffix(aws_region_name)}" ).rstrip("/") url: Final = f"{s3_endpoint_url}/{bucket_name}/{encode_s3_object_key_for_url(object_key)}" - litellm_params[S3_SIGNED_GET_HEADERS_PARAM] = self._sign_s3_get_request( + litellm_params[S3_SIGNED_REQUEST_HEADERS_PARAM] = self._sign_s3_request_without_body( api_base=url, aws_region_name=aws_region_name, request_params=request_params, + method=method, ) return url, {} - def _sign_s3_get_request( + def _sign_s3_request_without_body( self, api_base: str, aws_region_name: str, request_params: _BedrockS3RequestParams, + method: Literal["GET", "DELETE"] = "GET", ) -> dict[str, str]: - """ - SigV4-sign an S3 GetObject request, mirroring `_sign_s3_request` (PUT). - """ try: import hashlib @@ -1297,7 +1311,7 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): empty_body_hash: Final = hashlib.sha256(b"").hexdigest() aws_request: Final = AWSRequest( # any-ok: botocore AWSRequest is untyped - method="GET", + method=method, url=api_base, headers={"x-amz-content-sha256": empty_body_hash}, ) diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index f95ea1f2649..ca44fc95e25 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -131,7 +131,12 @@ failures up to three times. Teardown attempts every registered cleanup before reporting failures as test errors. Already deleted files and batches that are terminal are safe to clean up again. Managed batch cancellation polls for up to eleven minutes before input deletion: the ten-minute provider window plus a propagation margin. -Raw and model-encoded inputs can be deleted after cancellation is accepted +Accepted cancellation may still report validating or in_progress while the provider +updates its state. Raw and model-encoded batches are polled until cancelling or +terminal before input deletion. OpenAI and Azure lifecycle cleanup also deletes +output and error files returned by terminal batches. Bedrock deletion uses a signed S3 DELETE +restricted to the configured storage buckets and managed file prefixes. The low-RPM +test submits with its restricted key and cleans up with the test administrator key Azure input uploads request `expires_after` anchored to `created_at` with `seconds=1209600`, and the lifecycle tests check the returned expiry. This is a diff --git a/tests/e2e/batches/batch_cleanup.py b/tests/e2e/batches/batch_cleanup.py index 722df0c29bc..9284882ad82 100644 --- a/tests/e2e/batches/batch_cleanup.py +++ b/tests/e2e/batches/batch_cleanup.py @@ -1,16 +1,17 @@ +from builtins import ExceptionGroup from collections.abc import Callable from itertools import count from time import monotonic, sleep from typing import Final, Protocol -from pydantic import BaseModel - from batch_client import BatchObject, FileDeleteResponse from capabilities import is_managed_id from e2e_http import NetworkError, RateLimitedError, Result, Success, UnknownApiError +from pydantic import BaseModel CLEANUP_DELAYS: Final = (1.0, 2.0, 4.0) BATCH_TERMINAL_STATUSES: Final = frozenset({"completed", "failed", "expired", "cancelled"}) +BATCH_PENDING_STATUSES: Final = frozenset({"validating", "in_progress", "finalizing", "cancelling"}) BATCH_CANCEL_TIMEOUT_SECONDS: Final = 660.0 BATCH_CANCEL_POLL_SECONDS: Final = 10.0 @@ -63,6 +64,7 @@ def cleanup_batch( *, key: str, provider: str | None = None, + delete_output_files: bool = False, wait: Callable[[float], None] = sleep, clock: Callable[[], float] = monotonic, ) -> None: @@ -72,18 +74,28 @@ def cleanup_batch( f"Retrieve batch {batch_id} for cleanup", ) if fetched.status in BATCH_TERMINAL_STATUSES: + if delete_output_files: + _cleanup_batch_outputs(client, fetched, key=key, provider=provider) return if fetched.status == "cancelling" and not needs_terminal_state: return - if fetched.status != "cancelling": - result: Final = cleanup_result(lambda: client.cancel_batch(batch_id, key=key, provider=provider)) - if not (isinstance(result, UnknownApiError) and result.status_code in {400, 409}): - cancelled: Final = _require_cleanup_success(result, f"Cancel batch {batch_id}") - assert cancelled.status in BATCH_TERMINAL_STATUSES | {"cancelling"}, ( - f"Cancel batch {batch_id} left status {cancelled.status}" - ) - if not needs_terminal_state: - return + result: Final = ( + Success(status_code=200, data=fetched) + if fetched.status == "cancelling" + else cleanup_result(lambda: client.cancel_batch(batch_id, key=key, provider=provider)) + ) + conflicted: Final = isinstance(result, UnknownApiError) and result.status_code in {400, 409} + if not conflicted: + cancelled: Final = _require_cleanup_success(result, f"Cancel batch {batch_id}") + assert cancelled.status in BATCH_TERMINAL_STATUSES | BATCH_PENDING_STATUSES, ( + f"Cancel batch {batch_id} left status {cancelled.status}" + ) + if cancelled.status in BATCH_TERMINAL_STATUSES: + if delete_output_files: + _cleanup_batch_outputs(client, cancelled, key=key, provider=provider) + return + if cancelled.status == "cancelling" and not needs_terminal_state: + return deadline: Final = clock() + BATCH_CANCEL_TIMEOUT_SECONDS for current in ( _require_cleanup_success( @@ -93,11 +105,36 @@ def cleanup_batch( for _ in count() ): if current.status in BATCH_TERMINAL_STATUSES: + if delete_output_files: + _cleanup_batch_outputs(client, current, key=key, provider=provider) return - assert current.status == "cancelling", f"Cancel batch {batch_id} left status {current.status}" - if not needs_terminal_state: + assert current.status in ({"cancelling"} if conflicted else BATCH_PENDING_STATUSES), ( + f"Cancel batch {batch_id} left status {current.status}" + ) + if current.status == "cancelling" and not needs_terminal_state: return assert clock() < deadline, ( f"Batch {batch_id} cancellation did not finish within {BATCH_CANCEL_TIMEOUT_SECONDS}s" ) wait(BATCH_CANCEL_POLL_SECONDS) + + +def _cleanup_batch_outputs(client: BatchCleanupClient, batch: BatchObject, *, key: str, provider: str | None) -> None: + errors: Final = tuple( + error + for file_id in dict.fromkeys((batch.output_file_id, batch.error_file_id)) + if file_id is not None and file_id != batch.input_file_id + if (error := _output_cleanup_error(client, file_id, key=key, provider=provider)) is not None + ) + if errors: + raise ExceptionGroup(f"Batch {batch.id} output cleanup failed", errors) + + +def _output_cleanup_error( + client: BatchCleanupClient, file_id: str, *, key: str, provider: str | None +) -> Exception | None: + try: + cleanup_file(client, file_id, key=key, provider=provider) + except Exception as error: + return error + return None diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py index 66b7079ccc2..a875aee719b 100644 --- a/tests/e2e/batches/test_batch_cleanup.py +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -4,7 +4,6 @@ from dataclasses import dataclass, field from typing import Final import pytest - from batch_cleanup import BATCH_CANCEL_TIMEOUT_SECONDS, CLEANUP_DELAYS, cleanup_batch, cleanup_file, cleanup_result from batch_client import AZURE_FILE_EXPIRY_SECONDS, BatchObject, FileDeleteResponse, batch_upload_form from capabilities import CAPABILITIES, Capability @@ -219,6 +218,74 @@ class TestBatchCancellation: cleanup_batch(client, "batch-1", key="test-key", provider="azure") client.calls.assert_done() + @pytest.mark.parametrize("batch_id", ["batch-1", MANAGED_BATCH_ID]) + @pytest.mark.parametrize("pending_status", ["validating", "in_progress"]) + def test_accepted_cancellation_waits_through_stale_provider_status( + self, batch_id: str, pending_status: str + ) -> None: + client: Final = CleanupClient( + calls=ExpectedCalls( + iter( + ( + f"retrieve vertex_ai {batch_id}", + f"cancel vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + "delete vertex_ai file-1", + "delete key test-key", + ) + ) + ), + batches=iter((batch("validating"), batch(pending_status), batch(pending_status), batch("cancelled"))), + cancellations=iter((batch(pending_status),)), + files=iter((deleted_file(),)), + ) + delays: Final = ExpectedCalls(iter((10.0, 10.0))) + manager: Final = ResourceManager(client=client, strict_cleanup=True) + key: Final = manager.key() + manager.defer(lambda: cleanup_file(client, "file-1", key=key, provider="vertex_ai")) + manager.defer(lambda: cleanup_batch(client, batch_id, key=key, provider="vertex_ai", wait=delays)) + manager.teardown() + client.calls.assert_done() + delays.assert_done() + + @pytest.mark.parametrize("output_delete_fails", [False, True]) + def test_batch_that_completed_before_cleanup_deletes_output_and_error_files( + self, output_delete_fails: bool + ) -> None: + client: Final = CleanupClient( + calls=ExpectedCalls( + iter(("retrieve openai batch-1", "delete openai file-output", "delete openai file-error")) + ), + batches=iter( + ( + Success( + status_code=200, + data=BatchObject( + id="batch-1", + status="completed", + input_file_id="file-input", + output_file_id="file-output", + error_file_id="file-error", + ), + ), + ) + ), + files=iter( + ( + UnknownApiError(status_code=403, body="forbidden") if output_delete_fails else deleted_file(), + deleted_file(), + ) + ), + ) + if output_delete_fails: + with pytest.raises(ExceptionGroup, match="output cleanup failed"): + cleanup_batch(client, "batch-1", key="test-key", provider="openai", delete_output_files=True) + else: + cleanup_batch(client, "batch-1", key="test-key", provider="openai", delete_output_files=True) + client.calls.assert_done() + @pytest.mark.parametrize("status", ["completed", "in_progress"]) def test_cancellation_conflict_is_accepted_only_when_batch_became_inactive(self, status: str) -> None: client: Final = CleanupClient( diff --git a/tests/e2e/batches/test_batches_e2e.py b/tests/e2e/batches/test_batches_e2e.py index 1b4a6ed266f..c4b699190b8 100644 --- a/tests/e2e/batches/test_batches_e2e.py +++ b/tests/e2e/batches/test_batches_e2e.py @@ -25,7 +25,7 @@ from datetime import datetime, timedelta, timezone import pytest from pydantic import BaseModel -from e2e_config import PROXY_BASE_URL, unique_marker +from e2e_config import MASTER_KEY, PROXY_BASE_URL, unique_marker from batch_cleanup import cleanup_batch, cleanup_file from batch_client import ( @@ -257,7 +257,9 @@ def test_batch_lifecycle( require_successful_call(created) batch = BatchObject.model_validate_json(created.body) resources.defer( - lambda: cleanup_batch(client, batch.id, key=key, provider=provider) + lambda: cleanup_batch( + client, batch.id, key=key, provider=provider, delete_output_files=cap.provider in {"openai", "azure"} + ) ) assert batch.id, f"create returned no batch id (body={created.body[:200]})" @@ -801,7 +803,7 @@ class TestBatchEnqueuedTokenLimit: """ def _upload_batch_file( - self, client: BatchClient, resources: ResourceManager, key: str + self, client: BatchClient, resources: ResourceManager, key: str, *, cleanup_key: str | None = None ) -> FileObject: file = unwrap( client.upload_file( @@ -811,7 +813,7 @@ class TestBatchEnqueuedTokenLimit: key=key, ) ) - resources.defer(lambda: cleanup_file(client, file.id, key=key)) + resources.defer(lambda: cleanup_file(client, file.id, key=cleanup_key or key)) return file def _generate_enqueued_key( @@ -848,7 +850,7 @@ class TestBatchEnqueuedTokenLimit: marker="rpm", rpm_limit=BATCH_RL_RPM_LIMIT, ) - file = self._upload_batch_file(client, resources, key) + file = self._upload_batch_file(client, resources, key, cleanup_key=MASTER_KEY) created = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) @@ -859,7 +861,7 @@ class TestBatchEnqueuedTokenLimit: ) require_successful_call(created) batch = BatchObject.model_validate_json(created.body) - resources.defer(lambda: cleanup_batch(client, batch.id, key=key)) + resources.defer(lambda: cleanup_batch(client, batch.id, key=MASTER_KEY, delete_output_files=True)) @pytest.mark.covers( "quota_management.ratelimit.batch_enqueued_tokens.blocks_when_exhausted", diff --git a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py index 541c0db15d8..2c02a58663e 100644 --- a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py +++ b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py @@ -5,6 +5,8 @@ Test bedrock files transformation functionality import json import os from collections.abc import Mapping +from contextlib import AsyncExitStack, closing +from typing import Final from unittest.mock import MagicMock from urllib.parse import unquote, urlparse @@ -1855,6 +1857,77 @@ class TestBedrockBatchNonChatEndpointRecords: ] +class TestBedrockFileDeletion: + S3_URI: Final = "s3://my-bucket/litellm-bedrock-files-model-abc.jsonl" + URL: Final = "https://s3.us-west-2.amazonaws.com/my-bucket/litellm-bedrock-files-model-abc.jsonl" + + def test_delete_file_sends_signed_delete_and_returns_matching_id(self, monkeypatch: pytest.MonkeyPatch) -> None: + import httpx + import respx + + import litellm + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + monkeypatch.setenv("AWS_S3_BUCKET_NAME", "my-bucket") + with respx.mock, closing(HTTPHandler()) as client: + route: Final = respx.delete(self.URL).mock(return_value=httpx.Response(204)) + deleted: Final = litellm.file_delete( + file_id=self.S3_URI, custom_llm_provider="bedrock", client=client, + aws_access_key_id="AKIAEXAMPLE", aws_secret_access_key="test-secret", aws_region_name="us-west-2", + ) + assert route.call_count == 1 + request: Final = route.calls[0].request + assert request.content == b"" + signed: Final = AWSRequest(method="DELETE", url=self.URL, headers={ + "X-Amz-Date": request.headers["X-Amz-Date"], + "X-Amz-Content-SHA256": request.headers["X-Amz-Content-SHA256"], + }) + signed.context["timestamp"] = request.headers["X-Amz-Date"] + auth: Final = S3SigV4Auth(Credentials("AKIAEXAMPLE", "test-secret"), "s3", "us-west-2") + signature: Final = auth.signature(auth.string_to_sign(signed, auth.canonical_request(signed)), signed) + assert request.headers["Authorization"].endswith(f"Signature={signature}") + assert deleted.id == self.S3_URI and deleted.deleted is True + + @pytest.mark.asyncio + async def test_adelete_file_propagates_s3_errors(self, monkeypatch: pytest.MonkeyPatch) -> None: + import httpx + import respx + + import litellm + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + + monkeypatch.setenv("AWS_S3_BUCKET_NAME", "my-bucket") + monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True") + async with AsyncExitStack() as stack: + client: Final = AsyncHTTPHandler() + stack.push_async_callback(client.close) + with respx.mock: + route: Final = respx.delete(self.URL).mock( + return_value=httpx.Response(403, content=b"AccessDenied") + ) + from litellm.llms.bedrock.common_utils import BedrockError + + with pytest.raises(BedrockError, match="AccessDenied"): + await litellm.afile_delete( + file_id=self.S3_URI, custom_llm_provider="bedrock", client=client, + aws_access_key_id="AKIAEXAMPLE", aws_secret_access_key="test-secret", aws_region_name="us-west-2", + ) + assert route.call_count == 1 + + @pytest.mark.parametrize("file_id, message", [ + ("s3://other-bucket/litellm-bedrock-files-model-abc.jsonl", "configured storage bucket"), + ("s3://my-bucket/private/data.jsonl", "LiteLLM-managed"), + ]) + def test_delete_rejects_untrusted_objects_before_signing( + self, file_id: str, message: str, monkeypatch: pytest.MonkeyPatch + ) -> None: + from litellm.llms.bedrock.files.transformation import BedrockFilesConfig + + monkeypatch.setenv("AWS_S3_BUCKET_NAME", "my-bucket") + with pytest.raises(ValueError, match=message): + BedrockFilesConfig().transform_delete_file_request(file_id=file_id, optional_params={}, litellm_params={}) + + class TestBedrockFileContentTransformation: """SigV4-signed S3 GetObject retrieval of Bedrock batch output files.""" @@ -1873,7 +1946,7 @@ class TestBedrockFileContentTransformation: import hashlib from litellm.llms.bedrock.files.transformation import ( - S3_SIGNED_GET_HEADERS_PARAM, + S3_SIGNED_REQUEST_HEADERS_PARAM, BedrockFilesConfig, ) @@ -1889,7 +1962,7 @@ class TestBedrockFileContentTransformation: assert url == self.EXPECTED_URL assert params == {} - signed_headers = litellm_params[S3_SIGNED_GET_HEADERS_PARAM] + signed_headers = litellm_params[S3_SIGNED_REQUEST_HEADERS_PARAM] content_hashes = { value for name, value in signed_headers.items() @@ -2139,7 +2212,7 @@ class TestBedrockFileContentTransformation: def test_s3_region_name_wins_for_content_signing(self, monkeypatch): """s3_region_name must override aws_region_name for both the URL and the signature.""" from litellm.llms.bedrock.files.transformation import ( - S3_SIGNED_GET_HEADERS_PARAM, + S3_SIGNED_REQUEST_HEADERS_PARAM, BedrockFilesConfig, ) @@ -2154,17 +2227,17 @@ class TestBedrockFileContentTransformation: ) assert url.startswith("https://s3.eu-west-1.amazonaws.com/") - authorization = litellm_params[S3_SIGNED_GET_HEADERS_PARAM]["Authorization"] + authorization = litellm_params[S3_SIGNED_REQUEST_HEADERS_PARAM]["Authorization"] assert "/eu-west-1/s3/aws4_request" in authorization def test_validate_environment_merges_and_pops_signed_get_headers(self): from litellm.llms.bedrock.files.transformation import ( - S3_SIGNED_GET_HEADERS_PARAM, + S3_SIGNED_REQUEST_HEADERS_PARAM, BedrockFilesConfig, ) litellm_params = { - S3_SIGNED_GET_HEADERS_PARAM: {"Authorization": "AWS4-HMAC-SHA256 test"} + S3_SIGNED_REQUEST_HEADERS_PARAM: {"Authorization": "AWS4-HMAC-SHA256 test"} } headers = BedrockFilesConfig().validate_environment( @@ -2179,7 +2252,7 @@ class TestBedrockFileContentTransformation: "x-custom": "kept", "Authorization": "AWS4-HMAC-SHA256 test", } - assert S3_SIGNED_GET_HEADERS_PARAM not in litellm_params + assert S3_SIGNED_REQUEST_HEADERS_PARAM not in litellm_params def test_transform_file_content_response_wraps_binary_content(self): import httpx @@ -2379,7 +2452,7 @@ class TestBedrockFilesS3SignatureEncoding: self, monkeypatch: pytest.MonkeyPatch ) -> None: from litellm.llms.bedrock.files.transformation import ( - S3_SIGNED_GET_HEADERS_PARAM, + S3_SIGNED_REQUEST_HEADERS_PARAM, BedrockFilesConfig, ) @@ -2402,7 +2475,7 @@ class TestBedrockFilesS3SignatureEncoding: method="GET", url=url, body=None, - headers=litellm_params[S3_SIGNED_GET_HEADERS_PARAM], + headers=litellm_params[S3_SIGNED_REQUEST_HEADERS_PARAM], ) @@ -2457,7 +2530,7 @@ def test_sign_s3_request_assumes_role_with_external_id(monkeypatch): assert "ASIAFILESPUTROLE" in authorization -def test_sign_s3_get_request_assumes_role_with_external_id(monkeypatch): +def test_sign_s3_request_without_body_assumes_role_with_external_id(monkeypatch): """A trust policy requiring sts:ExternalId must be satisfied when signing the S3 download request.""" import datetime from unittest.mock import patch @@ -2504,7 +2577,7 @@ def test_sign_s3_get_request_assumes_role_with_external_id(monkeypatch): assert request_params.aws_external_id == "external-id-files-get" with patch.object(boto3, "client", return_value=FakeSTSClient()): - signed_headers = BedrockFilesConfig()._sign_s3_get_request( + signed_headers = BedrockFilesConfig()._sign_s3_request_without_body( api_base="https://s3.us-east-1.amazonaws.com/safe-bucket/litellm-bedrock-files-model-id-abc.jsonl", aws_region_name="us-east-1", request_params=request_params, From 4ab5719ff9e0770ecb9f2d1b53c4caf58f19e5db Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 16:36:27 -0700 Subject: [PATCH 010/105] test(batches): use immutable expectations with explicit test doubles --- litellm/files/main.py | 2 +- tests/e2e/batches/test_batch_cleanup.py | 187 ++++++++++++------------ 2 files changed, 93 insertions(+), 96 deletions(-) diff --git a/litellm/files/main.py b/litellm/files/main.py index 19da77b7364..218518eb3cd 100644 --- a/litellm/files/main.py +++ b/litellm/files/main.py @@ -31,7 +31,7 @@ FileCreateProvider = Literal[ FileRetrieveProvider = Literal[ "openai", "azure", "gemini", "vertex_ai", "hosted_vllm", "litellm_proxy", "manus", "anthropic" ] -FileDeleteProvider = Literal["openai", "azure", "gemini", "litellm_proxy", "manus", "anthropic"] +FileDeleteProvider = Literal["openai", "azure", "gemini", "bedrock", "litellm_proxy", "manus", "anthropic"] FileListProvider = Literal["openai", "azure", "litellm_proxy", "manus", "anthropic"] import litellm from litellm import get_secret_str diff --git a/tests/e2e/batches/test_batch_cleanup.py b/tests/e2e/batches/test_batch_cleanup.py index a875aee719b..d0038139dcf 100644 --- a/tests/e2e/batches/test_batch_cleanup.py +++ b/tests/e2e/batches/test_batch_cleanup.py @@ -1,7 +1,7 @@ from builtins import ExceptionGroup -from collections.abc import Iterator -from dataclasses import dataclass, field +from collections.abc import Callable from typing import Final +from unittest.mock import Mock, call import pytest from batch_cleanup import BATCH_CANCEL_TIMEOUT_SECONDS, CLEANUP_DELAYS, cleanup_batch, cleanup_file, cleanup_result @@ -15,35 +15,43 @@ MANAGED_FILE_ID: Final = "bGl0ZWxsbV9wcm94eTtmaWxlLTE=" MANAGED_BATCH_ID: Final = "bGl0ZWxsbV9wcm94eTtiYXRjaC0x" -@dataclass(frozen=True, slots=True) class ExpectedCalls[T]: - values: Iterator[T] + def __init__(self, values: tuple[T, ...]) -> None: + self.values: Final = values + self.recorder: Final = Mock() def __call__(self, value: T) -> None: - assert next(self.values, None) == value + self.recorder(value) def assert_done(self) -> None: - assert tuple(self.values) == () + assert tuple(self.recorder.call_args_list) == tuple(call(value) for value in self.values) -@dataclass(frozen=True, slots=True) class CleanupClient: - calls: ExpectedCalls[str] - files: Iterator[Result[FileDeleteResponse]] = field(default_factory=lambda: iter(())) - batches: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) - cancellations: Iterator[Result[BatchObject]] = field(default_factory=lambda: iter(())) + def __init__( + self, + *, + calls: ExpectedCalls[str], + files: tuple[Result[FileDeleteResponse], ...] = (), + batches: tuple[Result[BatchObject], ...] = (), + cancellations: tuple[Result[BatchObject], ...] = (), + ) -> None: + self.calls: Final = calls + self.file_response: Final[Callable[[], Result[FileDeleteResponse]]] = Mock(side_effect=files) + self.batch_response: Final[Callable[[], Result[BatchObject]]] = Mock(side_effect=batches) + self.cancel_response: Final[Callable[[], Result[BatchObject]]] = Mock(side_effect=cancellations) def delete_file(self, file_id: str, *, key: str, provider: str | None = None) -> Result[FileDeleteResponse]: self.calls(f"delete {provider} {file_id}") - return next(self.files) + return self.file_response() def retrieve_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: self.calls(f"retrieve {provider} {batch_id}") - return next(self.batches) + return self.batch_response() def cancel_batch(self, batch_id: str, *, key: str, provider: str | None = None) -> Result[BatchObject]: self.calls(f"cancel {provider} {batch_id}") - return next(self.cancellations) + return self.cancel_response() def generate_key(self, body: KeyGenerateBody) -> str: return "test-key" @@ -68,17 +76,15 @@ class TestFileCleanup: response: Final = Success( status_code=200, data=FileDeleteResponse.model_validate({"id": MANAGED_FILE_ID, "object": "file"}) ) - client: Final = CleanupClient( - calls=ExpectedCalls(iter((f"delete None {MANAGED_FILE_ID}",))), files=iter((response,)) - ) + client: Final = CleanupClient(calls=ExpectedCalls((f"delete None {MANAGED_FILE_ID}",)), files=(response,)) cleanup_file(client, MANAGED_FILE_ID, key="test-key") client.calls.assert_done() @pytest.mark.parametrize("file_id", ["file-1", MANAGED_FILE_ID]) def test_a_success_status_without_a_deletion_confirmation_is_rejected(self, file_id: str) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter((f"delete None {file_id}",))), - files=iter((Success(status_code=200, data=FileDeleteResponse(id=file_id)),)), + calls=ExpectedCalls((f"delete None {file_id}",)), + files=(Success(status_code=200, data=FileDeleteResponse(id=file_id)),), ) with pytest.raises(AssertionError, match="did not confirm deletion"): cleanup_file(client, file_id, key="test-key") @@ -88,15 +94,15 @@ class TestFileCleanup: def test_deletes_raw_files_through_the_upload_provider(self, cap: Capability) -> None: expected_provider: Final = cap.provider if cap.scenario in {"model_param", "provider_fallback"} else None client: Final = CleanupClient( - calls=ExpectedCalls(iter((f"delete {expected_provider} file-1",))), files=iter((deleted_file(),)) + calls=ExpectedCalls((f"delete {expected_provider} file-1",)), files=(deleted_file(),) ) cleanup_file(client, "file-1", key="test-key", provider=cap.file_provider) client.calls.assert_done() def test_failed_delete_is_reported_after_remaining_resources_are_cleaned(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("delete azure file-1", "delete key test-key"))), - files=iter((UnknownApiError(status_code=403, body="secret response"),)), + calls=ExpectedCalls(("delete azure file-1", "delete key test-key")), + files=(UnknownApiError(status_code=403, body="secret response"),), ) manager: Final = ResourceManager(client=client, strict_cleanup=True) key: Final = manager.key() @@ -109,7 +115,7 @@ class TestFileCleanup: def test_success_response_must_confirm_deletion(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("delete None file-1",))), files=iter((deleted_file(deleted=False),)) + calls=ExpectedCalls(("delete None file-1",)), files=(deleted_file(deleted=False),) ) with pytest.raises(AssertionError, match="did not confirm deletion"): cleanup_file(client, "file-1", key="test-key") @@ -117,16 +123,16 @@ class TestFileCleanup: def test_cleanup_is_idempotent_when_file_is_already_deleted(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("delete azure file-1",))), - files=iter((UnknownApiError(status_code=404, body="missing"),)), + calls=ExpectedCalls(("delete azure file-1",)), + files=(UnknownApiError(status_code=404, body="missing"),), ) cleanup_file(client, "file-1", key="test-key", provider="azure") client.calls.assert_done() def test_default_resource_cleanup_keeps_existing_best_effort_behavior(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("delete None file-1", "delete key test-key"))), - files=iter((UnknownApiError(status_code=403, body="forbidden"),)), + calls=ExpectedCalls(("delete None file-1", "delete key test-key")), + files=(UnknownApiError(status_code=403, body="forbidden"),), ) manager: Final = ResourceManager(client=client) key: Final = manager.key() @@ -141,37 +147,39 @@ class TestCleanupRetries: [NetworkError(message="offline"), RateLimitedError(), UnknownApiError(status_code=503, body="unavailable")], ) def test_transient_error_retries_and_returns_success(self, failure: Result[FileDeleteResponse]) -> None: - outcomes: Final = iter((failure, deleted_file())) - delays: Final = ExpectedCalls(iter((1.0,))) - result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays) + responses: Final = (failure, deleted_file()) + outcomes: Final = Mock(side_effect=responses) + delays: Final = ExpectedCalls((1.0,)) + result: Final[Result[FileDeleteResponse]] = cleanup_result(outcomes, wait=delays) assert isinstance(result, Success) and result.data.deleted delays.assert_done() def test_persistent_error_has_bounded_retries(self) -> None: failure: Final = UnknownApiError(status_code=503, body="unavailable") - outcomes: Final[Iterator[Result[FileDeleteResponse]]] = iter((failure,) * (len(CLEANUP_DELAYS) + 1)) - delays: Final = ExpectedCalls(iter(CLEANUP_DELAYS)) - result: Final[Result[FileDeleteResponse]] = cleanup_result(lambda: next(outcomes), wait=delays) + outcomes: Final = Mock(return_value=failure) + delays: Final = ExpectedCalls(CLEANUP_DELAYS) + result: Final[Result[FileDeleteResponse]] = cleanup_result(outcomes, wait=delays) assert result is failure delays.assert_done() - assert next(outcomes, None) is None + assert outcomes.call_count == len(CLEANUP_DELAYS) + 1 def test_permanent_error_is_not_retried(self) -> None: failure: Final = UnknownApiError(status_code=403, body="forbidden") - outcomes: Final = iter((failure, deleted_file())) - delays: Final = ExpectedCalls[float](iter(())) - assert cleanup_result(lambda: next(outcomes), wait=delays) is failure + responses: Final = (failure, deleted_file()) + outcomes: Final = Mock(side_effect=responses) + delays: Final = ExpectedCalls[float](()) + assert cleanup_result(outcomes, wait=delays) is failure delays.assert_done() - assert isinstance(next(outcomes), Success) + assert outcomes.call_count == 1 class TestBatchCancellation: def test_cancelling_batch_is_polled_until_terminal_without_cancelling_again(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter((f"retrieve None {MANAGED_BATCH_ID}",) * 3)), - batches=iter((batch("cancelling"), batch("cancelling"), batch("cancelled"))), + calls=ExpectedCalls((f"retrieve None {MANAGED_BATCH_ID}",) * 3), + batches=(batch("cancelling"), batch("cancelling"), batch("cancelled")), ) - delays: Final = ExpectedCalls(iter((10.0,))) + delays: Final = ExpectedCalls((10.0,)) cleanup_batch(client, MANAGED_BATCH_ID, key="test-key", wait=delays) client.calls.assert_done() delays.assert_done() @@ -179,23 +187,22 @@ class TestBatchCancellation: def test_cancellation_timeout_is_reported_but_file_and_key_cleanup_still_run(self) -> None: client: Final = CleanupClient( calls=ExpectedCalls( - iter( - ( - f"retrieve None {MANAGED_BATCH_ID}", - f"retrieve None {MANAGED_BATCH_ID}", - "delete None file-1", - "delete key test-key", - ) + ( + f"retrieve None {MANAGED_BATCH_ID}", + f"retrieve None {MANAGED_BATCH_ID}", + "delete None file-1", + "delete key test-key", ) ), - batches=iter((batch("cancelling"), batch("cancelling"))), - files=iter((deleted_file(),)), + batches=(batch("cancelling"), batch("cancelling")), + files=(deleted_file(),), ) - ticks: Final = iter((0.0, BATCH_CANCEL_TIMEOUT_SECONDS)) + times: Final = (0.0, BATCH_CANCEL_TIMEOUT_SECONDS) + ticks: Final[Callable[[], float]] = Mock(side_effect=times) manager: Final = ResourceManager(client=client, strict_cleanup=True) key: Final = manager.key() manager.defer(lambda: cleanup_file(client, "file-1", key=key)) - manager.defer(lambda: cleanup_batch(client, MANAGED_BATCH_ID, key=key, clock=lambda: next(ticks))) + manager.defer(lambda: cleanup_batch(client, MANAGED_BATCH_ID, key=key, clock=ticks)) with pytest.raises(ExceptionGroup) as caught: manager.teardown() assert "cancellation did not finish" in str(caught.value.exceptions[0]) @@ -203,17 +210,15 @@ class TestBatchCancellation: @pytest.mark.parametrize("status", ["completed", "failed", "expired", "cancelled"]) def test_inactive_batch_needs_no_cancellation(self, status: str) -> None: - client: Final = CleanupClient( - calls=ExpectedCalls(iter(("retrieve None batch-1",))), batches=iter((batch(status),)) - ) + client: Final = CleanupClient(calls=ExpectedCalls(("retrieve None batch-1",)), batches=(batch(status),)) cleanup_batch(client, "batch-1", key="test-key") client.calls.assert_done() def test_active_batch_is_cancelled_through_its_provider(self) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("retrieve azure batch-1", "cancel azure batch-1"))), - batches=iter((batch("in_progress"), batch("cancelled"))), - cancellations=iter((batch("cancelling"),)), + calls=ExpectedCalls(("retrieve azure batch-1", "cancel azure batch-1")), + batches=(batch("in_progress"), batch("cancelled")), + cancellations=(batch("cancelling"),), ) cleanup_batch(client, "batch-1", key="test-key", provider="azure") client.calls.assert_done() @@ -225,23 +230,21 @@ class TestBatchCancellation: ) -> None: client: Final = CleanupClient( calls=ExpectedCalls( - iter( - ( - f"retrieve vertex_ai {batch_id}", - f"cancel vertex_ai {batch_id}", - f"retrieve vertex_ai {batch_id}", - f"retrieve vertex_ai {batch_id}", - f"retrieve vertex_ai {batch_id}", - "delete vertex_ai file-1", - "delete key test-key", - ) + ( + f"retrieve vertex_ai {batch_id}", + f"cancel vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + f"retrieve vertex_ai {batch_id}", + "delete vertex_ai file-1", + "delete key test-key", ) ), - batches=iter((batch("validating"), batch(pending_status), batch(pending_status), batch("cancelled"))), - cancellations=iter((batch(pending_status),)), - files=iter((deleted_file(),)), + batches=(batch("validating"), batch(pending_status), batch(pending_status), batch("cancelled")), + cancellations=(batch(pending_status),), + files=(deleted_file(),), ) - delays: Final = ExpectedCalls(iter((10.0, 10.0))) + delays: Final = ExpectedCalls((10.0, 10.0)) manager: Final = ResourceManager(client=client, strict_cleanup=True) key: Final = manager.key() manager.defer(lambda: cleanup_file(client, "file-1", key=key, provider="vertex_ai")) @@ -255,28 +258,22 @@ class TestBatchCancellation: self, output_delete_fails: bool ) -> None: client: Final = CleanupClient( - calls=ExpectedCalls( - iter(("retrieve openai batch-1", "delete openai file-output", "delete openai file-error")) - ), - batches=iter( - ( - Success( - status_code=200, - data=BatchObject( - id="batch-1", - status="completed", - input_file_id="file-input", - output_file_id="file-output", - error_file_id="file-error", - ), + calls=ExpectedCalls(("retrieve openai batch-1", "delete openai file-output", "delete openai file-error")), + batches=( + Success( + status_code=200, + data=BatchObject( + id="batch-1", + status="completed", + input_file_id="file-input", + output_file_id="file-output", + error_file_id="file-error", ), - ) + ), ), - files=iter( - ( - UnknownApiError(status_code=403, body="forbidden") if output_delete_fails else deleted_file(), - deleted_file(), - ) + files=( + UnknownApiError(status_code=403, body="forbidden") if output_delete_fails else deleted_file(), + deleted_file(), ), ) if output_delete_fails: @@ -289,9 +286,9 @@ class TestBatchCancellation: @pytest.mark.parametrize("status", ["completed", "in_progress"]) def test_cancellation_conflict_is_accepted_only_when_batch_became_inactive(self, status: str) -> None: client: Final = CleanupClient( - calls=ExpectedCalls(iter(("retrieve None batch-1", "cancel None batch-1", "retrieve None batch-1"))), - batches=iter((batch("in_progress"), batch(status))), - cancellations=iter((UnknownApiError(status_code=409, body="conflict"),)), + calls=ExpectedCalls(("retrieve None batch-1", "cancel None batch-1", "retrieve None batch-1")), + batches=(batch("in_progress"), batch(status)), + cancellations=(UnknownApiError(status_code=409, body="conflict"),), ) if status == "completed": cleanup_batch(client, "batch-1", key="test-key") From 43a02e2dbcd485c3cc5dd31d8b11161da650c356 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 7 Sep 2026 16:53:41 -0700 Subject: [PATCH 011/105] fix(proxy): report the billed token rates in /cost/estimate The rate fields reported base cost-map prices while the cost lines were billed at the token tier, off-peak window and regional multipliers the calculator picks for the request, so a line did not always equal tokens times its reported rate. get_billed_token_rates now resolves the rates once, the token-type breakdown and the endpoint both read from it, and a tiered-model test asserts every line equals its token count times the rate reported next to it Claude-Session: https://claude.ai/code/session_011Tn3657NkV6ojLqewL64Kb --- .../litellm_core_utils/llm_cost_calc/utils.py | 206 ++++++++++++------ litellm/proxy/_types.py | 6 +- .../cost_tracking_settings.py | 63 ++---- .../llm_cost_calc/test_llm_cost_calc_utils.py | 49 +++++ .../test_cost_tracking_settings.py | 52 ++++- ui/litellm-dashboard/src/lib/http/schema.d.ts | 10 +- 6 files changed, 258 insertions(+), 128 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 8d53c8da3e6..2bb15fb4c48 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -1329,16 +1329,118 @@ def _cache_token_counts(usage: Usage) -> tuple[int, int, CacheCreationTokenDetai ) -def _custom_pricing_token_type_breakdown(usage: Usage, custom_cost_per_token: CostPerToken) -> TokenTypeCostBreakdown: +@dataclass(frozen=True, slots=True) +class BilledTokenRates: + """Per-token rates one request's usage bills at, after token tiers, off-peak windows and the + regional multipliers the totals apply, so each cost line equals its token count times its rate.""" + + input_cost_per_token: float + output_cost_per_token: float + cache_read_input_token_cost: float + cache_creation_input_token_cost: float + cache_creation_input_token_cost_above_1hr: float + output_cost_per_reasoning_token: float + + def scaled(self, multiplier: float) -> "BilledTokenRates": + if multiplier == 1.0: + return self + return BilledTokenRates( + input_cost_per_token=self.input_cost_per_token * multiplier, + output_cost_per_token=self.output_cost_per_token * multiplier, + cache_read_input_token_cost=self.cache_read_input_token_cost * multiplier, + cache_creation_input_token_cost=self.cache_creation_input_token_cost * multiplier, + cache_creation_input_token_cost_above_1hr=self.cache_creation_input_token_cost_above_1hr * multiplier, + output_cost_per_reasoning_token=self.output_cost_per_reasoning_token * multiplier, + ) + + +def _custom_pricing_rates(custom_cost_per_token: CostPerToken) -> BilledTokenRates: """Flat custom pricing has no tiers, uplifts or reasoning rate: cache tokens bill at the configured cache rates (else the input rate) and reasoning at the output rate, as _cost_per_token_custom_pricing_helper does.""" input_rate: Final = custom_cost_per_token["input_cost_per_token"] - cache_read_tokens, cache_creation_tokens, _ = _cache_token_counts(usage) - return TokenTypeCostBreakdown( - reasoning_cost=float(_reasoning_token_count(usage)) * custom_cost_per_token["output_cost_per_token"], - cache_read_cost=float(cache_read_tokens) * custom_cost_per_token.get("cache_read_input_token_cost", input_rate), - cache_creation_cost=float(cache_creation_tokens) - * custom_cost_per_token.get("cache_creation_input_token_cost", input_rate), + output_rate: Final = custom_cost_per_token["output_cost_per_token"] + cache_creation_rate: Final = custom_cost_per_token.get("cache_creation_input_token_cost", input_rate) + return BilledTokenRates( + input_cost_per_token=input_rate, + output_cost_per_token=output_rate, + cache_read_input_token_cost=custom_cost_per_token.get("cache_read_input_token_cost", input_rate), + cache_creation_input_token_cost=cache_creation_rate, + cache_creation_input_token_cost_above_1hr=cache_creation_rate, + output_cost_per_reasoning_token=output_rate, + ) + + +def _cost_map_billed_rates( + model_info: ModelInfo, + usage: Usage, + custom_llm_provider: str | None, + service_tier: str | None, + data_residency: str | None, + vertex_location: str | None, + current_time: datetime | None, +) -> BilledTokenRates: + billing_time: Final = current_time if current_time is not None else datetime.now(timezone.utc) + ( + prompt_base_cost, + completion_base_cost, + cache_creation_cost_rate, + cache_creation_cost_above_1hr_rate, + cache_read_cost_rate, + ) = _get_token_base_cost( + model_info=model_info, + usage=usage, + service_tier=service_tier, + current_time=billing_time, + threshold_is_inclusive=_uses_inclusive_token_thresholds(custom_llm_provider), + ) + reasoning_rate: Final = _resolve_billed_reasoning_rate( + model_info=model_info, + usage=usage, + service_tier=service_tier, + completion_base_cost=completion_base_cost, + current_time=billing_time, + ) + multiplier: Final = ( + _get_regional_uplift_multiplier(model_info, data_residency) + * get_vertex_regional_endpoint_uplift(model_info, vertex_location) + * get_provider_specific_geo_multiplier(model_info=model_info, usage=usage) + ) + return BilledTokenRates( + input_cost_per_token=prompt_base_cost, + output_cost_per_token=completion_base_cost, + cache_read_input_token_cost=cache_read_cost_rate, + cache_creation_input_token_cost=cache_creation_cost_rate, + cache_creation_input_token_cost_above_1hr=cache_creation_cost_above_1hr_rate, + output_cost_per_reasoning_token=reasoning_rate, + ).scaled(multiplier) + + +def get_billed_token_rates( + model: str, + custom_llm_provider: str | None, + usage: Usage, + service_tier: str | None = None, + data_residency: str | None = None, + vertex_location: str | None = None, + current_time: datetime | None = None, + custom_cost_per_token: CostPerToken | None = None, +) -> BilledTokenRates | None: + """Rates the cost calculator bills ``usage`` at, resolved exactly as the totals and the token-type + breakdown resolve them. None when the model's pricing cannot be resolved.""" + if custom_cost_per_token is not None: + return _custom_pricing_rates(custom_cost_per_token) + try: + model_info: Final = get_model_info(model=model, custom_llm_provider=custom_llm_provider) + except Exception: + return None + return _cost_map_billed_rates( + model_info=model_info, + usage=usage, + custom_llm_provider=custom_llm_provider, + service_tier=service_tier, + data_residency=data_residency, + vertex_location=vertex_location, + current_time=current_time, ) @@ -1360,77 +1462,39 @@ def get_token_type_cost_breakdown( cost calculators bypass ``generic_cost_per_token``, because cache tokens always land on ``prompt_tokens_details`` (via the Usage constructor and provider transformations) and reasoning tokens on ``completion_tokens_details``. It reuses - the same rate-resolution primitives as the total-cost path so the breakdown can - never drift from the totals. A deployment billed by ``custom_cost_per_token`` is - priced from those flat rates instead of the cost map, for the same reason. + the same rate resolution as the total-cost path (``get_billed_token_rates``) so the + breakdown can never drift from the totals. A deployment billed by + ``custom_cost_per_token`` is priced from those flat rates instead of the cost map and, + like its totals, bills cache writes flat rather than by their 5m/1h split. Returns zeros (never raises) when the model or its pricing cannot be resolved. """ - if custom_cost_per_token is not None: - return _custom_pricing_token_type_breakdown(usage=usage, custom_cost_per_token=custom_cost_per_token) - - try: - model_info: Final = get_model_info(model=model, custom_llm_provider=custom_llm_provider) - except Exception: + rates: Final = get_billed_token_rates( + model=model, + custom_llm_provider=custom_llm_provider, + usage=usage, + service_tier=service_tier, + data_residency=data_residency, + vertex_location=vertex_location, + current_time=current_time, + custom_cost_per_token=custom_cost_per_token, + ) + if rates is None: return TokenTypeCostBreakdown(0.0, 0.0, 0.0) - billing_time: Final = current_time if current_time is not None else datetime.now(timezone.utc) - ( - _prompt_base_cost, - completion_base_cost, - cache_creation_cost_rate, - cache_creation_cost_above_1hr_rate, - cache_read_cost_rate, - ) = _get_token_base_cost( - model_info=model_info, - usage=usage, - service_tier=service_tier, - current_time=billing_time, - threshold_is_inclusive=_uses_inclusive_token_thresholds(custom_llm_provider), - ) - - reasoning_rate: Final = _resolve_billed_reasoning_rate( - model_info=model_info, - usage=usage, - service_tier=service_tier, - completion_base_cost=completion_base_cost, - current_time=billing_time, - ) - reasoning_cost = float(_reasoning_token_count(usage)) * reasoning_rate - cache_read_tokens, cache_creation_tokens, cache_creation_token_details = _cache_token_counts(usage) - cache_read_cost = float(cache_read_tokens) * cache_read_cost_rate - cache_creation_cost = calculate_cache_writing_cost( - cache_creation_tokens=cache_creation_tokens, - cache_creation_token_details=cache_creation_token_details, - cache_creation_cost_above_1hr=cache_creation_cost_above_1hr_rate, - cache_creation_cost=cache_creation_cost_rate, + cache_creation_cost: Final = ( + float(cache_creation_tokens) * rates.cache_creation_input_token_cost + if custom_cost_per_token is not None + else calculate_cache_writing_cost( + cache_creation_tokens=cache_creation_tokens, + cache_creation_token_details=cache_creation_token_details, + cache_creation_cost_above_1hr=rates.cache_creation_input_token_cost_above_1hr, + cache_creation_cost=rates.cache_creation_input_token_cost, + ) ) - - # Apply the same flat regional-processing uplift the totals get, so per-type - # costs stay reconciled with input_cost/output_cost for regionalized OpenAI hosts. - uplift: Final = _get_regional_uplift_multiplier(model_info, data_residency) - if uplift != 1.0: - reasoning_cost *= uplift - cache_read_cost *= uplift - cache_creation_cost *= uplift - - vertex_uplift: Final = get_vertex_regional_endpoint_uplift(model_info, vertex_location) - if vertex_uplift != 1.0: - reasoning_cost *= vertex_uplift - cache_read_cost *= vertex_uplift - cache_creation_cost *= vertex_uplift - - # Mirror the provider-specific geo uplift (e.g. Anthropic us: 1.1) the totals - # apply, so cache and reasoning line items stay reconciled with them. - geo_multiplier: Final = get_provider_specific_geo_multiplier(model_info=model_info, usage=usage) - if geo_multiplier != 1.0: - reasoning_cost *= geo_multiplier - cache_read_cost *= geo_multiplier - cache_creation_cost *= geo_multiplier - return TokenTypeCostBreakdown( - reasoning_cost=reasoning_cost, - cache_read_cost=cache_read_cost, + reasoning_cost=float(_reasoning_token_count(usage)) * rates.output_cost_per_reasoning_token, + cache_read_cost=float(cache_read_tokens) * rates.cache_read_input_token_cost, cache_creation_cost=cache_creation_cost, ) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 65b7d409d7c..3c1cd9bfc69 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -5197,9 +5197,9 @@ class CostEstimateResponse(LiteLLMPydanticObjectBase): default=None, description="Cache-write share of monthly_input_cost" ) monthly_reasoning_cost: float | None = Field(default=None, description="Reasoning share of monthly_output_cost") - # Pricing info - input_cost_per_token: float | None = None - output_cost_per_token: float | None = None + # Pricing info: the rates this request's usage bills at, after token tiers and regional multipliers + input_cost_per_token: float | None = Field(default=None, description="Rate billed per input token") + output_cost_per_token: float | None = Field(default=None, description="Rate billed per output token") cache_read_input_token_cost: float | None = Field(default=None, description="Rate billed per cache-read token") cache_creation_input_token_cost: float | None = Field(default=None, description="Rate billed per cache-write token") output_cost_per_reasoning_token: float | None = Field(default=None, description="Rate billed per reasoning token") diff --git a/litellm/proxy/management_endpoints/cost_tracking_settings.py b/litellm/proxy/management_endpoints/cost_tracking_settings.py index 51f31a757cd..17d82fd17e3 100644 --- a/litellm/proxy/management_endpoints/cost_tracking_settings.py +++ b/litellm/proxy/management_endpoints/cost_tracking_settings.py @@ -20,6 +20,7 @@ from pydantic import BaseModel import litellm from litellm._logging import verbose_proxy_logger from litellm.cost_calculator import completion_cost +from litellm.litellm_core_utils.llm_cost_calc.utils import get_billed_token_rates from litellm.proxy._types import ( CommonProxyErrors, CostEstimateRequest, @@ -170,44 +171,6 @@ def _cost_lines(cost_per_request: float, cost_breakdown: CostBreakdown | None) - ) -@dataclass(frozen=True, slots=True) -class EffectiveTokenRates: - input_cost_per_token: float | None - output_cost_per_token: float | None - cache_read_input_token_cost: float | None - cache_creation_input_token_cost: float | None - output_cost_per_reasoning_token: float | None - - -def _custom_token_rates(custom_cost_per_token: CostPerToken) -> EffectiveTokenRates: - input_rate: Final = custom_cost_per_token["input_cost_per_token"] - output_rate: Final = custom_cost_per_token["output_cost_per_token"] - return EffectiveTokenRates( - input_cost_per_token=input_rate, - output_cost_per_token=output_rate, - cache_read_input_token_cost=custom_cost_per_token.get("cache_read_input_token_cost", input_rate), - cache_creation_input_token_cost=custom_cost_per_token.get("cache_creation_input_token_cost", input_rate), - output_cost_per_reasoning_token=output_rate, - ) - - -def _cost_map_token_rates(model_info: ModelInfo | None) -> EffectiveTokenRates: - """Base rates the cost calculator bills flat usage at: a cost-map model without a cache price - bills cache tokens at zero, and one without a reasoning price bills reasoning at the output rate.""" - if model_info is None: - return EffectiveTokenRates(None, None, None, None, None) - sources: Final = (model_info,) - output_rate: Final = _configured_price("output_cost_per_token", sources) - reasoning_rate: Final = _configured_price("output_cost_per_reasoning_token", sources) - return EffectiveTokenRates( - input_cost_per_token=_configured_price("input_cost_per_token", sources), - output_cost_per_token=output_rate, - cache_read_input_token_cost=_configured_price("cache_read_input_token_cost", sources) or 0.0, - cache_creation_input_token_cost=_configured_price("cache_creation_input_token_cost", sources) or 0.0, - output_cost_per_reasoning_token=output_rate if reasoning_rate is None else reasoning_rate, - ) - - def _usage_for_estimate(request: CostEstimateRequest) -> Usage: cache_tokens: Final = request.cache_read_input_tokens + request.cache_creation_input_tokens return Usage( @@ -654,7 +617,8 @@ async def estimate_cost( verbose_proxy_logger.debug("Cost estimate: request.model='%s' resolved to '%s'", request.model, resolved_model) - mock_response: Final = ModelResponse(model=resolved_model, usage=_usage_for_estimate(request)) + usage: Final = _usage_for_estimate(request) + mock_response: Final = ModelResponse(model=resolved_model, usage=usage) # Create a logging object to capture cost breakdown litellm_logging_obj: Final = LiteLLMLoggingObj( @@ -688,12 +652,13 @@ async def estimate_cost( daily: Final = per_request.times(request.num_requests_per_day) monthly: Final = per_request.times(request.num_requests_per_month) - model_info: Final = _lookup_model_info(resolved_model) - rates: Final = ( - _custom_token_rates(resolved.custom_cost_per_token) - if resolved.custom_cost_per_token is not None - else _cost_map_token_rates(model_info) + rates: Final = get_billed_token_rates( + model=resolved_model, + custom_llm_provider=resolved_provider, + usage=usage, + custom_cost_per_token=resolved.custom_cost_per_token, ) + model_info: Final = _lookup_model_info(resolved_model) mapped_provider: Final = model_info.get("litellm_provider") if model_info is not None else None custom_llm_provider: Final = mapped_provider if mapped_provider is not None else resolved_provider @@ -727,10 +692,10 @@ async def estimate_cost( monthly_cache_read_cost=monthly.cache_read_cost if monthly is not None else None, monthly_cache_creation_cost=monthly.cache_creation_cost if monthly is not None else None, monthly_reasoning_cost=monthly.reasoning_cost if monthly is not None else None, - input_cost_per_token=rates.input_cost_per_token, - output_cost_per_token=rates.output_cost_per_token, - cache_read_input_token_cost=rates.cache_read_input_token_cost, - cache_creation_input_token_cost=rates.cache_creation_input_token_cost, - output_cost_per_reasoning_token=rates.output_cost_per_reasoning_token, + input_cost_per_token=rates.input_cost_per_token if rates is not None else None, + output_cost_per_token=rates.output_cost_per_token if rates is not None else None, + cache_read_input_token_cost=rates.cache_read_input_token_cost if rates is not None else None, + cache_creation_input_token_cost=rates.cache_creation_input_token_cost if rates is not None else None, + output_cost_per_reasoning_token=rates.output_cost_per_reasoning_token if rates is not None else None, provider=custom_llm_provider, ) diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 7065003839b..4de3c059e63 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -27,6 +27,7 @@ from litellm.types.utils import ( ) from litellm.litellm_core_utils.llm_cost_calc.utils import ( + BilledTokenRates, CostCalculatorUtils, PromptTokensDetailsResult, TokenRates, @@ -38,6 +39,7 @@ from litellm.litellm_core_utils.llm_cost_calc.utils import ( apply_off_peak_pricing, calculate_cache_writing_cost, generic_cost_per_token, + get_billed_token_rates, get_token_type_cost_breakdown, ) from litellm.types.utils import CacheCreationTokenDetails, Usage @@ -3938,6 +3940,53 @@ def test_token_type_cost_breakdown_reconciles_with_custom_pricing_totals(): assert 300 * 2e-6 + breakdown.reasoning_cost == pytest.approx(completion_cost) +def test_billed_token_rates_follow_the_token_tier_the_breakdown_bills_at(monkeypatch): + monkeypatch.setitem( + litellm.model_cost, + "tiered-cache-model", + { + "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, + "input_cost_per_token_above_200k_tokens": 6e-6, + "output_cost_per_token_above_200k_tokens": 3e-5, + "cache_read_input_token_cost_above_200k_tokens": 6e-7, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-6, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + usage = Usage( + prompt_tokens=250_000, + completion_tokens=1_000, + total_tokens=251_000, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=200_000, cache_creation_tokens=10_000), + completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=200), + ) + + rates = get_billed_token_rates(model="tiered-cache-model", custom_llm_provider="openai", usage=usage) + breakdown = get_token_type_cost_breakdown(model="tiered-cache-model", custom_llm_provider="openai", usage=usage) + + assert rates == BilledTokenRates( + input_cost_per_token=6e-6, + output_cost_per_token=3e-5, + cache_read_input_token_cost=6e-7, + cache_creation_input_token_cost=7.5e-6, + cache_creation_input_token_cost_above_1hr=0.0, + output_cost_per_reasoning_token=3e-5, + ) + assert breakdown.cache_read_cost == pytest.approx(200_000 * rates.cache_read_input_token_cost) + assert breakdown.cache_creation_cost == pytest.approx(10_000 * rates.cache_creation_input_token_cost) + assert breakdown.reasoning_cost == pytest.approx(200 * rates.output_cost_per_reasoning_token) + + +def test_billed_token_rates_are_none_for_an_unpriced_model(): + usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15) + + assert get_billed_token_rates(model="no-such-model-anywhere", custom_llm_provider="openai", usage=usage) is None + + def test_token_type_cost_breakdown_zero_without_special_tokens(_local_model_cost_map): usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150) diff --git a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py index 6d3ef2ffea9..9847c4092df 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py @@ -813,9 +813,7 @@ async def _estimate(mock_router: MagicMock | None, model: str = AN_ALIAS, **over request = CostEstimateRequest( model=model, - input_tokens=INPUT_TOKENS, - output_tokens=OUTPUT_TOKENS, - **overrides, + **{"input_tokens": INPUT_TOKENS, "output_tokens": OUTPUT_TOKENS, **overrides}, ) with patch( # test-quality-ok: proxy_server module global is the endpoint's only injection point "litellm.proxy.proxy_server.llm_router", mock_router @@ -1062,6 +1060,54 @@ class TestEstimateCostCacheAndReasoningTokens: assert response.cache_read_input_token_cost == pytest.approx(5e-7) assert response.cache_creation_input_token_cost == pytest.approx(6.25e-6) + @pytest.mark.asyncio + async def test_a_tiered_model_reports_the_rates_its_lines_were_billed_at(self, monkeypatch): + """Above a token tier the calculator bills every line at the tier's rate, so the reported + rates must be the tier's too: each line equals its token count times the rate next to it.""" + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + { + "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, + "input_cost_per_token_above_200k_tokens": 6e-6, + "output_cost_per_token_above_200k_tokens": 3e-5, + "cache_read_input_token_cost_above_200k_tokens": 6e-7, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-6, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + + response = await _estimate( + None, + model=A_MAPPED_MODEL, + input_tokens=250_000, + cache_read_input_tokens=200_000, + cache_creation_input_tokens=10_000, + output_tokens=1_000, + reasoning_tokens=200, + ) + + assert response.input_cost_per_token == pytest.approx(6e-6) + assert response.output_cost_per_token == pytest.approx(3e-5) + assert response.cache_read_input_token_cost == pytest.approx(6e-7) + assert response.cache_creation_input_token_cost == pytest.approx(7.5e-6) + assert response.output_cost_per_reasoning_token == pytest.approx(3e-5) + assert response.cache_read_cost_per_request == pytest.approx(200_000 * response.cache_read_input_token_cost) + assert response.cache_creation_cost_per_request == pytest.approx( + 10_000 * response.cache_creation_input_token_cost + ) + assert response.reasoning_cost_per_request == pytest.approx(200 * response.output_cost_per_reasoning_token) + assert response.input_cost_per_request == pytest.approx( + 40_000 * response.input_cost_per_token + + response.cache_read_cost_per_request + + response.cache_creation_cost_per_request + ) + assert response.output_cost_per_request == pytest.approx(1_000 * response.output_cost_per_token) + class TestCostEstimateRequestTokenSubsets: def test_cache_tokens_beyond_the_input_tokens_are_rejected(self): diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6a9c86cc1b5..46cb5ad892a 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -26523,7 +26523,10 @@ export interface components { * @description Input token cost per request (before margin) */ input_cost_per_request: number; - /** Input Cost Per Token */ + /** + * Input Cost Per Token + * @description Rate billed per input token + */ input_cost_per_token?: number | null; /** Input Tokens */ input_tokens: number; @@ -26584,7 +26587,10 @@ export interface components { * @description Output token cost per request (before margin) */ output_cost_per_request: number; - /** Output Cost Per Token */ + /** + * Output Cost Per Token + * @description Rate billed per output token + */ output_cost_per_token?: number | null; /** Output Tokens */ output_tokens: number; From 0710231acc349f1cb8229fb5d691678dc2402e80 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:57:09 -0700 Subject: [PATCH 012/105] feat(cost_map): stamp and surface generated_at and source revision provenance The cost map JSON now carries a top-level `_metadata` block with `generated_at` and `source_revision`, written by the two bot writers only when model data changed. The loader pops it before the map becomes `litellm.model_cost`, records it next to the fetch ETag, and `/reload/model_cost_map`, `/model/cost_map/source`, and the reload schedule status return it. The Price Data Reload card shows the stamp, the ETag, and when the pod loaded the map. The schema and the cost map guard treat `_metadata` as a non-model root key --- ...to_update_price_and_context_window_file.py | 27 +++- ci_cd/cost_map_guard.py | 7 +- ci_cd/generate_model_prices_schema.py | 19 ++- .../litellm_core_utils/get_model_cost_map.py | 82 ++++++++++- ...odel_prices_and_context_window_backup.json | 4 + litellm/proxy/proxy_server.py | 15 +- model_prices_and_context_window.json | 4 + model_prices_and_context_window.schema.json | 20 ++- scripts/sync_together_ai_models.py | 23 +++- .../test_get_model_cost_map.py | 129 +++++++++++++++++- .../test_routes_model_cost_map.py | 83 ++++++++++- ...to_update_price_and_context_window_file.py | 54 ++++++++ tests/test_litellm/test_cost_map_guard.py | 20 +++ .../test_litellm/test_model_prices_schema.py | 19 +++ .../test_sync_together_ai_models.py | 53 +++++++ .../src/components/price_data_reload.test.tsx | 34 +++++ .../src/components/price_data_reload.tsx | 68 +++++++-- ui/litellm-dashboard/src/lib/http/schema.d.ts | 3 + 18 files changed, 636 insertions(+), 28 deletions(-) create mode 100644 tests/test_litellm/test_auto_update_price_and_context_window_file.py diff --git a/.github/scripts/auto_update_price_and_context_window_file.py b/.github/scripts/auto_update_price_and_context_window_file.py index 461d8d347d9..a7a3194f262 100644 --- a/.github/scripts/auto_update_price_and_context_window_file.py +++ b/.github/scripts/auto_update_price_and_context_window_file.py @@ -1,6 +1,9 @@ import asyncio import aiohttp import json +import os +import subprocess +from datetime import datetime, timezone # Asynchronously fetch data from a given URL async def fetch_data(url): @@ -31,13 +34,28 @@ def sync_local_data_with_remote(local_data, remote_data): for key in (set(remote_data) - set(local_data)): local_data[key] = remote_data[key] +def utc_now_iso(): + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + +def source_revision(): + from_env = os.environ.get("GITHUB_SHA") + if from_env: + return from_env + return subprocess.run(["git", "rev-parse", "HEAD"], check=True, capture_output=True, text=True).stdout.strip() + + +def stamp_metadata(data, generated_at, revision): + return {**data, "_metadata": {"generated_at": generated_at, "source_revision": revision}} + + # Write data to the json file def write_to_file(file_path, data): try: # Open the file in write mode with open(file_path, "w") as file: # Dump the data as JSON into the file - json.dump(data, file, indent=4) + file.write(json.dumps(data, indent=4) + "\n") print("Values updated successfully.") except Exception as e: # Print an error message if writing to file fails @@ -149,8 +167,13 @@ def main(): # If both local and openrouter data are available, synchronize and save if local_data and all_remote_data: + before = json.dumps(local_data, sort_keys=True) sync_local_data_with_remote(local_data, all_remote_data) - write_to_file(local_file_path, local_data) + changed = json.dumps(local_data, sort_keys=True) != before + write_to_file( + local_file_path, + stamp_metadata(local_data, utc_now_iso(), source_revision()) if changed else local_data, + ) else: print("Failed to fetch model data from either local file or URL.") diff --git a/ci_cd/cost_map_guard.py b/ci_cd/cost_map_guard.py index 50aa40ba220..351c06c74eb 100644 --- a/ci_cd/cost_map_guard.py +++ b/ci_cd/cost_map_guard.py @@ -2,7 +2,8 @@ Every pull request gets the file checks: the three cost map files parse, the backup copy matches the root file, and the JSON schema is in sync and validates the map. Pull requests from the cost map sync bot (branches named -litellm_cost_map_sync_*) additionally may only touch those three files and may only add or update models. +litellm_cost_map_sync_*) additionally may only touch those three files and may only add or update models, plus +restamp the _metadata provenance block. """ from __future__ import annotations @@ -15,7 +16,7 @@ from collections.abc import Sequence from dataclasses import dataclass from typing import Final -from generate_model_prices_schema import SPECIAL_ROOT_KEYS, build_schema, render, validation_errors +from generate_model_prices_schema import BOT_LOCKED_ROOT_KEYS, build_schema, render, validation_errors COST_MAP_PATH: Final = "model_prices_and_context_window.json" BACKUP_PATH: Final = "litellm/model_prices_and_context_window_backup.json" @@ -102,7 +103,7 @@ def _bot_failures(base: Snapshot, head_map: CostMap, changed_files: Sequence[str *(f"bot PRs may not remove fields: {ref}" for ref in removed_fields), *( f"bot PRs may not change {key}" - for key in sorted(SPECIAL_ROOT_KEYS) + for key in sorted(BOT_LOCKED_ROOT_KEYS) if base_map.get(key) != head_map.get(key) ), ) diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py index ab29b70bdd4..557afa50128 100644 --- a/ci_cd/generate_model_prices_schema.py +++ b/ci_cd/generate_model_prices_schema.py @@ -11,7 +11,9 @@ REPO_ROOT = Path(__file__).parent.parent PRICES_PATH = REPO_ROOT / "model_prices_and_context_window.json" SCHEMA_PATH = REPO_ROOT / "model_prices_and_context_window.schema.json" -SPECIAL_ROOT_KEYS = frozenset({"sample_spec", "fallback_generalizations"}) +METADATA_KEY = "_metadata" +SPECIAL_ROOT_KEYS = frozenset({"sample_spec", "fallback_generalizations", METADATA_KEY}) +BOT_LOCKED_ROOT_KEYS = SPECIAL_ROOT_KEYS - {METADATA_KEY} JsonSchema = dict @@ -271,13 +273,26 @@ def build_schema(prices: dict) -> JsonSchema: "description": ( "Schema for LiteLLM's model price and context window registry " "(https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). " - "Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, " + "Every top-level key except '_metadata', 'sample_spec', and 'fallback_generalizations' is a model id, " "optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. " "All costs are USD per unit. New optional fields are added regularly, so consumers should " "ignore unknown fields rather than reject them." ), "type": "object", "properties": { + METADATA_KEY: { + "type": "object", + "description": ( + "Provenance of this file: when an automated sync last regenerated it and the commit it " + "ran against. Human edits leave it untouched; not a model entry." + ), + "properties": { + "generated_at": {"type": "string", "format": "date-time"}, + "source_revision": STRING, + }, + "required": ["generated_at", "source_revision"], + "additionalProperties": False, + }, "sample_spec": { "type": "object", "description": ( diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index ba8738c8de0..a538e7cb330 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -20,6 +20,8 @@ from importlib.resources import files from typing import Final, Protocol import httpx +from pydantic import BaseModel, ConfigDict, ValidationError +from typing_extensions import ReadOnly, TypedDict from litellm import verbose_logger from litellm.constants import ( @@ -31,10 +33,11 @@ from litellm.litellm_core_utils.fallback_generalizations import ( ) FALLBACK_GENERALIZATIONS_KEY: Final = "fallback_generalizations" +METADATA_KEY: Final = "_metadata" # Reserved top-level keys that are not model entries. They must be excluded # from the model-count integrity check so a real upstream shrink can't be masked. -RESERVED_TOP_LEVEL_KEYS: Final = frozenset({"sample_spec", FALLBACK_GENERALIZATIONS_KEY}) +RESERVED_TOP_LEVEL_KEYS: Final = frozenset({"sample_spec", FALLBACK_GENERALIZATIONS_KEY, METADATA_KEY}) def _count_model_entries(model_cost: dict) -> int: @@ -166,6 +169,7 @@ MODEL_COST_MAP_FETCH_MAX_WAIT_SECONDS: Final = 30.0 @dataclass(frozen=True, slots=True) class ModelCostMapReloaded: model_cost_map: dict # mutable-ok: adopted as litellm.model_cost, whose consumer contract is a plain mutable dict + etag: str | None = None @dataclass(frozen=True, slots=True) @@ -254,7 +258,7 @@ def _classify_fetch_response(response: httpx.Response, url: str) -> _FetchAttemp return ModelCostMapReloadUnavailable(reason=f"invalid JSON from {url}: {e}") if not isinstance(parsed, dict): return ModelCostMapReloadUnavailable(reason=f"expected a JSON object from {url}, got {type(parsed).__name__}") - return ModelCostMapReloaded(model_cost_map=parsed) + return ModelCostMapReloaded(model_cost_map=parsed, etag=response.headers.get("etag")) def _next_retry_wait( @@ -328,10 +332,12 @@ async def refetch_model_cost_map( map they already have. """ if os.getenv("LITELLM_LOCAL_MODEL_COST_MAP", "").lower() == "true": + _cost_map_source_info.loaded_at = datetime.now(timezone.utc) _cost_map_source_info.source = "local" _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True _cost_map_source_info.fallback_reason = None + _cost_map_source_info.etag = None return ModelCostMapReloaded( model_cost_map=_finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) ) @@ -355,11 +361,13 @@ async def refetch_model_cost_map( backup_model_count=GetModelCostMap._get_backup_model_count(), ): return ModelCostMapReloadUnavailable(reason=f"model cost map from {url} failed integrity validation") + _cost_map_source_info.loaded_at = datetime.now(timezone.utc) _cost_map_source_info.source = "remote" _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False _cost_map_source_info.fallback_reason = None - return ModelCostMapReloaded(model_cost_map=_finalize_model_cost_map(result.model_cost_map)) + _cost_map_source_info.etag = result.etag + return ModelCostMapReloaded(model_cost_map=_finalize_model_cost_map(result.model_cost_map), etag=result.etag) class ModelCostMapSourceInfo: @@ -370,13 +378,60 @@ class ModelCostMapSourceInfo: is_env_forced: bool = False fallback_reason: str | None = None loaded_at: "datetime | None" = None + generated_at: str | None = None + source_revision: str | None = None + etag: str | None = None # Module-level singleton tracking the source of the current cost map _cost_map_source_info: Final = ModelCostMapSourceInfo() -def get_model_cost_map_source_info() -> dict: +class CostMapMetadata(BaseModel): + model_config = ConfigDict(frozen=True, extra="ignore") + + generated_at: str | None = None + source_revision: str | None = None + + +_EMPTY_METADATA: Final = CostMapMetadata() + + +def _parse_metadata(raw: object) -> CostMapMetadata: + if raw is None: + return _EMPTY_METADATA + try: + return CostMapMetadata.model_validate(raw) + except ValidationError as error: + verbose_logger.warning("LiteLLM: ignoring a malformed %s block in the model cost map: %s", METADATA_KEY, error) + return _EMPTY_METADATA + + +class CostMapProvenance(TypedDict): + generated_at: ReadOnly[str | None] + source_revision: ReadOnly[str | None] + etag: ReadOnly[str | None] + + +class CostMapSourceInfo(CostMapProvenance): + source: ReadOnly[str] + url: ReadOnly[str | None] + is_env_forced: ReadOnly[bool] + fallback_reason: ReadOnly[str | None] + loaded_at: ReadOnly[str | None] + + +def get_model_cost_map_provenance() -> CostMapProvenance: + """Which revision of the cost map this process serves: the ``_metadata`` stamp the file + carries plus the ETag the remote fetch returned (None for the bundled backup)""" + return { + "generated_at": _cost_map_source_info.generated_at, + "source_revision": _cost_map_source_info.source_revision, + "etag": _cost_map_source_info.etag, + } + + +def get_model_cost_map_source_info() -> CostMapSourceInfo: """ Return metadata about where the current model cost map was loaded from. @@ -385,12 +440,20 @@ def get_model_cost_map_source_info() -> dict: - url: the remote URL attempted (or None for local-only) - is_env_forced: True if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage - fallback_reason: human-readable reason if remote failed and local was used + - loaded_at: ISO 8601 time this process last loaded the map + - generated_at, source_revision: the ``_metadata`` stamp inside the loaded file + - etag: the ETag of the remote fetch (None for the bundled backup) """ + loaded_at: Final = _cost_map_source_info.loaded_at return { "source": _cost_map_source_info.source, "url": _cost_map_source_info.url, "is_env_forced": _cost_map_source_info.is_env_forced, "fallback_reason": _cost_map_source_info.fallback_reason, + "loaded_at": loaded_at.isoformat() if loaded_at is not None else None, + "generated_at": _cost_map_source_info.generated_at, + "source_revision": _cost_map_source_info.source_revision, + "etag": _cost_map_source_info.etag, } @@ -455,14 +518,18 @@ def _expand_model_aliases(model_cost: dict) -> dict: def _finalize_model_cost_map(model_cost: dict) -> dict: - """Extract fallback generalizations out of the raw map, then expand aliases. + """Extract fallback generalizations and the provenance stamp out of the raw map, then expand aliases. The ``fallback_generalizations`` block is installed into the generalizations - module and removed from the map so it is never treated as a model entry. + module and the ``_metadata`` block into the source info; both are removed from + the map so neither is ever treated as a model entry. """ raw: Final = model_cost.pop(FALLBACK_GENERALIZATIONS_KEY, None) rules: Final = raw.get("rules") if isinstance(raw, dict) else None set_fallback_generalizations(rules) + metadata: Final = _parse_metadata(model_cost.pop(METADATA_KEY, None)) + _cost_map_source_info.generated_at = metadata.generated_at + _cost_map_source_info.source_revision = metadata.source_revision return _expand_model_aliases(model_cost) @@ -494,10 +561,12 @@ def get_model_cost_map( _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True _cost_map_source_info.fallback_reason = None + _cost_map_source_info.etag = None return _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False + _cost_map_source_info.etag = None result: Final = _fetch_remote_model_cost_map_with_retry_sync( url=url, @@ -533,4 +602,5 @@ def get_model_cost_map( _cost_map_source_info.source = "remote" _cost_map_source_info.fallback_reason = None + _cost_map_source_info.etag = result.etag return _finalize_model_cost_map(content) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b1ffc1583e4..5edb3c0e9d8 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1,4 +1,8 @@ { + "_metadata": { + "generated_at": "2026-09-07T23:38:47Z", + "source_revision": "cd681a573fd9f5b6f15a1355f46178e4e9d374d2" + }, "sample_spec": { "code_interpreter_cost_per_session": 0.0, "computer_use_input_cost_per_1k_tokens": 0.0, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 0915b8dd1b9..4741e4cd9d3 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -17749,6 +17749,7 @@ async def reload_model_cost_map( # Immediately reload the model cost map in the current pod from litellm.litellm_core_utils.get_model_cost_map import ( ModelCostMapReloadUnavailable, + get_model_cost_map_provenance, refetch_model_cost_map, ) @@ -17762,6 +17763,7 @@ async def reload_model_cost_map( models_count = _swap_in_model_cost_map(reload_result.model_cost_map) current_time = utc_now() proxy_config.model_cost_map_loaded_at = current_time + provenance: Final = get_model_cost_map_provenance() # Publish a new revision so every other pod reloads on its next poll; this pod has # already served it, so adopt it here rather than reloading again a tick later @@ -17776,6 +17778,7 @@ async def reload_model_cost_map( "status": "success", "models_count": models_count, "timestamp": current_time.isoformat(), + **provenance, } except HTTPException: raise @@ -17896,12 +17899,17 @@ async def get_model_cost_map_reload_status( try: global prisma_client + from litellm.litellm_core_utils.get_model_cost_map import ( + get_model_cost_map_provenance, + ) + provenance: Final = get_model_cost_map_provenance() if prisma_client is None: verbose_proxy_logger.info("No database connection, returning not scheduled") - return reload_schedule_status(None) + return {**reload_schedule_status(None), **provenance} - return reload_schedule_status(await read_reload_schedule(prisma_client, MODEL_COST_MAP_RELOAD_PARAM_NAME)) + schedule: Final = await read_reload_schedule(prisma_client, MODEL_COST_MAP_RELOAD_PARAM_NAME) + return {**reload_schedule_status(schedule), **provenance} except Exception as e: verbose_proxy_logger.exception("Failed to get model cost map reload status: %s", e) raise HTTPException( @@ -17929,6 +17937,9 @@ async def get_model_cost_map_source( - url: the remote URL that was attempted (null when env-forced local) - is_env_forced: true if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage - fallback_reason: human-readable reason why remote failed (null on success) + - loaded_at: when this pod last loaded the map + - generated_at, source_revision: the _metadata stamp inside the loaded file + - etag: the ETag of the remote fetch (null for the bundled backup) - model_count: number of models in the currently loaded cost map """ # Read-only source info — admin viewers can read. diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b1ffc1583e4..5edb3c0e9d8 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1,4 +1,8 @@ { + "_metadata": { + "generated_at": "2026-09-07T23:38:47Z", + "source_revision": "cd681a573fd9f5b6f15a1355f46178e4e9d374d2" + }, "sample_spec": { "code_interpreter_cost_per_session": 0.0, "computer_use_input_cost_per_1k_tokens": 0.0, diff --git a/model_prices_and_context_window.schema.json b/model_prices_and_context_window.schema.json index 47a1934a703..c40c2a67682 100644 --- a/model_prices_and_context_window.schema.json +++ b/model_prices_and_context_window.schema.json @@ -1,9 +1,27 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "LiteLLM model_prices_and_context_window.json", - "description": "Schema for LiteLLM's model price and context window registry (https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. All costs are USD per unit. New optional fields are added regularly, so consumers should ignore unknown fields rather than reject them.", + "description": "Schema for LiteLLM's model price and context window registry (https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). Every top-level key except '_metadata', 'sample_spec', and 'fallback_generalizations' is a model id, optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. All costs are USD per unit. New optional fields are added regularly, so consumers should ignore unknown fields rather than reject them.", "type": "object", "properties": { + "_metadata": { + "type": "object", + "description": "Provenance of this file: when an automated sync last regenerated it and the commit it ran against. Human edits leave it untouched; not a model entry.", + "properties": { + "generated_at": { + "type": "string", + "format": "date-time" + }, + "source_revision": { + "type": "string" + } + }, + "required": [ + "generated_at", + "source_revision" + ], + "additionalProperties": false + }, "sample_spec": { "type": "object", "description": "Documentation placeholder illustrating the entry shape; not a real model and not schema-conformant (several values are prose)." diff --git a/scripts/sync_together_ai_models.py b/scripts/sync_together_ai_models.py index 12b128890f1..e009f1a7ce6 100644 --- a/scripts/sync_together_ai_models.py +++ b/scripts/sync_together_ai_models.py @@ -19,9 +19,11 @@ import argparse import json import os import re +import subprocess import sys from collections.abc import Mapping, Sequence from dataclasses import dataclass, field +from datetime import datetime, timezone from pathlib import Path from types import MappingProxyType from typing import Final @@ -33,6 +35,7 @@ MODELS_URL: Final = "https://api.together.ai/v1/models?serverless" DEPRECATIONS_URL: Final = "https://docs.together.ai/docs/deprecations.md" PROVIDER: Final = "together_ai" PREFIX: Final = "together_ai/" +METADATA_KEY: Final = "_metadata" SOURCE_URL: Final = "https://docs.together.ai/docs/serverless-models" COST_MAP_RELPATHS: Final = ( "model_prices_and_context_window.json", @@ -495,6 +498,23 @@ def _serialize(cost_map: CostMap) -> str: return json.dumps(cost_map, indent=4, ensure_ascii=False) + "\n" +def stamp_metadata(cost_map: CostMap, generated_at: str, source_revision: str) -> CostMap: + return {**cost_map, METADATA_KEY: {"generated_at": generated_at, "source_revision": source_revision}} + + +def _utc_now_iso() -> str: + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + +def _source_revision(repo_root: Path) -> str: + from_env: Final = os.environ.get("GITHUB_SHA") + if from_env: + return from_env + return subprocess.run( + ("git", "rev-parse", "HEAD"), cwd=repo_root, check=True, capture_output=True, text=True + ).stdout.strip() + + def main(argv: Sequence[str]) -> int: parser: Final = argparse.ArgumentParser(description=__doc__) parser.add_argument("--write", action="store_true", help="apply the sync to the cost map files (default: dry run)") @@ -527,8 +547,9 @@ def main(argv: Sequence[str]) -> int: if args.pr_body_file is not None: args.pr_body_file.write_text(body) if args.write and outcome.has_changes: + stamped: Final = _serialize(stamp_metadata(outcome.cost_map, _utc_now_iso(), _source_revision(args.repo_root))) for relpath in COST_MAP_RELPATHS: - (args.repo_root / relpath).write_text(_serialize(outcome.cost_map)) + (args.repo_root / relpath).write_text(stamped) print(render_summary(outcome)) print() print(body) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 18185126775..62f72495491 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -17,9 +17,11 @@ from litellm.litellm_core_utils.fallback_generalizations import ( ) from litellm.litellm_core_utils.get_model_cost_map import ( FALLBACK_GENERALIZATIONS_KEY, + METADATA_KEY, GetModelCostMap, _count_model_entries, _finalize_model_cost_map, + get_model_cost_map_provenance, ) @@ -31,6 +33,20 @@ def _load_root_cost_map() -> dict: return json.load(f) +def _load_bundled_stamp() -> dict: + path = os.path.join( + os.path.dirname(__file__), "../../../litellm/model_prices_and_context_window_backup.json" + ) + with open(path) as f: + return json.load(f)[METADATA_KEY] + + +_STAMP = { + "generated_at": "2026-09-07T00:00:00Z", + "source_revision": "0123456789abcdef0123456789abcdef01234567", +} + + def _make_models(n: int) -> dict: return { f"model-{i}": {"litellm_provider": "openai", "mode": "chat"} for i in range(n) @@ -41,6 +57,7 @@ def test_count_model_entries_excludes_reserved_keys(): m = _make_models(3) m["sample_spec"] = {"foo": "bar"} m[FALLBACK_GENERALIZATIONS_KEY] = {"rules": []} + m[METADATA_KEY] = dict(_STAMP) assert _count_model_entries(m) == 3 @@ -126,6 +143,39 @@ def test_finalize_with_no_block_clears_rules(): set_fallback_generalizations(previous) +def test_finalize_pops_metadata_and_records_provenance(): + finalized = _finalize_model_cost_map({**_make_models(2), METADATA_KEY: dict(_STAMP)}) + + assert METADATA_KEY not in finalized + assert len(finalized) == 2 + provenance = get_model_cost_map_provenance() + assert provenance["generated_at"] == _STAMP["generated_at"] + assert provenance["source_revision"] == _STAMP["source_revision"] + + +def test_finalize_without_metadata_clears_the_previous_stamp(): + _finalize_model_cost_map({**_make_models(2), METADATA_KEY: dict(_STAMP)}) + + _finalize_model_cost_map(_make_models(2)) + + provenance = get_model_cost_map_provenance() + assert provenance["generated_at"] is None + assert provenance["source_revision"] is None + + +@pytest.mark.parametrize( + "raw", + ["2026-09-07T00:00:00Z", {"generated_at": 42}, ["2026-09-07T00:00:00Z"]], + ids=["string", "wrong_field_type", "list"], +) +def test_finalize_tolerates_a_malformed_metadata_block(raw): + finalized = _finalize_model_cost_map({**_make_models(2), METADATA_KEY: raw}) + + assert METADATA_KEY not in finalized + assert len(finalized) == 2 + assert get_model_cost_map_provenance()["generated_at"] is None + + def test_shipped_backup_carries_the_claude_routing_rules(): """The bundled backup must ship the Claude routing rules so a fresh install (or an offline fallback) routes unknown Claude models without code changes. @@ -340,6 +390,10 @@ def _real_map_bytes() -> bytes: return json.dumps(_load_root_cost_map()).encode() +def _stamped_map_bytes(stamp: dict) -> bytes: + return json.dumps({**_load_root_cost_map(), METADATA_KEY: stamp}).encode() + + class _SleepRecorder: """Injected in place of asyncio.sleep so tests assert waits without real delay.""" @@ -500,6 +554,43 @@ async def test_refetch_respects_local_env_override(monkeypatch): assert len(result.model_cost_map) > 100 +@pytest.mark.asyncio +async def test_refetch_records_the_file_stamp_and_the_fetch_etag(): + """A reload reports which revision of the map it swapped in: the ``_metadata`` stamp the file + carries plus the ETag the fetch returned, with the stamp itself kept out of the model map.""" + client, _ = _mock_client( + [httpx.Response(200, headers={"ETag": 'W/"abc123"'}, content=_stamped_map_bytes(_STAMP))] + ) + + result = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) + + assert isinstance(result, ModelCostMapReloaded) + assert result.etag == 'W/"abc123"' + assert METADATA_KEY not in result.model_cost_map + assert get_model_cost_map_provenance() == { + "generated_at": _STAMP["generated_at"], + "source_revision": _STAMP["source_revision"], + "etag": 'W/"abc123"', + } + + +@pytest.mark.asyncio +async def test_refetch_local_override_reports_the_bundled_stamp_without_an_etag(monkeypatch): + """Forcing the bundled backup after a remote reload must drop the remote ETag, since the map + served is no longer the one that ETag identifies.""" + remote, _ = _mock_client( + [httpx.Response(200, headers={"ETag": 'W/"remote"'}, content=_stamped_map_bytes(_STAMP))] + ) + await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=remote) + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + + result = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0)) + + assert isinstance(result, ModelCostMapReloaded) + assert METADATA_KEY not in result.model_cost_map + assert get_model_cost_map_provenance() == {**_load_bundled_stamp(), "etag": None} + + # --------------------------------------------------------------------------- # get_model_cost_map: the boot-time load retries transient failures like a reload does # --------------------------------------------------------------------------- @@ -542,7 +633,7 @@ def test_boot_load_retries_transient_failures_instead_of_falling_back(): source = get_model_cost_map_source_info() assert source["source"] == "remote" assert source["fallback_reason"] is None - assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} + assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY, METADATA_KEY} def test_boot_load_honors_retry_after_then_falls_back_after_max_attempts(): @@ -592,3 +683,39 @@ def test_boot_load_respects_local_env_override(monkeypatch): ) assert len(cost_map) > 100 assert get_model_cost_map_source_info()["is_env_forced"] is True + + +def test_boot_load_records_the_file_stamp_and_the_fetch_etag(): + client, _ = _mock_client( + [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_stamped_map_bytes(_STAMP))], + client_cls=httpx.Client, + ) + + cost_map = get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=client) + + assert METADATA_KEY not in cost_map + source = get_model_cost_map_source_info() + assert source["source"] == "remote" + assert source["etag"] == 'W/"boot"' + assert source["generated_at"] == _STAMP["generated_at"] + assert source["source_revision"] == _STAMP["source_revision"] + assert source["loaded_at"] is not None + + +def test_boot_load_fallback_to_the_backup_drops_the_remote_etag(): + """A boot that lands on the bundled backup reports the backup's own stamp and no ETag, even + when an earlier load in the same process had fetched the remote map.""" + remote, _ = _mock_client( + [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_stamped_map_bytes(_STAMP))], + client_cls=httpx.Client, + ) + get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=remote) + failing, _ = _mock_client([httpx.Response(404)], client_cls=httpx.Client) + + cost_map = get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=failing) + + assert METADATA_KEY not in cost_map + source = get_model_cost_map_source_info() + assert source["source"] == "local" + assert source["etag"] is None + assert {"generated_at": source["generated_at"], "source_revision": source["source_revision"]} == _load_bundled_stamp() diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py index b75ee1caccf..fb3583a7dd2 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py @@ -11,6 +11,7 @@ Routes covered: from __future__ import annotations import json +from pathlib import Path from unittest.mock import AsyncMock, MagicMock @@ -20,6 +21,13 @@ from .conftest import VOLATILE_KEYS, normalize # dict-equality assertions remain stable. _VOLATILE = VOLATILE_KEYS | frozenset({"timestamp"}) +_PROVENANCE = { + "generated_at": "2026-09-07T00:00:00Z", + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "etag": 'W/"cost-map-etag"', +} +_ROOT_COST_MAP = Path(__file__).resolve().parents[4] / "model_prices_and_context_window.json" + # --------------------------------------------------------------------------- # Helpers @@ -42,6 +50,14 @@ def _attach_litellm_config(mock_prisma): return table +def _pin_provenance(monkeypatch): + """Fix what this process reports as its cost map revision, independent of the map loaded at import.""" + monkeypatch.setattr( + "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_provenance", + lambda: dict(_PROVENANCE), + ) + + # --------------------------------------------------------------------------- # POST /reload/model_cost_map # --------------------------------------------------------------------------- @@ -55,6 +71,7 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): table = _attach_litellm_config(mock_prisma) monkeypatch.setattr(ps, "prisma_client", mock_prisma) + _pin_provenance(monkeypatch) fake_cost_map = {"gpt-4": {"input_cost": 0.03}, "gpt-3.5": {"input_cost": 0.002}} monkeypatch.setattr( @@ -83,6 +100,7 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): "status": "success", "models_count": 2, "timestamp": "", + **_PROVENANCE, } assert table.upsert.await_count == 1 update_payload = table.upsert.await_args.kwargs["data"]["update"] @@ -90,6 +108,57 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): assert update_payload["reload_revision"] == {"increment": 1} +def test_reload_model_cost_map_surfaces_provenance_and_keeps_metadata_out_of_the_model_list( + client, auth_as, monkeypatch, mock_prisma +): + """A real refetch through the reload route reports the file's stamp and the fetch ETag on every + status surface, while the ``_metadata`` block never shows up as a model anywhere.""" + import httpx + + import litellm + from litellm.proxy import proxy_server as ps + from litellm.proxy._types import LitellmUserRoles + + _attach_litellm_config(mock_prisma) + monkeypatch.setattr(ps, "prisma_client", mock_prisma) + monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) + stamped = {**json.loads(_ROOT_COST_MAP.read_text()), "_metadata": {k: v for k, v in _PROVENANCE.items() if k != "etag"}} + served = httpx.Response(200, headers={"ETag": _PROVENANCE["etag"]}, content=json.dumps(stamped).encode()) + monkeypatch.setattr( + "litellm.litellm_core_utils.get_model_cost_map._default_reload_client", + lambda: httpx.AsyncClient(transport=httpx.MockTransport(lambda request: served)), + ) + monkeypatch.setattr("litellm.add_known_models", lambda model_cost_map=None: None) + monkeypatch.setattr("litellm.model_cost", {}, raising=False) + + async def _fake_invalidate(name): + return None + + monkeypatch.setattr(ps, "invalidate_config_param", _fake_invalidate) + + with auth_as(LitellmUserRoles.PROXY_ADMIN): + reload_response = client.post("/reload/model_cost_map") + source_response = client.get("/model/cost_map/source") + status_response = client.get("/schedule/model_cost_map_reload/status") + public_response = client.get("/public/litellm_model_cost_map") + + assert reload_response.status_code == 200 + reload_body = reload_response.json() + assert {key: reload_body[key] for key in _PROVENANCE} == _PROVENANCE + assert source_response.status_code == 200 + source_body = source_response.json() + assert {key: source_body[key] for key in _PROVENANCE} == _PROVENANCE + assert source_body["source"] == "remote" + assert status_response.status_code == 200 + assert {key: status_response.json()[key] for key in _PROVENANCE} == _PROVENANCE + assert public_response.status_code == 200 + public_body = public_response.json() + assert "_metadata" not in public_body + assert "_metadata" not in litellm.model_cost + assert "gpt-4o" in public_body + assert reload_body["models_count"] == len(litellm.model_cost) + + def test_reload_model_cost_map_fetch_failure_502_keeps_map( client, auth_as, monkeypatch, mock_prisma ): @@ -270,11 +339,12 @@ def test_cancel_model_cost_map_reload_no_db_500(client, auth_as, monkeypatch): def test_get_model_cost_map_reload_status_no_db_not_scheduled( client, auth_as, monkeypatch ): - """No prisma client → returns the not-scheduled shape (4 keys, all-null).""" + """No prisma client → returns the not-scheduled shape (all-null) plus the cost map provenance.""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles monkeypatch.setattr(ps, "prisma_client", None) + _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") assert response.status_code == 200 @@ -283,6 +353,7 @@ def test_get_model_cost_map_reload_status_no_db_not_scheduled( "interval_hours": None, "last_run": None, "next_run": None, + **_PROVENANCE, } @@ -300,6 +371,7 @@ def test_get_model_cost_map_reload_status_scheduled( config_row.last_run_at = None table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) + _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -309,6 +381,7 @@ def test_get_model_cost_map_reload_status_scheduled( "interval_hours": 12, "last_run": None, "next_run": None, + **_PROVENANCE, } @@ -328,6 +401,7 @@ def test_get_model_cost_map_reload_status_reports_persisted_last_run( config_row.last_run_at = datetime(2024, 1, 1, 6, 0, 0, tzinfo=timezone.utc) table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) + _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -337,6 +411,7 @@ def test_get_model_cost_map_reload_status_reports_persisted_last_run( "interval_hours": 6, "last_run": "2024-01-01T06:00:00+00:00", "next_run": "2024-01-01T12:00:00+00:00", + **_PROVENANCE, } @@ -356,6 +431,7 @@ def test_get_model_cost_map_reload_status_no_config_not_scheduled( config_row.last_run_at = None table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) + _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -365,6 +441,7 @@ def test_get_model_cost_map_reload_status_no_config_not_scheduled( "interval_hours": None, "last_run": None, "next_run": None, + **_PROVENANCE, } @@ -391,6 +468,8 @@ def test_get_model_cost_map_source_happy(client, auth_as, monkeypatch): "url": "https://example.invalid/cost_map.json", "is_env_forced": False, "fallback_reason": None, + "loaded_at": "2026-09-07T01:02:03+00:00", + **_PROVENANCE, } monkeypatch.setattr( "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_source_info", @@ -406,6 +485,8 @@ def test_get_model_cost_map_source_happy(client, auth_as, monkeypatch): "url": "https://example.invalid/cost_map.json", "is_env_forced": False, "fallback_reason": None, + "loaded_at": "2026-09-07T01:02:03+00:00", + **_PROVENANCE, "model_count": 3, } diff --git a/tests/test_litellm/test_auto_update_price_and_context_window_file.py b/tests/test_litellm/test_auto_update_price_and_context_window_file.py new file mode 100644 index 00000000000..d3cda09cd96 --- /dev/null +++ b/tests/test_litellm/test_auto_update_price_and_context_window_file.py @@ -0,0 +1,54 @@ +"""Tests for .github/scripts/auto_update_price_and_context_window_file.py.""" + +import importlib.util +import json +import re +import sys +from pathlib import Path +from typing import Final + +_REPO_ROOT: Final = Path(__file__).resolve().parents[2] +_MODULE_PATH: Final = _REPO_ROOT / ".github" / "scripts" / "auto_update_price_and_context_window_file.py" +_spec: Final = importlib.util.spec_from_file_location("auto_update_price_and_context_window_file", _MODULE_PATH) +script: Final = importlib.util.module_from_spec(_spec) +sys.modules[_spec.name] = script +_spec.loader.exec_module(script) + +_LOCAL_FILE: Final = "model_prices_and_context_window.json" +_GENERATED_AT: Final = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z") + + +def _openrouter_row(model_id: str) -> dict: + return {"id": model_id, "context_length": 8192, "pricing": {"prompt": "0.000001", "completion": "0.000002"}} + + +def _serve(openrouter_rows: list) -> object: + async def fetch_data(url: str) -> list: + return openrouter_rows if "openrouter" in url else [] + + return fetch_data + + +def _read_local(tmp_path: Path) -> dict: + return json.loads((tmp_path / _LOCAL_FILE).read_text()) + + +def test_main_stamps_provenance_only_when_the_sync_changed_the_file(tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + monkeypatch.setenv("GITHUB_SHA", "feedface") + monkeypatch.setattr(script, "fetch_data", _serve([_openrouter_row("acme/x")])) + (tmp_path / _LOCAL_FILE).write_text(json.dumps({"sample_spec": {"input_cost_per_token": "USD"}}, indent=4) + "\n") + + script.main() + + written = _read_local(tmp_path) + assert written["openrouter/acme/x"]["litellm_provider"] == "openrouter" + assert written["_metadata"]["source_revision"] == "feedface" + assert _GENERATED_AT.fullmatch(written["_metadata"]["generated_at"]) + + sentinel = {**written, "_metadata": {**written["_metadata"], "generated_at": "2000-01-01T00:00:00Z"}} + (tmp_path / _LOCAL_FILE).write_text(json.dumps(sentinel, indent=4) + "\n") + + script.main() + + assert _read_local(tmp_path) == sentinel diff --git a/tests/test_litellm/test_cost_map_guard.py b/tests/test_litellm/test_cost_map_guard.py index 1b4330ed62c..1a60cf81164 100644 --- a/tests/test_litellm/test_cost_map_guard.py +++ b/tests/test_litellm/test_cost_map_guard.py @@ -141,6 +141,26 @@ def test_bot_may_not_change_special_root_keys() -> None: assert _failures(head) == ("bot PRs may not change fallback_generalizations",) +STAMP: Final = {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef0123456789abcdef01234567"} + + +def test_bot_may_stamp_and_restamp_metadata() -> None: + stamped = _snapshot({**BASE_MAP, "_metadata": STAMP}) + assert _failures(stamped) == () + assert _failures(stamped, bot=False) == () + + restamped = _snapshot( + { + **BASE_MAP, + "_metadata": {**STAMP, "generated_at": "2026-09-14T00:00:00Z"}, + "fallback_generalizations": {"rules": []}, + } + ) + assert guard.guard_failures(stamped, restamped, MAP_FILES, True) == ( + "bot PRs may not change fallback_generalizations", + ) + + def _commit(repo: Path, cost_map: dict[str, object], message: str) -> str: text = _serialize(cost_map) (repo / guard.COST_MAP_PATH).write_text(text) diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index c2c22c25998..3517f5840e8 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -98,6 +98,25 @@ def test_schema_rejects_malformed_entries(committed_schema: dict, entry: dict): assert not validator.is_valid({"some-model": entry}) +@pytest.mark.parametrize( + "metadata", + [ + "2026-09-07T00:00:00Z", + {"generated_at": "2026-09-07T00:00:00Z"}, + {"source_revision": "0123456789abcdef"}, + {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef", "author": "bot"}, + ], + ids=["not_an_object", "missing_revision", "missing_generated_at", "unknown_field"], +) +def test_schema_rejects_a_malformed_metadata_block(committed_schema: dict, metadata: object): + assert not build_validator(committed_schema).is_valid({"_metadata": metadata}) + + +def test_schema_accepts_the_provenance_stamp_as_a_non_model_root_key(committed_schema: dict): + stamp = {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef0123456789abcdef01234567"} + assert build_validator(committed_schema).is_valid({"_metadata": stamp}) + + def test_schema_accepts_minimal_and_unknown_optional_fields(committed_schema: dict): validator = build_validator(committed_schema) assert validator.is_valid({"some-model": {"litellm_provider": "openai"}}) diff --git a/tests/test_litellm/test_sync_together_ai_models.py b/tests/test_litellm/test_sync_together_ai_models.py index b8a85bcfbdc..f58f573c208 100644 --- a/tests/test_litellm/test_sync_together_ai_models.py +++ b/tests/test_litellm/test_sync_together_ai_models.py @@ -1,5 +1,6 @@ import importlib.util import json +import re from pathlib import Path from types import MappingProxyType @@ -369,6 +370,58 @@ def test_sync_is_idempotent_over_the_repo_cost_map() -> None: assert second.cost_map == first.cost_map +def test_stamp_metadata_adds_the_provenance_block_without_touching_models() -> None: + cost_map = {"sample_spec": {"input_cost_per_token": "USD"}, "together_ai/acme/x": {"mode": "chat"}} + + stamped = sync.stamp_metadata(cost_map, "2026-09-07T00:00:00Z", "feedface") + + assert stamped["_metadata"] == {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "feedface"} + assert {key: value for key, value in stamped.items() if key != "_metadata"} == cost_map + assert "_metadata" not in cost_map + + +def _write_registry(repo_root: Path, cost_map: dict) -> None: + for relpath in sync.COST_MAP_RELPATHS: + target = repo_root / relpath + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(cost_map, indent=4) + "\n") + + +def _read_registries(repo_root: Path) -> tuple[dict, ...]: + return tuple(json.loads((repo_root / relpath).read_text()) for relpath in sync.COST_MAP_RELPATHS) + + +def test_write_stamps_provenance_into_both_files_only_when_the_sync_changed_them(tmp_path: Path, monkeypatch) -> None: + monkeypatch.setenv("GITHUB_SHA", "feedface") + cost_map = json.loads((ROOT / "model_prices_and_context_window.json").read_text()) + dropped = next(f"together_ai/{model.id}" for model in RECORDED_CATALOG if f"together_ai/{model.id}" in cost_map) + _write_registry(tmp_path, {key: value for key, value in cost_map.items() if key not in {dropped, "_metadata"}}) + argv = ( + "--write", + "--models-json", + str(FIXTURES / "models_serverless.json"), + "--deprecations-md", + str(FIXTURES / "deprecations.md"), + "--repo-root", + str(tmp_path), + ) + + assert sync.main(argv) == 0 + + written = _read_registries(tmp_path) + assert written[0] == written[1] + assert dropped in written[0] + assert written[0]["_metadata"]["source_revision"] == "feedface" + assert re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", written[0]["_metadata"]["generated_at"]) + + sentinel = {**written[0], "_metadata": {**written[0]["_metadata"], "generated_at": "2000-01-01T00:00:00Z"}} + _write_registry(tmp_path, sentinel) + + assert sync.main(argv) == 0 + + assert _read_registries(tmp_path) == (sentinel, sentinel) + + def test_pr_body_lists_every_section_and_the_skipped_types() -> None: outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) body = sync.render_pr_body(outcome) diff --git a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx index 01381df1620..a85211f498c 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx @@ -32,8 +32,18 @@ const remoteSource = { url: "https://pricing.example.test/model_prices.json", is_env_forced: false, fallback_reason: null, + loaded_at: null, + generated_at: null, + source_revision: null, + etag: null, model_count: 1234, }; +const provenance = { + loaded_at: "2026-09-07T10:00:00Z", + generated_at: "2026-09-06T23:38:47Z", + source_revision: "cd681a573fd9f5b6f15a1355f46178e4e9d374d2", + etag: 'W/"eb8e9a53f4cc284b"', +}; describe("PriceDataReload", () => { beforeEach(() => { @@ -51,6 +61,30 @@ describe("PriceDataReload", () => { expect(screen.getByText("No periodic reload scheduled")).toBeInTheDocument(); }); + it("shows which revision of the cost map is loaded when the source reports one", async () => { + vi.mocked(getModelCostMapSource).mockResolvedValue({ ...remoteSource, ...provenance } as never); + render(); + + expect(await screen.findByText("Source revision:")).toBeInTheDocument(); + expect(screen.getByText("cd681a573fd9")).toBeInTheDocument(); + expect(screen.getByText("ETag:")).toBeInTheDocument(); + expect(screen.getByText('W/"eb8e9a53f4cc284b"')).toBeInTheDocument(); + expect(screen.getByText("Generated at:")).toBeInTheDocument(); + expect(screen.getByText(new Date(provenance.generated_at).toLocaleString())).toBeInTheDocument(); + expect(screen.getByText("Loaded at:")).toBeInTheDocument(); + expect(screen.getByText(new Date(provenance.loaded_at).toLocaleString())).toBeInTheDocument(); + }); + + it("hides the provenance rows when the loaded map carries no stamp", async () => { + render(); + + expect(await screen.findByText("Pricing Data Source")).toBeInTheDocument(); + expect(screen.queryByText("Generated at:")).not.toBeInTheDocument(); + expect(screen.queryByText("Source revision:")).not.toBeInTheDocument(); + expect(screen.queryByText("ETag:")).not.toBeInTheDocument(); + expect(screen.queryByText("Loaded at:")).not.toBeInTheDocument(); + }); + it("confirms an immediate reload and refreshes dependent data", async () => { const user = userEvent.setup(); const onReloadSuccess = vi.fn(); diff --git a/ui/litellm-dashboard/src/components/price_data_reload.tsx b/ui/litellm-dashboard/src/components/price_data_reload.tsx index 1c6801eede2..3bb70072937 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.tsx @@ -49,9 +49,17 @@ interface CostMapSourceInfo { url: string | null; is_env_forced: boolean; fallback_reason: string | null; + loaded_at: string | null; + generated_at: string | null; + source_revision: string | null; + etag: string | null; model_count: number; } +const SHORT_REVISION_LENGTH = 12; + +const shortRevision = (revision: string) => revision.slice(0, SHORT_REVISION_LENGTH); + const EMPTY_RELOAD_STATUS: ReloadStatus = { scheduled: false, interval_hours: null, @@ -89,6 +97,55 @@ const isValidReloadInterval = (value: number) => { return value >= 1 && value <= 168; }; +const formatDateTime = (dateTimeString: string | null) => { + if (!dateTimeString) return "Never"; + try { + return new Date(dateTimeString).toLocaleString(); + } catch { + return dateTimeString; + } +}; + +const CostMapProvenanceRows: React.FC<{ sourceInfo: CostMapSourceInfo }> = ({ sourceInfo }) => ( + <> + {sourceInfo.generated_at && ( +
+ Generated at: + {formatDateTime(sourceInfo.generated_at)} +
+ )} + + {sourceInfo.source_revision && ( +
+ Source revision: + + }> + {shortRevision(sourceInfo.source_revision)} + + {sourceInfo.source_revision} + +
+ )} + + {sourceInfo.etag && ( +
+ ETag: + + }>{sourceInfo.etag} + {sourceInfo.etag} + +
+ )} + + {sourceInfo.loaded_at && ( +
+ Loaded at: + {formatDateTime(sourceInfo.loaded_at)} +
+ )} + +); + const PriceDataReload: React.FC = ({ accessToken, onReloadSuccess, @@ -227,15 +284,6 @@ const PriceDataReload: React.FC = ({ } }; - const formatDateTime = (dateTimeString: string | null) => { - if (!dateTimeString) return "Never"; - try { - return new Date(dateTimeString).toLocaleString(); - } catch { - return dateTimeString; - } - }; - const getStatusText = () => { if (!reloadStatus?.scheduled) return "Not scheduled"; if (!reloadStatus.last_run) return "Ready"; @@ -334,6 +382,8 @@ const PriceDataReload: React.FC = ({ )} + + {sourceInfo.is_env_forced && (
diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6fb08445aff..7b9b24c9627 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -8684,6 +8684,9 @@ export interface paths { * - url: the remote URL that was attempted (null when env-forced local) * - is_env_forced: true if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage * - fallback_reason: human-readable reason why remote failed (null on success) + * - loaded_at: when this pod last loaded the map + * - generated_at, source_revision: the _metadata stamp inside the loaded file + * - etag: the ETag of the remote fetch (null for the bundled backup) * - model_count: number of models in the currently loaded cost map */ get: operations["get_model_cost_map_source_model_cost_map_source_get"]; From 7d3b68fea5a6343781401e52f40b02f6c581541c Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 7 Sep 2026 16:59:53 -0700 Subject: [PATCH 013/105] fix(files): preserve managed deletion routing and response identity --- .../proxy/hooks/managed_files.py | 13 ++- tests/e2e/batches/COVERAGE.md | 3 + .../proxy/test_managed_files_hook.py | 104 ++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py index bc1eb6cebc2..6e0bb0da3f4 100644 --- a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py +++ b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py @@ -1779,7 +1779,16 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): # Remove conflicting keys from data to avoid duplicate keyword arguments filtered_data = {k: v for k, v in data.items() if k not in ("model", "file_id")} for model_id, model_file_id in specific_model_file_id_mapping.items(): - delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **filtered_data) # type: ignore + credentials = llm_router.get_deployment_credentials_with_provider(model_id=model_id) + delete_data = { + **{k: v for k, v in filtered_data.items() if k != "_litellm_internal_model_credentials"}, + **( + {"_litellm_internal_model_credentials": MappingProxyType(dict(credentials))} + if credentials is not None + else {} + ), + } + delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **delete_data) stored_file_object = await self.delete_unified_file_id(file_id, litellm_parent_otel_span) @@ -1790,7 +1799,7 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): prom_logger.record_managed_file_deleted(result="success") if stored_file_object: - return stored_file_object + return OpenAIFileObject.model_validate(stored_file_object).model_copy(update={"id": file_id}) elif delete_response: delete_response.id = file_id return delete_response diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index ca44fc95e25..919c39f21a2 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -138,6 +138,9 @@ output and error files returned by terminal batches. Bedrock deletion uses a sig restricted to the configured storage buckets and managed file prefixes. The low-RPM test submits with its restricted key and cleans up with the test administrator key +Managed deletion forwards the deployment's trusted bucket configuration and returns +the requested managed file ID even when stored output metadata carries a provider ID + Azure input uploads request `expires_after` anchored to `created_at` with `seconds=1209600`, and the lifecycle tests check the returned expiry. This is a fallback for interrupted runs: immediate deletion remains the normal cleanup. diff --git a/tests/test_litellm/enterprise/proxy/test_managed_files_hook.py b/tests/test_litellm/enterprise/proxy/test_managed_files_hook.py index 091b958d7c3..48fceb50403 100644 --- a/tests/test_litellm/enterprise/proxy/test_managed_files_hook.py +++ b/tests/test_litellm/enterprise/proxy/test_managed_files_hook.py @@ -1095,6 +1095,110 @@ async def test_afile_content_passes_trusted_model_credentials_to_router(): assert trusted_credentials["s3_bucket_name"] == "my-bucket" +def _managed_deletion_file_id(provider_file_id): + from litellm.types.utils import SpecialEnums + + value = SpecialEnums.LITELLM_MANAGED_FILE_COMPLETE_STR.value.format( + "application/json", "test-file", "batch-model", provider_file_id, "model-123" + ) + return base64.urlsafe_b64encode(value.encode()).decode().rstrip("=") + + +def _managed_files_with_deletion_row(unified_file_id, provider_file_id, file_object): + from litellm.caching import DualCache + from litellm.models.managed_files import LiteLLM_ManagedFileTable + from litellm_enterprise.proxy.hooks.managed_files import _PROXY_LiteLLMManagedFiles + + row = LiteLLM_ManagedFileTable( + unified_file_id=unified_file_id, + model_mappings={"model-123": provider_file_id}, + flat_model_file_ids=[provider_file_id], + file_object=file_object, + ) + table = MagicMock( + find_first=AsyncMock(return_value=row), + delete=AsyncMock(), + ) + return _PROXY_LiteLLMManagedFiles( + internal_usage_cache=DualCache(), + prisma_client=MagicMock(db=MagicMock(litellm_managedfiletable=table)), + ), table + + +@pytest.mark.asyncio +async def test_afile_delete_bedrock_uses_deployment_bucket_and_signed_s3_delete(monkeypatch): + import httpx + import respx + + from litellm import Router + + monkeypatch.delenv("AWS_S3_BUCKET_NAME", raising=False) + monkeypatch.delenv("AWS_S3_OUTPUT_BUCKET_NAME", raising=False) + monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True") + router = Router( + model_list=[ + { + "model_name": "bedrock-batch", + "litellm_params": { + "model": "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", + "aws_access_key_id": "AKIAEXAMPLE", + "aws_secret_access_key": "secret", + "aws_region_name": "us-west-2", + "s3_bucket_name": "my-bucket", + }, + "model_info": {"id": "model-123"}, + } + ], + num_retries=0, + ) + s3_uri = "s3://my-bucket/litellm-bedrock-files/input.jsonl" + unified_file_id = _managed_deletion_file_id(s3_uri) + managed_files, table = _managed_files_with_deletion_row(unified_file_id, s3_uri, None) + with respx.mock: + route = respx.delete( + "https://s3.us-west-2.amazonaws.com/my-bucket/litellm-bedrock-files/input.jsonl" + ).mock(return_value=httpx.Response(204)) + response = await managed_files.afile_delete( + file_id=unified_file_id, + litellm_parent_otel_span=None, + llm_router=router, + _litellm_internal_model_credentials={"s3_bucket_name": "request-bucket"}, + ) + + assert len(route.calls) == 1 + assert route.calls[0].request.headers["Authorization"].startswith("AWS4-HMAC-SHA256") + assert response.id == unified_file_id + assert response.deleted is True + table.delete.assert_awaited_once_with(where={"unified_file_id": unified_file_id}) + + +@pytest.mark.asyncio +async def test_afile_delete_returns_managed_id_for_stored_provider_output(): + from openai.types import FileDeleted + + provider_file_id = "file-error-output" + unified_file_id = _managed_deletion_file_id(provider_file_id) + stored_file = _make_file_object(provider_file_id) + managed_files, table = _managed_files_with_deletion_row(unified_file_id, provider_file_id, stored_file) + router = MagicMock( + get_deployment_credentials_with_provider=MagicMock(return_value=None), + afile_delete=AsyncMock(return_value=FileDeleted(id=provider_file_id, object="file", deleted=True)), + ) + response = await managed_files.afile_delete( + file_id=unified_file_id, + litellm_parent_otel_span=None, + llm_router=router, + _litellm_internal_model_credentials={"s3_bucket_name": "request-bucket"}, + ) + + assert response.id == unified_file_id + assert response.object == "file" + assert response.filename == stored_file.filename + assert stored_file.id == provider_file_id + router.afile_delete.assert_awaited_once_with(model="model-123", file_id=provider_file_id) + table.delete.assert_awaited_once_with(where={"unified_file_id": unified_file_id}) + + @pytest.mark.asyncio async def test_afile_content_bedrock_unified_id_end_to_end(monkeypatch): """ From 6c1bba54c2c2d8e2b8c47673678eccdcda4f36a2 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:28:58 -0700 Subject: [PATCH 014/105] fix(ui): show a malformed generated_at stamp as-is on the Price Data Reload card --- .../src/components/price_data_reload.test.tsx | 13 +++++++++++++ .../src/components/price_data_reload.tsx | 7 ++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx index a85211f498c..fde69675b72 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx @@ -75,6 +75,19 @@ describe("PriceDataReload", () => { expect(screen.getByText(new Date(provenance.loaded_at).toLocaleString())).toBeInTheDocument(); }); + it("shows a malformed generated_at stamp as-is instead of Invalid Date", async () => { + vi.mocked(getModelCostMapSource).mockResolvedValue({ + ...remoteSource, + ...provenance, + generated_at: "yesterday-ish", + } as never); + render(); + + expect(await screen.findByText("Generated at:")).toBeInTheDocument(); + expect(screen.getByText("yesterday-ish")).toBeInTheDocument(); + expect(screen.queryByText("Invalid Date")).not.toBeInTheDocument(); + }); + it("hides the provenance rows when the loaded map carries no stamp", async () => { render(); diff --git a/ui/litellm-dashboard/src/components/price_data_reload.tsx b/ui/litellm-dashboard/src/components/price_data_reload.tsx index 3bb70072937..c152916f271 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.tsx @@ -99,11 +99,8 @@ const isValidReloadInterval = (value: number) => { const formatDateTime = (dateTimeString: string | null) => { if (!dateTimeString) return "Never"; - try { - return new Date(dateTimeString).toLocaleString(); - } catch { - return dateTimeString; - } + const parsed = new Date(dateTimeString); + return Number.isNaN(parsed.getTime()) ? dateTimeString : parsed.toLocaleString(); }; const CostMapProvenanceRows: React.FC<{ sourceInfo: CostMapSourceInfo }> = ({ sourceInfo }) => ( From aa1c76bc3b601e8beef987101297a9e7f94f8560 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:28:59 -0700 Subject: [PATCH 015/105] test(cost_map): skip every reserved top-level key in the price map schema test --- tests/test_litellm/test_utils.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index 8a56a84ade7..f6c9a4537a1 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -21,6 +21,7 @@ from litellm._logging import ( verbose_logger, ) from litellm.integrations.custom_logger import CustomLogger +from litellm.litellm_core_utils.get_model_cost_map import RESERVED_TOP_LEVEL_KEYS from litellm.proxy.utils import is_valid_api_key from litellm.types.utils import ( CallTypes, @@ -1218,15 +1219,12 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): with open(prod_json, "r") as model_prices_file: actual_json = json.load(model_prices_file) assert isinstance(actual_json, dict) - actual_json.pop( - "sample_spec", None - ) # remove the sample, whose schema is inconsistent with the real data - actual_json.pop( - "fallback_generalizations", None - ) # reserved meta key, not a model entry + model_entries: Final = { + key: value for key, value in actual_json.items() if key not in RESERVED_TOP_LEVEL_KEYS + } # Validate schema - validate(actual_json, INTENDED_SCHEMA) + validate(model_entries, INTENDED_SCHEMA) # Validate cost values # Define exceptions for models that are allowed to have costs > 1 @@ -1237,7 +1235,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "runwayml/seedance2", # 4K output is 150 credits/second = $1.50/second ] - is_valid, violations = validate_model_cost_values(actual_json, exceptions) + is_valid, violations = validate_model_cost_values(model_entries, exceptions) if not is_valid: error_message = "Cost validation failed:\n" + "\n".join(violations) @@ -1268,8 +1266,7 @@ def test_max_tokens_consistency(): inconsistencies = [] for model_name, config in models.items(): - # Skip the sample_spec - if model_name == "sample_spec": + if model_name in RESERVED_TOP_LEVEL_KEYS: continue # Check if both max_tokens and max_output_tokens exist From fb7d06da4b75f04ab6487e8dabb3ac0f0a54407a Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:45:53 -0700 Subject: [PATCH 016/105] test(budget_reservation): type the tiny-budget reservation helper --- .../proxy/spend_tracking/test_budget_reservation.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py index 9935dbceb7d..5e88268c283 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py +++ b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py @@ -53,7 +53,7 @@ async def test_non_exempt_llm_route_still_reserves_budget(): ANTHROPIC_MESSAGES: Final = [{"role": "user", "content": "hello!!!"}] -COUNT_TOKENS_REQUESTS: Final = ( +COUNT_TOKENS_REQUESTS: Final[tuple[tuple[str, dict[str, object]], ...]] = ( ("/v1/messages/count_tokens", {"model": "claude-sonnet-5", "messages": ANTHROPIC_MESSAGES}), ("/v1beta/models/gemini-3.8-flash:countTokens", {"contents": [{"role": "user", "parts": [{"text": "hello!!!"}]}]}), ) @@ -68,7 +68,7 @@ def spend_counter_cache(monkeypatch: pytest.MonkeyPatch) -> DualCache: return cache -async def _reserve_for_tiny_budget_key(route: str, request_body: dict) -> dict | None: +async def _reserve_for_tiny_budget_key(route: str, request_body: dict[str, object]) -> dict[str, object] | None: return await reserve_budget_for_request( request_body=request_body, route=route, @@ -85,7 +85,7 @@ async def _reserve_for_tiny_budget_key(route: str, request_body: dict) -> dict | @pytest.mark.asyncio @pytest.mark.parametrize(("route", "request_body"), COUNT_TOKENS_REQUESTS) async def test_repeated_token_counting_never_touches_a_tiny_budget( - spend_counter_cache: DualCache, route: str, request_body: dict + spend_counter_cache: DualCache, route: str, request_body: dict[str, object] ): counter_key: Final = f"spend:key:{TINY_BUDGET_KEY_TOKEN}" @@ -97,8 +97,10 @@ async def test_repeated_token_counting_never_touches_a_tiny_budget( "/v1/messages", {"model": "claude-sonnet-5", "max_tokens": 16, "messages": ANTHROPIC_MESSAGES} ) assert completion is not None - assert completion["reserved_cost"] > 0 - assert spend_counter_cache.in_memory_cache.get_cache(key=counter_key) == pytest.approx(completion["reserved_cost"]) + reserved_cost: Final = completion["reserved_cost"] + assert isinstance(reserved_cost, float) + assert reserved_cost > 0 + assert spend_counter_cache.in_memory_cache.get_cache(key=counter_key) == pytest.approx(reserved_cost) BEDROCK_SONNET: Final = "us.anthropic.claude-sonnet-4-6" From 1d7e81cf5d3a29dd4731b3282cf0842aac854ea1 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:47:06 -0700 Subject: [PATCH 017/105] fix(streaming): guard empty choices and missing role when assembling stream chunks --- .../streaming_chunk_builder_utils.py | 23 ++-- .../test_streaming_chunk_builder_utils.py | 129 +++++++----------- 2 files changed, 66 insertions(+), 86 deletions(-) diff --git a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py index 81955fe769e..cf9604a0fd5 100644 --- a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py @@ -24,6 +24,7 @@ from litellm.types.utils import ( Choices, CompletionTokensDetails, CompletionTokensDetailsWrapper, + Delta, Function, FunctionCall, ModelResponse, @@ -326,6 +327,18 @@ class ChunkProcessor: return chunk_id return "" + @staticmethod + def _get_role_from_chunks(chunks: Sequence["_BaseChunk"]) -> str: + return ChunkProcessor._role_of_choice(next((c["choices"][0] for c in chunks if c.get("choices")), None)) + + @staticmethod + def _role_of_choice(choice: object) -> str: + match choice: + case StreamingChoices(delta=Delta(role=str() as role)) | {"delta": {"role": str() as role}} if role: + return role + case _: + return "assistant" + @staticmethod def _get_model_from_chunks(chunks: Sequence["_BaseChunk"], first_chunk_model: str) -> str: """ @@ -353,15 +366,7 @@ class ChunkProcessor: model: Final = ChunkProcessor._get_model_from_chunks(chunks, first_chunk_model) system_fingerprint: Final = chunk.get("system_fingerprint", None) - # Fall back to None rather than `chunk`: if no chunk carries a non-empty - # `choices` array, indexing [0] on the first chunk raises IndexError. - first_chunk_with_choices = next((c for c in chunks if c.get("choices")), None) - role: str = "assistant" - if first_chunk_with_choices is not None: - _choices = first_chunk_with_choices["choices"] - if len(_choices) > 0: - # `delta` may be absent or omit `role` (e.g. content-only deltas). - role = _choices[0].get("delta", {}).get("role") or "assistant" + role: Final = ChunkProcessor._get_role_from_chunks(chunks) finish_reason = "stop" for chunk in chunks: if "choices" in chunk and len(chunk["choices"]) > 0: diff --git a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py index 2d2451e73f7..626b8a63b20 100644 --- a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py @@ -1,4 +1,6 @@ import json +from collections.abc import Mapping, Sequence +from typing import Final import pytest @@ -1478,104 +1480,77 @@ def test_calculate_usage_fills_unknown_split_from_reasoning_estimate( assert usage.completion_tokens_details.text_tokens == expected_text_tokens -def _empty_choices_chunk(**extra): - chunk = { - "id": "chatcmpl-empty-choices", +def _openai_chunk( + choices: Sequence[Mapping[str, object]], usage: Mapping[str, int] | None = None +) -> dict[str, object]: + base: Final = { + "id": "chatcmpl-lit6552", "object": "chat.completion.chunk", "created": 1, - "model": "claude-opus-4-8", - "choices": [], + "model": "gpt-5.4-mini", + "choices": list(choices), } - chunk.update(extra) - return chunk + return base if usage is None else {**base, "usage": dict(usage)} @pytest.mark.parametrize( "chunks", [ + pytest.param([_openai_chunk(choices=[]), _openai_chunk(choices=[])], id="all_empty_choices_dicts"), pytest.param( - [_empty_choices_chunk(), _empty_choices_chunk()], - id="all_chunks_have_empty_choices", - ), - pytest.param( - [ - _empty_choices_chunk(usage={"prompt_tokens": 10}), - _empty_choices_chunk(usage={"completion_tokens": 0}), - ], - id="usage_only_chunks", + [ModelResponseStream(model="gpt-5.4-mini", choices=[]) for _ in range(2)], + id="all_empty_choices_objects", ), ], ) -def test_build_base_response_handles_empty_choices(chunks): - """Empty `choices` arrays must not raise IndexError. - - `next((c for c in chunks if c.get("choices")), chunk)` used to fall back to the - first chunk, whose `choices` may be `[]`, so `["choices"][0]` went out of range. - The resulting error is surfaced to the client mid-stream and the request never - reaches SpendLogs. - """ - processor = ChunkProcessor(chunks=list(chunks)) - - response = processor.build_base_response(list(chunks)) +def test_stream_chunk_builder_survives_all_empty_choices(chunks: Sequence[object]) -> None: + response: Final = stream_chunk_builder(chunks=list(chunks)) + assert response is not None assert response.choices[0].message.role == "assistant" + assert response.choices[0].finish_reason == "stop" + + +def test_stream_chunk_builder_keeps_usage_from_usage_only_frames() -> None: + usage_frame: Final = _openai_chunk( + choices=[], usage={"prompt_tokens": 10, "completion_tokens": 0, "total_tokens": 10} + ) + + response: Final = stream_chunk_builder(chunks=[usage_frame]) + + assert response is not None + assert response.choices[0].message.role == "assistant" + assert response.usage.prompt_tokens == 10 + assert response.usage.total_tokens == 10 @pytest.mark.parametrize( "delta", - [ - pytest.param({"content": "Hello"}, id="delta_without_role"), - pytest.param({}, id="delta_empty_dict"), - ], + [pytest.param({"content": "Hi"}, id="delta_without_role"), pytest.param({}, id="empty_delta")], ) -def test_build_base_response_handles_delta_without_role(delta): - """A `delta` that omits `role` must not raise KeyError.""" - chunks = [ - { - "id": "chatcmpl-no-role", - "object": "chat.completion.chunk", - "created": 1, - "model": "claude-opus-4-8", - "choices": [{"index": 0, "delta": delta, "finish_reason": None}], - } +def test_stream_chunk_builder_defaults_role_when_delta_omits_it(delta: Mapping[str, str]) -> None: + chunks: Final = [ + _openai_chunk(choices=[{"index": 0, "delta": dict(delta), "finish_reason": None}]), + _openai_chunk(choices=[{"index": 0, "delta": {"content": "!"}, "finish_reason": "stop"}]), ] - processor = ChunkProcessor(chunks=list(chunks)) - response = processor.build_base_response(list(chunks)) - - assert response.choices[0].message.role == "assistant" - - -def test_build_base_response_still_reads_role_and_finish_reason(): - """Regression guard: well-formed chunks keep their role and finish_reason.""" - chunks = [ - _empty_choices_chunk(), - { - "id": "chatcmpl-normal", - "object": "chat.completion.chunk", - "created": 1, - "model": "claude-opus-4-8", - "choices": [ - { - "index": 0, - "delta": {"role": "assistant", "content": "Hi"}, - "finish_reason": None, - } - ], - }, - { - "id": "chatcmpl-normal", - "object": "chat.completion.chunk", - "created": 2, - "model": "claude-opus-4-8", - "choices": [ - {"index": 0, "delta": {"content": "!"}, "finish_reason": "stop"} - ], - }, - ] - processor = ChunkProcessor(chunks=list(chunks)) - - response = processor.build_base_response(list(chunks)) + response: Final = stream_chunk_builder(chunks=chunks) + assert response is not None assert response.choices[0].message.role == "assistant" + assert response.choices[0].message.content == delta.get("content", "") + "!" assert response.choices[0].finish_reason == "stop" + + +def test_stream_chunk_builder_reads_role_from_first_frame_with_choices() -> None: + chunks: Final = [ + _openai_chunk(choices=[]), + _openai_chunk(choices=[{"index": 0, "delta": {"role": "user", "content": "Hi"}, "finish_reason": None}]), + _openai_chunk(choices=[{"index": 0, "delta": {}, "finish_reason": "stop"}]), + ] + + response: Final = stream_chunk_builder(chunks=chunks) + + assert response is not None + assert response.choices[0].message.role == "user" + assert response.choices[0].message.content == "Hi" From 9041768fb43715dc8c28e5dc139c86adac2659ce Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:47:51 -0700 Subject: [PATCH 018/105] feat(cost_map): derive source_revision from the loaded bytes instead of a _metadata stamp The revision an operator checks is now the git blob id of the exact bytes the process loaded, the same id git rev-parse :model_prices_and_context_window.json prints, so it is always present, never goes stale between bot writes, and needs no stamp in the JSON that every PR touching the file would have to regenerate. The _metadata block, the generated_at field, the schema and guard changes, and the bot stamping are dropped --- ...to_update_price_and_context_window_file.py | 27 +--- ci_cd/cost_map_guard.py | 7 +- ci_cd/generate_model_prices_schema.py | 19 +-- .../litellm_core_utils/get_model_cost_map.py | 98 ++++++------- ...odel_prices_and_context_window_backup.json | 4 - litellm/proxy/proxy_server.py | 2 +- model_prices_and_context_window.json | 4 - model_prices_and_context_window.schema.json | 20 +-- scripts/sync_together_ai_models.py | 23 +-- .../test_get_model_cost_map.py | 135 +++++++----------- .../test_routes_model_cost_map.py | 24 ++-- ...to_update_price_and_context_window_file.py | 54 ------- tests/test_litellm/test_cost_map_guard.py | 20 --- .../test_litellm/test_model_prices_schema.py | 19 --- .../test_sync_together_ai_models.py | 53 ------- tests/test_litellm/test_utils.py | 17 ++- .../src/components/price_data_reload.test.tsx | 15 +- .../src/components/price_data_reload.tsx | 8 -- ui/litellm-dashboard/src/lib/http/schema.d.ts | 2 +- 19 files changed, 131 insertions(+), 420 deletions(-) delete mode 100644 tests/test_litellm/test_auto_update_price_and_context_window_file.py diff --git a/.github/scripts/auto_update_price_and_context_window_file.py b/.github/scripts/auto_update_price_and_context_window_file.py index a7a3194f262..461d8d347d9 100644 --- a/.github/scripts/auto_update_price_and_context_window_file.py +++ b/.github/scripts/auto_update_price_and_context_window_file.py @@ -1,9 +1,6 @@ import asyncio import aiohttp import json -import os -import subprocess -from datetime import datetime, timezone # Asynchronously fetch data from a given URL async def fetch_data(url): @@ -34,28 +31,13 @@ def sync_local_data_with_remote(local_data, remote_data): for key in (set(remote_data) - set(local_data)): local_data[key] = remote_data[key] -def utc_now_iso(): - return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - - -def source_revision(): - from_env = os.environ.get("GITHUB_SHA") - if from_env: - return from_env - return subprocess.run(["git", "rev-parse", "HEAD"], check=True, capture_output=True, text=True).stdout.strip() - - -def stamp_metadata(data, generated_at, revision): - return {**data, "_metadata": {"generated_at": generated_at, "source_revision": revision}} - - # Write data to the json file def write_to_file(file_path, data): try: # Open the file in write mode with open(file_path, "w") as file: # Dump the data as JSON into the file - file.write(json.dumps(data, indent=4) + "\n") + json.dump(data, file, indent=4) print("Values updated successfully.") except Exception as e: # Print an error message if writing to file fails @@ -167,13 +149,8 @@ def main(): # If both local and openrouter data are available, synchronize and save if local_data and all_remote_data: - before = json.dumps(local_data, sort_keys=True) sync_local_data_with_remote(local_data, all_remote_data) - changed = json.dumps(local_data, sort_keys=True) != before - write_to_file( - local_file_path, - stamp_metadata(local_data, utc_now_iso(), source_revision()) if changed else local_data, - ) + write_to_file(local_file_path, local_data) else: print("Failed to fetch model data from either local file or URL.") diff --git a/ci_cd/cost_map_guard.py b/ci_cd/cost_map_guard.py index 351c06c74eb..50aa40ba220 100644 --- a/ci_cd/cost_map_guard.py +++ b/ci_cd/cost_map_guard.py @@ -2,8 +2,7 @@ Every pull request gets the file checks: the three cost map files parse, the backup copy matches the root file, and the JSON schema is in sync and validates the map. Pull requests from the cost map sync bot (branches named -litellm_cost_map_sync_*) additionally may only touch those three files and may only add or update models, plus -restamp the _metadata provenance block. +litellm_cost_map_sync_*) additionally may only touch those three files and may only add or update models. """ from __future__ import annotations @@ -16,7 +15,7 @@ from collections.abc import Sequence from dataclasses import dataclass from typing import Final -from generate_model_prices_schema import BOT_LOCKED_ROOT_KEYS, build_schema, render, validation_errors +from generate_model_prices_schema import SPECIAL_ROOT_KEYS, build_schema, render, validation_errors COST_MAP_PATH: Final = "model_prices_and_context_window.json" BACKUP_PATH: Final = "litellm/model_prices_and_context_window_backup.json" @@ -103,7 +102,7 @@ def _bot_failures(base: Snapshot, head_map: CostMap, changed_files: Sequence[str *(f"bot PRs may not remove fields: {ref}" for ref in removed_fields), *( f"bot PRs may not change {key}" - for key in sorted(BOT_LOCKED_ROOT_KEYS) + for key in sorted(SPECIAL_ROOT_KEYS) if base_map.get(key) != head_map.get(key) ), ) diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py index 557afa50128..ab29b70bdd4 100644 --- a/ci_cd/generate_model_prices_schema.py +++ b/ci_cd/generate_model_prices_schema.py @@ -11,9 +11,7 @@ REPO_ROOT = Path(__file__).parent.parent PRICES_PATH = REPO_ROOT / "model_prices_and_context_window.json" SCHEMA_PATH = REPO_ROOT / "model_prices_and_context_window.schema.json" -METADATA_KEY = "_metadata" -SPECIAL_ROOT_KEYS = frozenset({"sample_spec", "fallback_generalizations", METADATA_KEY}) -BOT_LOCKED_ROOT_KEYS = SPECIAL_ROOT_KEYS - {METADATA_KEY} +SPECIAL_ROOT_KEYS = frozenset({"sample_spec", "fallback_generalizations"}) JsonSchema = dict @@ -273,26 +271,13 @@ def build_schema(prices: dict) -> JsonSchema: "description": ( "Schema for LiteLLM's model price and context window registry " "(https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). " - "Every top-level key except '_metadata', 'sample_spec', and 'fallback_generalizations' is a model id, " + "Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, " "optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. " "All costs are USD per unit. New optional fields are added regularly, so consumers should " "ignore unknown fields rather than reject them." ), "type": "object", "properties": { - METADATA_KEY: { - "type": "object", - "description": ( - "Provenance of this file: when an automated sync last regenerated it and the commit it " - "ran against. Human edits leave it untouched; not a model entry." - ), - "properties": { - "generated_at": {"type": "string", "format": "date-time"}, - "source_revision": STRING, - }, - "required": ["generated_at", "source_revision"], - "additionalProperties": False, - }, "sample_spec": { "type": "object", "description": ( diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index a538e7cb330..2bdfbc66088 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -9,18 +9,18 @@ export LITELLM_LOCAL_MODEL_COST_MAP=True """ import asyncio +import hashlib import json import os import random import time from collections.abc import Awaitable, Callable -from dataclasses import dataclass +from dataclasses import dataclass, replace from datetime import datetime, timezone from importlib.resources import files from typing import Final, Protocol import httpx -from pydantic import BaseModel, ConfigDict, ValidationError from typing_extensions import ReadOnly, TypedDict from litellm import verbose_logger @@ -33,11 +33,10 @@ from litellm.litellm_core_utils.fallback_generalizations import ( ) FALLBACK_GENERALIZATIONS_KEY: Final = "fallback_generalizations" -METADATA_KEY: Final = "_metadata" # Reserved top-level keys that are not model entries. They must be excluded # from the model-count integrity check so a real upstream shrink can't be masked. -RESERVED_TOP_LEVEL_KEYS: Final = frozenset({"sample_spec", FALLBACK_GENERALIZATIONS_KEY, METADATA_KEY}) +RESERVED_TOP_LEVEL_KEYS: Final = frozenset({"sample_spec", FALLBACK_GENERALIZATIONS_KEY}) def _count_model_entries(model_cost: dict) -> int: @@ -45,6 +44,11 @@ def _count_model_entries(model_cost: dict) -> int: return sum(1 for key in model_cost if key not in RESERVED_TOP_LEVEL_KEYS) +def git_blob_id(body: bytes) -> str: + """The sha1 git gives these bytes as a blob, so ``git rev-parse :`` reproduces it for the file""" + return hashlib.sha1(b"blob %d\0" % len(body) + body, usedforsecurity=False).hexdigest() + + class GetModelCostMap: """ Handles fetching, validating, and loading the model cost map. @@ -56,15 +60,25 @@ class GetModelCostMap: _backup_model_count: int = -1 # -1 = not yet loaded + @staticmethod + def read_local_model_cost_map_bytes() -> bytes: + return files("litellm").joinpath("model_prices_and_context_window_backup.json").read_bytes() + @staticmethod def read_local_model_cost_map_text() -> str: - return files("litellm").joinpath("model_prices_and_context_window_backup.json").read_text(encoding="utf-8") + return GetModelCostMap.read_local_model_cost_map_bytes().decode("utf-8") + + @staticmethod + def load_local_model_cost_map_with_revision() -> "ModelCostMapReloaded": + """The bundled backup map together with the git blob id of the file it was parsed from""" + body: Final = GetModelCostMap.read_local_model_cost_map_bytes() + content: Final = json.loads(body) + return ModelCostMapReloaded(model_cost_map=content, revision=git_blob_id(body)) @staticmethod def load_local_model_cost_map() -> dict: """Load the local backup model cost map bundled with the package.""" - content: Final = json.loads(GetModelCostMap.read_local_model_cost_map_text()) - return content + return GetModelCostMap.load_local_model_cost_map_with_revision().model_cost_map @classmethod def _get_backup_model_count(cls) -> int: @@ -169,6 +183,7 @@ MODEL_COST_MAP_FETCH_MAX_WAIT_SECONDS: Final = 30.0 @dataclass(frozen=True, slots=True) class ModelCostMapReloaded: model_cost_map: dict # mutable-ok: adopted as litellm.model_cost, whose consumer contract is a plain mutable dict + revision: str | None = None etag: str | None = None @@ -258,7 +273,9 @@ def _classify_fetch_response(response: httpx.Response, url: str) -> _FetchAttemp return ModelCostMapReloadUnavailable(reason=f"invalid JSON from {url}: {e}") if not isinstance(parsed, dict): return ModelCostMapReloadUnavailable(reason=f"expected a JSON object from {url}, got {type(parsed).__name__}") - return ModelCostMapReloaded(model_cost_map=parsed, etag=response.headers.get("etag")) + return ModelCostMapReloaded( + model_cost_map=parsed, revision=git_blob_id(response.content), etag=response.headers.get("etag") + ) def _next_retry_wait( @@ -337,10 +354,7 @@ async def refetch_model_cost_map( _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True _cost_map_source_info.fallback_reason = None - _cost_map_source_info.etag = None - return ModelCostMapReloaded( - model_cost_map=_finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) - ) + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()) result: Final = await _fetch_remote_model_cost_map_with_retry( url=url, @@ -366,8 +380,7 @@ async def refetch_model_cost_map( _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False _cost_map_source_info.fallback_reason = None - _cost_map_source_info.etag = result.etag - return ModelCostMapReloaded(model_cost_map=_finalize_model_cost_map(result.model_cost_map), etag=result.etag) + return _finalize_loaded_model_cost_map(result) class ModelCostMapSourceInfo: @@ -378,7 +391,6 @@ class ModelCostMapSourceInfo: is_env_forced: bool = False fallback_reason: str | None = None loaded_at: "datetime | None" = None - generated_at: str | None = None source_revision: str | None = None etag: str | None = None @@ -387,28 +399,7 @@ class ModelCostMapSourceInfo: _cost_map_source_info: Final = ModelCostMapSourceInfo() -class CostMapMetadata(BaseModel): - model_config = ConfigDict(frozen=True, extra="ignore") - - generated_at: str | None = None - source_revision: str | None = None - - -_EMPTY_METADATA: Final = CostMapMetadata() - - -def _parse_metadata(raw: object) -> CostMapMetadata: - if raw is None: - return _EMPTY_METADATA - try: - return CostMapMetadata.model_validate(raw) - except ValidationError as error: - verbose_logger.warning("LiteLLM: ignoring a malformed %s block in the model cost map: %s", METADATA_KEY, error) - return _EMPTY_METADATA - - class CostMapProvenance(TypedDict): - generated_at: ReadOnly[str | None] source_revision: ReadOnly[str | None] etag: ReadOnly[str | None] @@ -422,10 +413,10 @@ class CostMapSourceInfo(CostMapProvenance): def get_model_cost_map_provenance() -> CostMapProvenance: - """Which revision of the cost map this process serves: the ``_metadata`` stamp the file - carries plus the ETag the remote fetch returned (None for the bundled backup)""" + """Which revision of the cost map this process serves: the git blob id of the bytes it loaded, the + same id ``git rev-parse :model_prices_and_context_window.json`` prints for a checkout, plus + the ETag the remote fetch returned (None for the bundled backup)""" return { - "generated_at": _cost_map_source_info.generated_at, "source_revision": _cost_map_source_info.source_revision, "etag": _cost_map_source_info.etag, } @@ -441,7 +432,7 @@ def get_model_cost_map_source_info() -> CostMapSourceInfo: - is_env_forced: True if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage - fallback_reason: human-readable reason if remote failed and local was used - loaded_at: ISO 8601 time this process last loaded the map - - generated_at, source_revision: the ``_metadata`` stamp inside the loaded file + - source_revision: git blob id of the loaded file's bytes - etag: the ETag of the remote fetch (None for the bundled backup) """ loaded_at: Final = _cost_map_source_info.loaded_at @@ -451,7 +442,6 @@ def get_model_cost_map_source_info() -> CostMapSourceInfo: "is_env_forced": _cost_map_source_info.is_env_forced, "fallback_reason": _cost_map_source_info.fallback_reason, "loaded_at": loaded_at.isoformat() if loaded_at is not None else None, - "generated_at": _cost_map_source_info.generated_at, "source_revision": _cost_map_source_info.source_revision, "etag": _cost_map_source_info.etag, } @@ -518,21 +508,24 @@ def _expand_model_aliases(model_cost: dict) -> dict: def _finalize_model_cost_map(model_cost: dict) -> dict: - """Extract fallback generalizations and the provenance stamp out of the raw map, then expand aliases. + """Extract fallback generalizations out of the raw map, then expand aliases. The ``fallback_generalizations`` block is installed into the generalizations - module and the ``_metadata`` block into the source info; both are removed from - the map so neither is ever treated as a model entry. + module and removed from the map so it is never treated as a model entry. """ raw: Final = model_cost.pop(FALLBACK_GENERALIZATIONS_KEY, None) rules: Final = raw.get("rules") if isinstance(raw, dict) else None set_fallback_generalizations(rules) - metadata: Final = _parse_metadata(model_cost.pop(METADATA_KEY, None)) - _cost_map_source_info.generated_at = metadata.generated_at - _cost_map_source_info.source_revision = metadata.source_revision return _expand_model_aliases(model_cost) +def _finalize_loaded_model_cost_map(loaded: ModelCostMapReloaded) -> ModelCostMapReloaded: + """Record which bytes this process now serves, then finalize the map they parsed into""" + _cost_map_source_info.source_revision = loaded.revision + _cost_map_source_info.etag = loaded.etag + return replace(loaded, model_cost_map=_finalize_model_cost_map(loaded.model_cost_map)) + + def get_model_cost_map( url: str, timeout: int = 5, @@ -561,12 +554,10 @@ def get_model_cost_map( _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True _cost_map_source_info.fallback_reason = None - _cost_map_source_info.etag = None - return _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False - _cost_map_source_info.etag = None result: Final = _fetch_remote_model_cost_map_with_retry_sync( url=url, @@ -584,7 +575,7 @@ def get_model_cost_map( ) _cost_map_source_info.source = "local" _cost_map_source_info.fallback_reason = f"Remote fetch failed: {result.reason}" - return _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map content: Final = result.model_cost_map # Validate using cached count (cheap int comparison, no file I/O) @@ -598,9 +589,8 @@ def get_model_cost_map( ) _cost_map_source_info.source = "local" _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" - return _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map _cost_map_source_info.source = "remote" _cost_map_source_info.fallback_reason = None - _cost_map_source_info.etag = result.etag - return _finalize_model_cost_map(content) + return _finalize_loaded_model_cost_map(result).model_cost_map diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 5edb3c0e9d8..b1ffc1583e4 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1,8 +1,4 @@ { - "_metadata": { - "generated_at": "2026-09-07T23:38:47Z", - "source_revision": "cd681a573fd9f5b6f15a1355f46178e4e9d374d2" - }, "sample_spec": { "code_interpreter_cost_per_session": 0.0, "computer_use_input_cost_per_1k_tokens": 0.0, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4741e4cd9d3..818a1506754 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -17938,7 +17938,7 @@ async def get_model_cost_map_source( - is_env_forced: true if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage - fallback_reason: human-readable reason why remote failed (null on success) - loaded_at: when this pod last loaded the map - - generated_at, source_revision: the _metadata stamp inside the loaded file + - source_revision: git blob id of the loaded file, what git rev-parse : prints for it - etag: the ETag of the remote fetch (null for the bundled backup) - model_count: number of models in the currently loaded cost map """ diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 5edb3c0e9d8..b1ffc1583e4 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1,8 +1,4 @@ { - "_metadata": { - "generated_at": "2026-09-07T23:38:47Z", - "source_revision": "cd681a573fd9f5b6f15a1355f46178e4e9d374d2" - }, "sample_spec": { "code_interpreter_cost_per_session": 0.0, "computer_use_input_cost_per_1k_tokens": 0.0, diff --git a/model_prices_and_context_window.schema.json b/model_prices_and_context_window.schema.json index c40c2a67682..47a1934a703 100644 --- a/model_prices_and_context_window.schema.json +++ b/model_prices_and_context_window.schema.json @@ -1,27 +1,9 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "LiteLLM model_prices_and_context_window.json", - "description": "Schema for LiteLLM's model price and context window registry (https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). Every top-level key except '_metadata', 'sample_spec', and 'fallback_generalizations' is a model id, optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. All costs are USD per unit. New optional fields are added regularly, so consumers should ignore unknown fields rather than reject them.", + "description": "Schema for LiteLLM's model price and context window registry (https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. All costs are USD per unit. New optional fields are added regularly, so consumers should ignore unknown fields rather than reject them.", "type": "object", "properties": { - "_metadata": { - "type": "object", - "description": "Provenance of this file: when an automated sync last regenerated it and the commit it ran against. Human edits leave it untouched; not a model entry.", - "properties": { - "generated_at": { - "type": "string", - "format": "date-time" - }, - "source_revision": { - "type": "string" - } - }, - "required": [ - "generated_at", - "source_revision" - ], - "additionalProperties": false - }, "sample_spec": { "type": "object", "description": "Documentation placeholder illustrating the entry shape; not a real model and not schema-conformant (several values are prose)." diff --git a/scripts/sync_together_ai_models.py b/scripts/sync_together_ai_models.py index e009f1a7ce6..12b128890f1 100644 --- a/scripts/sync_together_ai_models.py +++ b/scripts/sync_together_ai_models.py @@ -19,11 +19,9 @@ import argparse import json import os import re -import subprocess import sys from collections.abc import Mapping, Sequence from dataclasses import dataclass, field -from datetime import datetime, timezone from pathlib import Path from types import MappingProxyType from typing import Final @@ -35,7 +33,6 @@ MODELS_URL: Final = "https://api.together.ai/v1/models?serverless" DEPRECATIONS_URL: Final = "https://docs.together.ai/docs/deprecations.md" PROVIDER: Final = "together_ai" PREFIX: Final = "together_ai/" -METADATA_KEY: Final = "_metadata" SOURCE_URL: Final = "https://docs.together.ai/docs/serverless-models" COST_MAP_RELPATHS: Final = ( "model_prices_and_context_window.json", @@ -498,23 +495,6 @@ def _serialize(cost_map: CostMap) -> str: return json.dumps(cost_map, indent=4, ensure_ascii=False) + "\n" -def stamp_metadata(cost_map: CostMap, generated_at: str, source_revision: str) -> CostMap: - return {**cost_map, METADATA_KEY: {"generated_at": generated_at, "source_revision": source_revision}} - - -def _utc_now_iso() -> str: - return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - - -def _source_revision(repo_root: Path) -> str: - from_env: Final = os.environ.get("GITHUB_SHA") - if from_env: - return from_env - return subprocess.run( - ("git", "rev-parse", "HEAD"), cwd=repo_root, check=True, capture_output=True, text=True - ).stdout.strip() - - def main(argv: Sequence[str]) -> int: parser: Final = argparse.ArgumentParser(description=__doc__) parser.add_argument("--write", action="store_true", help="apply the sync to the cost map files (default: dry run)") @@ -547,9 +527,8 @@ def main(argv: Sequence[str]) -> int: if args.pr_body_file is not None: args.pr_body_file.write_text(body) if args.write and outcome.has_changes: - stamped: Final = _serialize(stamp_metadata(outcome.cost_map, _utc_now_iso(), _source_revision(args.repo_root))) for relpath in COST_MAP_RELPATHS: - (args.repo_root / relpath).write_text(stamped) + (args.repo_root / relpath).write_text(_serialize(outcome.cost_map)) print(render_summary(outcome)) print() print(body) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 62f72495491..d9fe6d2f979 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -17,11 +17,11 @@ from litellm.litellm_core_utils.fallback_generalizations import ( ) from litellm.litellm_core_utils.get_model_cost_map import ( FALLBACK_GENERALIZATIONS_KEY, - METADATA_KEY, GetModelCostMap, _count_model_entries, _finalize_model_cost_map, get_model_cost_map_provenance, + git_blob_id, ) @@ -33,18 +33,16 @@ def _load_root_cost_map() -> dict: return json.load(f) -def _load_bundled_stamp() -> dict: - path = os.path.join( - os.path.dirname(__file__), "../../../litellm/model_prices_and_context_window_backup.json" - ) - with open(path) as f: - return json.load(f)[METADATA_KEY] +def _bundled_blob_id() -> str: + path = os.path.join(os.path.dirname(__file__), "../../../litellm/model_prices_and_context_window_backup.json") + with open(path, "rb") as f: + return git_blob_id(f.read()) -_STAMP = { - "generated_at": "2026-09-07T00:00:00Z", - "source_revision": "0123456789abcdef0123456789abcdef01234567", -} +def test_git_blob_id_is_what_git_hash_object_prints(): + """An operator checks a reported revision with ``git hash-object`` or ``git rev-parse :``, + so the id must be git's blob sha1 of the exact bytes, not a plain sha1 or a hash of the parsed JSON.""" + assert git_blob_id(b'{"gpt-5.4-mini": {"mode": "chat"}}\n') == "18b9a8381e13a3b38a2128f184f631f95829e987" def _make_models(n: int) -> dict: @@ -57,7 +55,6 @@ def test_count_model_entries_excludes_reserved_keys(): m = _make_models(3) m["sample_spec"] = {"foo": "bar"} m[FALLBACK_GENERALIZATIONS_KEY] = {"rules": []} - m[METADATA_KEY] = dict(_STAMP) assert _count_model_entries(m) == 3 @@ -143,39 +140,6 @@ def test_finalize_with_no_block_clears_rules(): set_fallback_generalizations(previous) -def test_finalize_pops_metadata_and_records_provenance(): - finalized = _finalize_model_cost_map({**_make_models(2), METADATA_KEY: dict(_STAMP)}) - - assert METADATA_KEY not in finalized - assert len(finalized) == 2 - provenance = get_model_cost_map_provenance() - assert provenance["generated_at"] == _STAMP["generated_at"] - assert provenance["source_revision"] == _STAMP["source_revision"] - - -def test_finalize_without_metadata_clears_the_previous_stamp(): - _finalize_model_cost_map({**_make_models(2), METADATA_KEY: dict(_STAMP)}) - - _finalize_model_cost_map(_make_models(2)) - - provenance = get_model_cost_map_provenance() - assert provenance["generated_at"] is None - assert provenance["source_revision"] is None - - -@pytest.mark.parametrize( - "raw", - ["2026-09-07T00:00:00Z", {"generated_at": 42}, ["2026-09-07T00:00:00Z"]], - ids=["string", "wrong_field_type", "list"], -) -def test_finalize_tolerates_a_malformed_metadata_block(raw): - finalized = _finalize_model_cost_map({**_make_models(2), METADATA_KEY: raw}) - - assert METADATA_KEY not in finalized - assert len(finalized) == 2 - assert get_model_cost_map_provenance()["generated_at"] is None - - def test_shipped_backup_carries_the_claude_routing_rules(): """The bundled backup must ship the Claude routing rules so a fresh install (or an offline fallback) routes unknown Claude models without code changes. @@ -390,10 +354,6 @@ def _real_map_bytes() -> bytes: return json.dumps(_load_root_cost_map()).encode() -def _stamped_map_bytes(stamp: dict) -> bytes: - return json.dumps({**_load_root_cost_map(), METADATA_KEY: stamp}).encode() - - class _SleepRecorder: """Injected in place of asyncio.sleep so tests assert waits without real delay.""" @@ -555,40 +515,51 @@ async def test_refetch_respects_local_env_override(monkeypatch): @pytest.mark.asyncio -async def test_refetch_records_the_file_stamp_and_the_fetch_etag(): - """A reload reports which revision of the map it swapped in: the ``_metadata`` stamp the file - carries plus the ETag the fetch returned, with the stamp itself kept out of the model map.""" - client, _ = _mock_client( - [httpx.Response(200, headers={"ETag": 'W/"abc123"'}, content=_stamped_map_bytes(_STAMP))] - ) +async def test_refetch_records_the_blob_id_of_the_bytes_served_and_the_fetch_etag(): + """A reload reports which revision of the map it swapped in: the git blob id of the exact bytes the + fetch returned, so ``git rev-parse :model_prices_and_context_window.json`` can confirm it, + plus the ETag the fetch returned.""" + body = _real_map_bytes() + client, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"abc123"'}, content=body)]) result = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloaded) + assert result.revision == git_blob_id(body) assert result.etag == 'W/"abc123"' - assert METADATA_KEY not in result.model_cost_map - assert get_model_cost_map_provenance() == { - "generated_at": _STAMP["generated_at"], - "source_revision": _STAMP["source_revision"], - "etag": 'W/"abc123"', - } + assert get_model_cost_map_provenance() == {"source_revision": git_blob_id(body), "etag": 'W/"abc123"'} @pytest.mark.asyncio -async def test_refetch_local_override_reports_the_bundled_stamp_without_an_etag(monkeypatch): - """Forcing the bundled backup after a remote reload must drop the remote ETag, since the map - served is no longer the one that ETag identifies.""" - remote, _ = _mock_client( - [httpx.Response(200, headers={"ETag": 'W/"remote"'}, content=_stamped_map_bytes(_STAMP))] +async def test_refetch_revision_follows_the_bytes_not_the_url(): + """Two fetches of the same URL that return different bytes report different revisions.""" + edited = json.loads(_real_map_bytes()) + edited["gpt-5.4-mini"]["input_cost_per_token"] = 0.5 + client, _ = _mock_client( + [httpx.Response(200, content=_real_map_bytes()), httpx.Response(200, content=json.dumps(edited).encode())] ) + + first = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) + second = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) + + assert isinstance(first, ModelCostMapReloaded) and isinstance(second, ModelCostMapReloaded) + assert first.revision != second.revision + assert get_model_cost_map_provenance()["source_revision"] == second.revision + + +@pytest.mark.asyncio +async def test_refetch_local_override_reports_the_bundled_blob_id_without_an_etag(monkeypatch): + """Forcing the bundled backup after a remote reload must report the backup's own blob id and drop the + remote ETag, since the map served is no longer the one that ETag identifies.""" + remote, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"remote"'}, content=_real_map_bytes())]) await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=remote) monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") result = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0)) assert isinstance(result, ModelCostMapReloaded) - assert METADATA_KEY not in result.model_cost_map - assert get_model_cost_map_provenance() == {**_load_bundled_stamp(), "etag": None} + assert result.revision == _bundled_blob_id() + assert get_model_cost_map_provenance() == {"source_revision": _bundled_blob_id(), "etag": None} # --------------------------------------------------------------------------- @@ -633,7 +604,7 @@ def test_boot_load_retries_transient_failures_instead_of_falling_back(): source = get_model_cost_map_source_info() assert source["source"] == "remote" assert source["fallback_reason"] is None - assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY, METADATA_KEY} + assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} def test_boot_load_honors_retry_after_then_falls_back_after_max_attempts(): @@ -685,37 +656,31 @@ def test_boot_load_respects_local_env_override(monkeypatch): assert get_model_cost_map_source_info()["is_env_forced"] is True -def test_boot_load_records_the_file_stamp_and_the_fetch_etag(): - client, _ = _mock_client( - [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_stamped_map_bytes(_STAMP))], - client_cls=httpx.Client, - ) +def test_boot_load_records_the_blob_id_of_the_bytes_served_and_the_fetch_etag(): + body = _real_map_bytes() + client, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=body)], client_cls=httpx.Client) - cost_map = get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=client) + get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=client) - assert METADATA_KEY not in cost_map source = get_model_cost_map_source_info() assert source["source"] == "remote" assert source["etag"] == 'W/"boot"' - assert source["generated_at"] == _STAMP["generated_at"] - assert source["source_revision"] == _STAMP["source_revision"] + assert source["source_revision"] == git_blob_id(body) assert source["loaded_at"] is not None -def test_boot_load_fallback_to_the_backup_drops_the_remote_etag(): - """A boot that lands on the bundled backup reports the backup's own stamp and no ETag, even +def test_boot_load_fallback_to_the_backup_reports_its_blob_id_and_drops_the_remote_etag(): + """A boot that lands on the bundled backup reports the backup's own blob id and no ETag, even when an earlier load in the same process had fetched the remote map.""" remote, _ = _mock_client( - [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_stamped_map_bytes(_STAMP))], - client_cls=httpx.Client, + [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_real_map_bytes())], client_cls=httpx.Client ) get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=remote) failing, _ = _mock_client([httpx.Response(404)], client_cls=httpx.Client) - cost_map = get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=failing) + get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=failing) - assert METADATA_KEY not in cost_map source = get_model_cost_map_source_info() assert source["source"] == "local" assert source["etag"] is None - assert {"generated_at": source["generated_at"], "source_revision": source["source_revision"]} == _load_bundled_stamp() + assert source["source_revision"] == _bundled_blob_id() diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py index fb3583a7dd2..0490993a314 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py @@ -22,7 +22,6 @@ from .conftest import VOLATILE_KEYS, normalize _VOLATILE = VOLATILE_KEYS | frozenset({"timestamp"}) _PROVENANCE = { - "generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef0123456789abcdef01234567", "etag": 'W/"cost-map-etag"', } @@ -108,22 +107,24 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): assert update_payload["reload_revision"] == {"increment": 1} -def test_reload_model_cost_map_surfaces_provenance_and_keeps_metadata_out_of_the_model_list( +def test_reload_model_cost_map_surfaces_the_blob_id_of_the_bytes_served_on_every_status_surface( client, auth_as, monkeypatch, mock_prisma ): - """A real refetch through the reload route reports the file's stamp and the fetch ETag on every - status surface, while the ``_metadata`` block never shows up as a model anywhere.""" + """A real refetch through the reload route reports the git blob id of the exact bytes it fetched and + the fetch ETag on the reload response, the source route, and the schedule status alike.""" import httpx import litellm + from litellm.litellm_core_utils.get_model_cost_map import git_blob_id from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles _attach_litellm_config(mock_prisma) monkeypatch.setattr(ps, "prisma_client", mock_prisma) monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) - stamped = {**json.loads(_ROOT_COST_MAP.read_text()), "_metadata": {k: v for k, v in _PROVENANCE.items() if k != "etag"}} - served = httpx.Response(200, headers={"ETag": _PROVENANCE["etag"]}, content=json.dumps(stamped).encode()) + body = _ROOT_COST_MAP.read_bytes() + expected = {"source_revision": git_blob_id(body), "etag": _PROVENANCE["etag"]} + served = httpx.Response(200, headers={"ETag": _PROVENANCE["etag"]}, content=body) monkeypatch.setattr( "litellm.litellm_core_utils.get_model_cost_map._default_reload_client", lambda: httpx.AsyncClient(transport=httpx.MockTransport(lambda request: served)), @@ -144,18 +145,15 @@ def test_reload_model_cost_map_surfaces_provenance_and_keeps_metadata_out_of_the assert reload_response.status_code == 200 reload_body = reload_response.json() - assert {key: reload_body[key] for key in _PROVENANCE} == _PROVENANCE + assert {key: reload_body[key] for key in expected} == expected assert source_response.status_code == 200 source_body = source_response.json() - assert {key: source_body[key] for key in _PROVENANCE} == _PROVENANCE + assert {key: source_body[key] for key in expected} == expected assert source_body["source"] == "remote" assert status_response.status_code == 200 - assert {key: status_response.json()[key] for key in _PROVENANCE} == _PROVENANCE + assert {key: status_response.json()[key] for key in expected} == expected assert public_response.status_code == 200 - public_body = public_response.json() - assert "_metadata" not in public_body - assert "_metadata" not in litellm.model_cost - assert "gpt-4o" in public_body + assert "gpt-4o" in public_response.json() assert reload_body["models_count"] == len(litellm.model_cost) diff --git a/tests/test_litellm/test_auto_update_price_and_context_window_file.py b/tests/test_litellm/test_auto_update_price_and_context_window_file.py deleted file mode 100644 index d3cda09cd96..00000000000 --- a/tests/test_litellm/test_auto_update_price_and_context_window_file.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Tests for .github/scripts/auto_update_price_and_context_window_file.py.""" - -import importlib.util -import json -import re -import sys -from pathlib import Path -from typing import Final - -_REPO_ROOT: Final = Path(__file__).resolve().parents[2] -_MODULE_PATH: Final = _REPO_ROOT / ".github" / "scripts" / "auto_update_price_and_context_window_file.py" -_spec: Final = importlib.util.spec_from_file_location("auto_update_price_and_context_window_file", _MODULE_PATH) -script: Final = importlib.util.module_from_spec(_spec) -sys.modules[_spec.name] = script -_spec.loader.exec_module(script) - -_LOCAL_FILE: Final = "model_prices_and_context_window.json" -_GENERATED_AT: Final = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z") - - -def _openrouter_row(model_id: str) -> dict: - return {"id": model_id, "context_length": 8192, "pricing": {"prompt": "0.000001", "completion": "0.000002"}} - - -def _serve(openrouter_rows: list) -> object: - async def fetch_data(url: str) -> list: - return openrouter_rows if "openrouter" in url else [] - - return fetch_data - - -def _read_local(tmp_path: Path) -> dict: - return json.loads((tmp_path / _LOCAL_FILE).read_text()) - - -def test_main_stamps_provenance_only_when_the_sync_changed_the_file(tmp_path: Path, monkeypatch) -> None: - monkeypatch.chdir(tmp_path) - monkeypatch.setenv("GITHUB_SHA", "feedface") - monkeypatch.setattr(script, "fetch_data", _serve([_openrouter_row("acme/x")])) - (tmp_path / _LOCAL_FILE).write_text(json.dumps({"sample_spec": {"input_cost_per_token": "USD"}}, indent=4) + "\n") - - script.main() - - written = _read_local(tmp_path) - assert written["openrouter/acme/x"]["litellm_provider"] == "openrouter" - assert written["_metadata"]["source_revision"] == "feedface" - assert _GENERATED_AT.fullmatch(written["_metadata"]["generated_at"]) - - sentinel = {**written, "_metadata": {**written["_metadata"], "generated_at": "2000-01-01T00:00:00Z"}} - (tmp_path / _LOCAL_FILE).write_text(json.dumps(sentinel, indent=4) + "\n") - - script.main() - - assert _read_local(tmp_path) == sentinel diff --git a/tests/test_litellm/test_cost_map_guard.py b/tests/test_litellm/test_cost_map_guard.py index 1a60cf81164..1b4330ed62c 100644 --- a/tests/test_litellm/test_cost_map_guard.py +++ b/tests/test_litellm/test_cost_map_guard.py @@ -141,26 +141,6 @@ def test_bot_may_not_change_special_root_keys() -> None: assert _failures(head) == ("bot PRs may not change fallback_generalizations",) -STAMP: Final = {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef0123456789abcdef01234567"} - - -def test_bot_may_stamp_and_restamp_metadata() -> None: - stamped = _snapshot({**BASE_MAP, "_metadata": STAMP}) - assert _failures(stamped) == () - assert _failures(stamped, bot=False) == () - - restamped = _snapshot( - { - **BASE_MAP, - "_metadata": {**STAMP, "generated_at": "2026-09-14T00:00:00Z"}, - "fallback_generalizations": {"rules": []}, - } - ) - assert guard.guard_failures(stamped, restamped, MAP_FILES, True) == ( - "bot PRs may not change fallback_generalizations", - ) - - def _commit(repo: Path, cost_map: dict[str, object], message: str) -> str: text = _serialize(cost_map) (repo / guard.COST_MAP_PATH).write_text(text) diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index 3517f5840e8..c2c22c25998 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -98,25 +98,6 @@ def test_schema_rejects_malformed_entries(committed_schema: dict, entry: dict): assert not validator.is_valid({"some-model": entry}) -@pytest.mark.parametrize( - "metadata", - [ - "2026-09-07T00:00:00Z", - {"generated_at": "2026-09-07T00:00:00Z"}, - {"source_revision": "0123456789abcdef"}, - {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef", "author": "bot"}, - ], - ids=["not_an_object", "missing_revision", "missing_generated_at", "unknown_field"], -) -def test_schema_rejects_a_malformed_metadata_block(committed_schema: dict, metadata: object): - assert not build_validator(committed_schema).is_valid({"_metadata": metadata}) - - -def test_schema_accepts_the_provenance_stamp_as_a_non_model_root_key(committed_schema: dict): - stamp = {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "0123456789abcdef0123456789abcdef01234567"} - assert build_validator(committed_schema).is_valid({"_metadata": stamp}) - - def test_schema_accepts_minimal_and_unknown_optional_fields(committed_schema: dict): validator = build_validator(committed_schema) assert validator.is_valid({"some-model": {"litellm_provider": "openai"}}) diff --git a/tests/test_litellm/test_sync_together_ai_models.py b/tests/test_litellm/test_sync_together_ai_models.py index f58f573c208..b8a85bcfbdc 100644 --- a/tests/test_litellm/test_sync_together_ai_models.py +++ b/tests/test_litellm/test_sync_together_ai_models.py @@ -1,6 +1,5 @@ import importlib.util import json -import re from pathlib import Path from types import MappingProxyType @@ -370,58 +369,6 @@ def test_sync_is_idempotent_over_the_repo_cost_map() -> None: assert second.cost_map == first.cost_map -def test_stamp_metadata_adds_the_provenance_block_without_touching_models() -> None: - cost_map = {"sample_spec": {"input_cost_per_token": "USD"}, "together_ai/acme/x": {"mode": "chat"}} - - stamped = sync.stamp_metadata(cost_map, "2026-09-07T00:00:00Z", "feedface") - - assert stamped["_metadata"] == {"generated_at": "2026-09-07T00:00:00Z", "source_revision": "feedface"} - assert {key: value for key, value in stamped.items() if key != "_metadata"} == cost_map - assert "_metadata" not in cost_map - - -def _write_registry(repo_root: Path, cost_map: dict) -> None: - for relpath in sync.COST_MAP_RELPATHS: - target = repo_root / relpath - target.parent.mkdir(parents=True, exist_ok=True) - target.write_text(json.dumps(cost_map, indent=4) + "\n") - - -def _read_registries(repo_root: Path) -> tuple[dict, ...]: - return tuple(json.loads((repo_root / relpath).read_text()) for relpath in sync.COST_MAP_RELPATHS) - - -def test_write_stamps_provenance_into_both_files_only_when_the_sync_changed_them(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setenv("GITHUB_SHA", "feedface") - cost_map = json.loads((ROOT / "model_prices_and_context_window.json").read_text()) - dropped = next(f"together_ai/{model.id}" for model in RECORDED_CATALOG if f"together_ai/{model.id}" in cost_map) - _write_registry(tmp_path, {key: value for key, value in cost_map.items() if key not in {dropped, "_metadata"}}) - argv = ( - "--write", - "--models-json", - str(FIXTURES / "models_serverless.json"), - "--deprecations-md", - str(FIXTURES / "deprecations.md"), - "--repo-root", - str(tmp_path), - ) - - assert sync.main(argv) == 0 - - written = _read_registries(tmp_path) - assert written[0] == written[1] - assert dropped in written[0] - assert written[0]["_metadata"]["source_revision"] == "feedface" - assert re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", written[0]["_metadata"]["generated_at"]) - - sentinel = {**written[0], "_metadata": {**written[0]["_metadata"], "generated_at": "2000-01-01T00:00:00Z"}} - _write_registry(tmp_path, sentinel) - - assert sync.main(argv) == 0 - - assert _read_registries(tmp_path) == (sentinel, sentinel) - - def test_pr_body_lists_every_section_and_the_skipped_types() -> None: outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) body = sync.render_pr_body(outcome) diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index f6c9a4537a1..8a56a84ade7 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -21,7 +21,6 @@ from litellm._logging import ( verbose_logger, ) from litellm.integrations.custom_logger import CustomLogger -from litellm.litellm_core_utils.get_model_cost_map import RESERVED_TOP_LEVEL_KEYS from litellm.proxy.utils import is_valid_api_key from litellm.types.utils import ( CallTypes, @@ -1219,12 +1218,15 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): with open(prod_json, "r") as model_prices_file: actual_json = json.load(model_prices_file) assert isinstance(actual_json, dict) - model_entries: Final = { - key: value for key, value in actual_json.items() if key not in RESERVED_TOP_LEVEL_KEYS - } + actual_json.pop( + "sample_spec", None + ) # remove the sample, whose schema is inconsistent with the real data + actual_json.pop( + "fallback_generalizations", None + ) # reserved meta key, not a model entry # Validate schema - validate(model_entries, INTENDED_SCHEMA) + validate(actual_json, INTENDED_SCHEMA) # Validate cost values # Define exceptions for models that are allowed to have costs > 1 @@ -1235,7 +1237,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "runwayml/seedance2", # 4K output is 150 credits/second = $1.50/second ] - is_valid, violations = validate_model_cost_values(model_entries, exceptions) + is_valid, violations = validate_model_cost_values(actual_json, exceptions) if not is_valid: error_message = "Cost validation failed:\n" + "\n".join(violations) @@ -1266,7 +1268,8 @@ def test_max_tokens_consistency(): inconsistencies = [] for model_name, config in models.items(): - if model_name in RESERVED_TOP_LEVEL_KEYS: + # Skip the sample_spec + if model_name == "sample_spec": continue # Check if both max_tokens and max_output_tokens exist diff --git a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx index fde69675b72..101612993b0 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx @@ -33,15 +33,13 @@ const remoteSource = { is_env_forced: false, fallback_reason: null, loaded_at: null, - generated_at: null, source_revision: null, etag: null, model_count: 1234, }; const provenance = { loaded_at: "2026-09-07T10:00:00Z", - generated_at: "2026-09-06T23:38:47Z", - source_revision: "cd681a573fd9f5b6f15a1355f46178e4e9d374d2", + source_revision: "4273ec544726bf255ea920533e209e6022653bb4", etag: 'W/"eb8e9a53f4cc284b"', }; @@ -66,24 +64,22 @@ describe("PriceDataReload", () => { render(); expect(await screen.findByText("Source revision:")).toBeInTheDocument(); - expect(screen.getByText("cd681a573fd9")).toBeInTheDocument(); + expect(screen.getByText("4273ec544726")).toBeInTheDocument(); expect(screen.getByText("ETag:")).toBeInTheDocument(); expect(screen.getByText('W/"eb8e9a53f4cc284b"')).toBeInTheDocument(); - expect(screen.getByText("Generated at:")).toBeInTheDocument(); - expect(screen.getByText(new Date(provenance.generated_at).toLocaleString())).toBeInTheDocument(); expect(screen.getByText("Loaded at:")).toBeInTheDocument(); expect(screen.getByText(new Date(provenance.loaded_at).toLocaleString())).toBeInTheDocument(); }); - it("shows a malformed generated_at stamp as-is instead of Invalid Date", async () => { + it("shows a malformed loaded_at as-is instead of Invalid Date", async () => { vi.mocked(getModelCostMapSource).mockResolvedValue({ ...remoteSource, ...provenance, - generated_at: "yesterday-ish", + loaded_at: "yesterday-ish", } as never); render(); - expect(await screen.findByText("Generated at:")).toBeInTheDocument(); + expect(await screen.findByText("Loaded at:")).toBeInTheDocument(); expect(screen.getByText("yesterday-ish")).toBeInTheDocument(); expect(screen.queryByText("Invalid Date")).not.toBeInTheDocument(); }); @@ -92,7 +88,6 @@ describe("PriceDataReload", () => { render(); expect(await screen.findByText("Pricing Data Source")).toBeInTheDocument(); - expect(screen.queryByText("Generated at:")).not.toBeInTheDocument(); expect(screen.queryByText("Source revision:")).not.toBeInTheDocument(); expect(screen.queryByText("ETag:")).not.toBeInTheDocument(); expect(screen.queryByText("Loaded at:")).not.toBeInTheDocument(); diff --git a/ui/litellm-dashboard/src/components/price_data_reload.tsx b/ui/litellm-dashboard/src/components/price_data_reload.tsx index c152916f271..e5977a1b6e3 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.tsx @@ -50,7 +50,6 @@ interface CostMapSourceInfo { is_env_forced: boolean; fallback_reason: string | null; loaded_at: string | null; - generated_at: string | null; source_revision: string | null; etag: string | null; model_count: number; @@ -105,13 +104,6 @@ const formatDateTime = (dateTimeString: string | null) => { const CostMapProvenanceRows: React.FC<{ sourceInfo: CostMapSourceInfo }> = ({ sourceInfo }) => ( <> - {sourceInfo.generated_at && ( -
- Generated at: - {formatDateTime(sourceInfo.generated_at)} -
- )} - {sourceInfo.source_revision && (
Source revision: diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 7b9b24c9627..fc73d8264ef 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -8685,7 +8685,7 @@ export interface paths { * - is_env_forced: true if LITELLM_LOCAL_MODEL_COST_MAP=True forced local usage * - fallback_reason: human-readable reason why remote failed (null on success) * - loaded_at: when this pod last loaded the map - * - generated_at, source_revision: the _metadata stamp inside the loaded file + * - source_revision: git blob id of the loaded file, what git rev-parse : prints for it * - etag: the ETag of the remote fetch (null for the bundled backup) * - model_count: number of models in the currently loaded cost map */ From 3b199cd3da3fc97a6a373a1247fb798fa2353ca1 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:11:58 -0700 Subject: [PATCH 019/105] fix(azure_ai): price seven Foundry catalog names and charge the model router fee once Add cost map entries for azure_ai/gpt-chat-latest, codex-mini, whisper, model-router, cohere-command-a, grok-4-20-reasoning, and grok-4-20-non-reasoning, priced from the live Azure AI Foundry and Azure OpenAI pricing pages and the Azure Retail Prices API. Skip the model router flat fee when the response model is the router entry itself, since the generic cost already priced that fee. Before, azure_ai/model_router charged it twice. Resolves LIT-3157 --- litellm/llms/azure_ai/cost_calculator.py | 76 +++----- ...odel_prices_and_context_window_backup.json | 130 +++++++++++++ model_prices_and_context_window.json | 130 +++++++++++++ .../azure_ai/test_azure_ai_cost_calculator.py | 26 +++ ...azure_ai_foundry_catalog_model_metadata.py | 176 ++++++++++++++++++ 5 files changed, 492 insertions(+), 46 deletions(-) create mode 100644 tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py diff --git a/litellm/llms/azure_ai/cost_calculator.py b/litellm/llms/azure_ai/cost_calculator.py index 95f536296a2..141148f06e7 100644 --- a/litellm/llms/azure_ai/cost_calculator.py +++ b/litellm/llms/azure_ai/cost_calculator.py @@ -56,6 +56,27 @@ def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> fl return 0.0 +ROUTER_FEE_ENTRY_NAMES: Final = frozenset({"model-router", "model_router"}) + + +def _prices_router_fee_itself(model: str) -> bool: + return model.lower().rsplit("/", 1)[-1] in ROUTER_FEE_ENTRY_NAMES + + +def _base_cost_per_token(model: str, usage: Usage, service_tier: str | None) -> tuple[float, float] | None: + try: + return generic_cost_per_token( + model=model, usage=usage, custom_llm_provider="azure_ai", service_tier=service_tier + ) + except Exception as e: + if not _is_azure_model_router(model): + raise + verbose_logger.debug( + "Azure AI Model Router: model '%s' not in cost map, calculating routing flat cost only. Error: %s", model, e + ) + return None + + def cost_per_token( model: str, usage: Usage, @@ -66,9 +87,9 @@ def cost_per_token( """ Calculate the cost per token for Azure AI models. - For Azure AI Foundry Model Router: - - Adds a flat cost of $0.14 per million input tokens (from model_prices_and_context_window.json) - - Plus the cost of the actual model used (handled by generic_cost_per_token) + For Azure AI Foundry Model Router the routing fee (the azure_ai/model_router entry, $0.14 per + million input tokens) is added on top of the routed model's cost. When the response model is + the router entry itself, generic_cost_per_token has already charged that fee. Args: model: str, the model name without provider prefix (from response) @@ -83,49 +104,12 @@ def cost_per_token( ValueError: If the model is not found in the cost map and cost cannot be calculated (except for Model Router models where we return just the routing flat cost) """ - prompt_cost = 0.0 - completion_cost = 0.0 - - # Determine if this was a model router request - # Check both the response model and the request model is_router_request: Final = _is_azure_model_router(model) or ( request_model is not None and _is_azure_model_router(request_model) ) - - # Calculate base cost using generic cost calculator - # This may raise an exception if the model is not in the cost map - try: - prompt_cost, completion_cost = generic_cost_per_token( - model=model, - usage=usage, - custom_llm_provider="azure_ai", - service_tier=service_tier, - ) - except Exception as e: - # For Model Router, the model name (e.g., "azure-model-router") may not be in the cost map - # because it's a routing service, not an actual model. In this case, we continue - # to calculate just the routing flat cost. - if not _is_azure_model_router(model): - # Re-raise for non-router models - they should have pricing defined - raise - verbose_logger.debug( - "Azure AI Model Router: model '%s' not in cost map, calculating routing flat cost only. Error: %s", model, e - ) - - # Add flat cost for Azure Model Router - # The flat cost is defined in model_prices_and_context_window.json for azure_ai/model_router - if is_router_request: - # Use the request model for flat cost calculation if available, otherwise use response model - router_model_for_calc: Final = request_model if request_model else model - router_flat_cost: Final = calculate_azure_model_router_flat_cost(router_model_for_calc, usage.prompt_tokens) - - if router_flat_cost > 0: - verbose_logger.debug( - f"Azure AI Model Router flat cost: ${router_flat_cost:.6f} " - f"({usage.prompt_tokens} tokens × ${router_flat_cost / usage.prompt_tokens:.9f}/token)" - ) - - # Add flat cost to prompt cost - prompt_cost += router_flat_cost - - return prompt_cost, completion_cost + base_cost: Final = _base_cost_per_token(model=model, usage=usage, service_tier=service_tier) + prompt_cost, completion_cost = base_cost if base_cost is not None else (0.0, 0.0) + if not is_router_request or (base_cost is not None and _prices_router_fee_itself(model)): + return prompt_cost, completion_cost + router_flat_cost: Final = calculate_azure_model_router_flat_cost(request_model or model, usage.prompt_tokens) + return prompt_cost + router_flat_cost, completion_cost diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b1ffc1583e4..6649fa831d7 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -3581,6 +3581,82 @@ "supports_xhigh_reasoning_effort": true, "supports_minimal_reasoning_effort": false }, + "azure_ai/gpt-chat-latest": { + "cache_read_input_token_cost": 5e-07, + "deprecation_date": "2026-12-02", + "input_cost_per_token": 5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 3e-05, + "reasoning_effort_levels": [ + "medium" + ], + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", + "supported_endpoints": [ + "/v1/chat/completions", + "/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_ai/codex-mini": { + "cache_read_input_token_cost": 3.75e-07, + "deprecation_date": "2026-11-15", + "input_cost_per_token": 1.5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "max_tokens": 100000, + "mode": "responses", + "output_cost_per_token": 6e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", + "supported_endpoints": [ + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": 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_ai/whisper": { + "deprecation_date": "2026-12-15", + "input_cost_per_second": 0.0001, + "litellm_provider": "azure_ai", + "mode": "audio_transcription", + "output_cost_per_second": 0.0001, + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" + }, "azure_ai/gpt-5.5-2026-04-23": { "cache_read_input_token_cost": 5e-07, "cache_read_input_token_cost_above_272k_tokens": 1e-06, @@ -3991,6 +4067,17 @@ "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" }, + "azure_ai/model-router": { + "input_cost_per_token": 1.4e-07, + "output_cost_per_token": 0, + "litellm_provider": "azure_ai", + "max_input_tokens": 1048576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", + "comment": "Catalog-name twin of azure_ai/model_router: the flat $0.14 per M input tokens is the router's own fee, the routed model is priced on top of it" + }, "azure/eu/gpt-4o-2024-08-06": { "deprecation_date": "2027-04-14", "cache_read_input_token_cost": 1.375e-06, @@ -10302,6 +10389,18 @@ "/v1/ocr" ] }, + "azure_ai/cohere-command-a": { + "input_cost_per_token": 2.5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 131072, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "chat", + "output_cost_per_token": 1e-05, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/", + "supports_function_calling": true, + "supports_tool_choice": true + }, "azure_ai/doc-intelligence/prebuilt-read": { "litellm_provider": "azure_ai", "ocr_cost_per_page": 0.0015, @@ -10653,6 +10752,37 @@ "supports_vision": true, "supports_web_search": true }, + "azure_ai/grok-4-20-reasoning": { + "input_cost_per_token": 1.25e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 262000, + "max_output_tokens": 8192, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_reasoning": true + }, + "azure_ai/grok-4-20-non-reasoning": { + "input_cost_per_token": 1.25e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 262000, + "max_output_tokens": 8192, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, "azure_ai/grok-4-fast-non-reasoning": { "deprecation_date": "2026-05-01", "input_cost_per_token": 2e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b1ffc1583e4..6649fa831d7 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -3581,6 +3581,82 @@ "supports_xhigh_reasoning_effort": true, "supports_minimal_reasoning_effort": false }, + "azure_ai/gpt-chat-latest": { + "cache_read_input_token_cost": 5e-07, + "deprecation_date": "2026-12-02", + "input_cost_per_token": 5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 3e-05, + "reasoning_effort_levels": [ + "medium" + ], + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", + "supported_endpoints": [ + "/v1/chat/completions", + "/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_ai/codex-mini": { + "cache_read_input_token_cost": 3.75e-07, + "deprecation_date": "2026-11-15", + "input_cost_per_token": 1.5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "max_tokens": 100000, + "mode": "responses", + "output_cost_per_token": 6e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", + "supported_endpoints": [ + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": 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_ai/whisper": { + "deprecation_date": "2026-12-15", + "input_cost_per_second": 0.0001, + "litellm_provider": "azure_ai", + "mode": "audio_transcription", + "output_cost_per_second": 0.0001, + "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" + }, "azure_ai/gpt-5.5-2026-04-23": { "cache_read_input_token_cost": 5e-07, "cache_read_input_token_cost_above_272k_tokens": 1e-06, @@ -3991,6 +4067,17 @@ "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" }, + "azure_ai/model-router": { + "input_cost_per_token": 1.4e-07, + "output_cost_per_token": 0, + "litellm_provider": "azure_ai", + "max_input_tokens": 1048576, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", + "comment": "Catalog-name twin of azure_ai/model_router: the flat $0.14 per M input tokens is the router's own fee, the routed model is priced on top of it" + }, "azure/eu/gpt-4o-2024-08-06": { "deprecation_date": "2027-04-14", "cache_read_input_token_cost": 1.375e-06, @@ -10302,6 +10389,18 @@ "/v1/ocr" ] }, + "azure_ai/cohere-command-a": { + "input_cost_per_token": 2.5e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 131072, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "chat", + "output_cost_per_token": 1e-05, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/", + "supports_function_calling": true, + "supports_tool_choice": true + }, "azure_ai/doc-intelligence/prebuilt-read": { "litellm_provider": "azure_ai", "ocr_cost_per_page": 0.0015, @@ -10653,6 +10752,37 @@ "supports_vision": true, "supports_web_search": true }, + "azure_ai/grok-4-20-reasoning": { + "input_cost_per_token": 1.25e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 262000, + "max_output_tokens": 8192, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_reasoning": true + }, + "azure_ai/grok-4-20-non-reasoning": { + "input_cost_per_token": 1.25e-06, + "litellm_provider": "azure_ai", + "max_input_tokens": 262000, + "max_output_tokens": 8192, + "max_tokens": 8192, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/", + "supports_function_calling": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, "azure_ai/grok-4-fast-non-reasoning": { "deprecation_date": "2026-05-01", "input_cost_per_token": 2e-07, diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py index 9612d97d946..80cd99bd46b 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py @@ -528,3 +528,29 @@ def test_mai_thinking_1_model_info_and_cost(local_model_cost_map): assert model_info["supports_function_calling"] is True assert prompt_cost == pytest.approx(2.0) assert completion_cost == pytest.approx(8.0) + + +@pytest.mark.usefixtures("local_model_cost_map") +@pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) +def test_router_entry_as_response_model_charges_the_fee_once(router_entry_name: str) -> None: + usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) + prompt_cost, completion_cost = cost_per_token(model=router_entry_name, usage=usage) + assert prompt_cost == pytest.approx(0.14, rel=1e-9) + assert completion_cost == 0.0 + + +@pytest.mark.usefixtures("local_model_cost_map") +def test_unmapped_router_deployment_name_still_charges_the_fee() -> None: + usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) + prompt_cost, completion_cost = cost_per_token(model="azure-model-router", usage=usage) + assert prompt_cost == pytest.approx(0.14, rel=1e-9) + assert completion_cost == 0.0 + + +@pytest.mark.usefixtures("local_model_cost_map") +def test_routed_model_response_adds_the_fee_on_top() -> None: + usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) + routed_prompt_cost, _ = cost_per_token(model="gpt-5-nano", usage=usage) + prompt_cost, _ = cost_per_token(model="gpt-5-nano", usage=usage, request_model="azure_ai/model-router") + assert routed_prompt_cost > 0 + assert prompt_cost == pytest.approx(routed_prompt_cost + 0.14, rel=1e-9) diff --git a/tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py b/tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py new file mode 100644 index 00000000000..9c5ca26a89c --- /dev/null +++ b/tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py @@ -0,0 +1,176 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import Final + +import pytest +from pydantic import TypeAdapter + +from litellm import cost_per_token, get_model_info +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + +REPO_ROOT: Final = Path(__file__).parents[2] +COST_MAP_ADAPTER: Final = TypeAdapter(dict[str, dict[str, object]]) +AZURE_OPENAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" +FOUNDRY_AOAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/" +FOUNDRY_COHERE_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/" +FOUNDRY_GROK_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/" + + +@dataclass(frozen=True, slots=True) +class TokenPricedCatalogModel: + catalog_name: str + mode: str + source: str + input_cost_per_token: float + output_cost_per_token: float + max_input_tokens: int + max_output_tokens: int + cache_read_input_token_cost: float | None + supported_flags: tuple[str, ...] + + +TOKEN_PRICED_MODELS: Final = ( + TokenPricedCatalogModel( + catalog_name="gpt-chat-latest", + mode="chat", + source=AZURE_OPENAI_PRICING, + input_cost_per_token=5e-06, + output_cost_per_token=3e-05, + max_input_tokens=200000, + max_output_tokens=128000, + cache_read_input_token_cost=5e-07, + supported_flags=( + "supports_function_calling", + "supports_prompt_caching", + "supports_reasoning", + "supports_response_schema", + "supports_tool_choice", + "supports_vision", + "supports_web_search", + ), + ), + TokenPricedCatalogModel( + catalog_name="codex-mini", + mode="responses", + source=AZURE_OPENAI_PRICING, + input_cost_per_token=1.5e-06, + output_cost_per_token=6e-06, + max_input_tokens=200000, + max_output_tokens=100000, + cache_read_input_token_cost=3.75e-07, + supported_flags=("supports_function_calling", "supports_prompt_caching", "supports_reasoning", "supports_vision"), + ), + TokenPricedCatalogModel( + catalog_name="model-router", + mode="chat", + source=FOUNDRY_AOAI_PRICING, + input_cost_per_token=1.4e-07, + output_cost_per_token=0.0, + max_input_tokens=1048576, + max_output_tokens=32768, + cache_read_input_token_cost=None, + supported_flags=(), + ), + TokenPricedCatalogModel( + catalog_name="cohere-command-a", + mode="chat", + source=FOUNDRY_COHERE_PRICING, + input_cost_per_token=2.5e-06, + output_cost_per_token=1e-05, + max_input_tokens=131072, + max_output_tokens=4096, + cache_read_input_token_cost=None, + supported_flags=("supports_function_calling", "supports_tool_choice"), + ), + TokenPricedCatalogModel( + catalog_name="grok-4-20-reasoning", + mode="chat", + source=FOUNDRY_GROK_PRICING, + input_cost_per_token=1.25e-06, + output_cost_per_token=2.5e-06, + max_input_tokens=262000, + max_output_tokens=8192, + cache_read_input_token_cost=None, + supported_flags=( + "supports_function_calling", + "supports_reasoning", + "supports_response_schema", + "supports_tool_choice", + "supports_vision", + "supports_web_search", + ), + ), + TokenPricedCatalogModel( + catalog_name="grok-4-20-non-reasoning", + mode="chat", + source=FOUNDRY_GROK_PRICING, + input_cost_per_token=1.25e-06, + output_cost_per_token=2.5e-06, + max_input_tokens=262000, + max_output_tokens=8192, + cache_read_input_token_cost=None, + supported_flags=( + "supports_function_calling", + "supports_response_schema", + "supports_tool_choice", + "supports_vision", + "supports_web_search", + ), + ), +) +CATALOG_NAMES: Final = tuple(spec.catalog_name for spec in TOKEN_PRICED_MODELS) + ("whisper",) + + +def _cost_map_entry(path: Path, catalog_name: str) -> dict[str, object]: + return COST_MAP_ADAPTER.validate_json(path.read_bytes())[f"azure_ai/{catalog_name}"] + + +@pytest.mark.usefixtures("local_model_cost_map") +@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) +def test_azure_ai_catalog_name_is_priced_and_routed(spec: TokenPricedCatalogModel) -> None: + routed_model, provider, _, _ = get_llm_provider(model=f"azure_ai/{spec.catalog_name}") + assert (routed_model, provider) == (spec.catalog_name, "azure_ai") + + info = get_model_info(model=routed_model, custom_llm_provider=provider) + assert info["litellm_provider"] == "azure_ai" + assert info["mode"] == spec.mode + assert info["input_cost_per_token"] == spec.input_cost_per_token + assert info["output_cost_per_token"] == spec.output_cost_per_token + assert info["cache_read_input_token_cost"] == spec.cache_read_input_token_cost + assert info["max_input_tokens"] == spec.max_input_tokens + assert info["max_output_tokens"] == spec.max_output_tokens + assert info["max_tokens"] == spec.max_output_tokens + for flag in spec.supported_flags: + assert info[flag] is True, flag + + +@pytest.mark.usefixtures("local_model_cost_map") +@pytest.mark.parametrize( + "spec", [spec for spec in TOKEN_PRICED_MODELS if spec.catalog_name != "model-router"], ids=lambda spec: spec.catalog_name +) +def test_azure_ai_catalog_name_costs_a_million_tokens_at_list_price(spec: TokenPricedCatalogModel) -> None: + prompt_cost, completion_cost = cost_per_token( + model=f"azure_ai/{spec.catalog_name}", prompt_tokens=1_000_000, completion_tokens=1_000_000 + ) + assert prompt_cost == pytest.approx(spec.input_cost_per_token * 1_000_000) + assert completion_cost == pytest.approx(spec.output_cost_per_token * 1_000_000) + + +@pytest.mark.usefixtures("local_model_cost_map") +def test_azure_ai_whisper_catalog_name_is_priced_per_second() -> None: + routed_model, provider, _, _ = get_llm_provider(model="azure_ai/whisper") + assert (routed_model, provider) == ("whisper", "azure_ai") + + info = get_model_info(model=routed_model, custom_llm_provider=provider) + assert info["mode"] == "audio_transcription" + assert info["input_cost_per_second"] == 0.0001 + assert info["output_cost_per_second"] == 0.0001 + + +@pytest.mark.parametrize("catalog_name", CATALOG_NAMES) +def test_azure_ai_catalog_entry_source_and_backup_match(catalog_name: str) -> None: + main_entry = _cost_map_entry(REPO_ROOT / "model_prices_and_context_window.json", catalog_name) + backup_entry = _cost_map_entry(REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json", catalog_name) + + assert str(main_entry["source"]).startswith("https://azure.microsoft.com/en-us/pricing/details/") + assert backup_entry == main_entry From bb52fd44fa425033313c7eac85bb5edaf92d71be Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:20:41 -0700 Subject: [PATCH 020/105] fix(cost_map): label the card's loaded_at as per-worker and cover the integrity-failure fallback --- .../test_get_model_cost_map.py | 20 +++++++++++++++++++ .../src/components/price_data_reload.test.tsx | 2 ++ .../src/components/price_data_reload.tsx | 9 +++++++++ 3 files changed, 31 insertions(+) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index d9fe6d2f979..7c3ad283639 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -684,3 +684,23 @@ def test_boot_load_fallback_to_the_backup_reports_its_blob_id_and_drops_the_remo assert source["source"] == "local" assert source["etag"] is None assert source["source_revision"] == _bundled_blob_id() + + +def test_boot_load_that_fails_the_integrity_check_reports_the_backup_not_the_rejected_fetch(): + """A fetch that succeeds but fails integrity validation is thrown away, so the provenance must + describe the backup that got loaded, never the ETag or bytes of the map that was rejected.""" + remote, _ = _mock_client( + [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_real_map_bytes())], client_cls=httpx.Client + ) + get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=remote) + shrunk_body = b'{"gpt-5.4-mini": {"mode": "chat", "input_cost_per_token": 1e-06, "output_cost_per_token": 2e-06}}' + shrunk, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"shrunk"'}, content=shrunk_body)], client_cls=httpx.Client) + + get_model_cost_map(url=_URL, sleep=_SyncSleepRecorder(), rng=random.Random(0), client=shrunk) + + source = get_model_cost_map_source_info() + assert source["source"] == "local" + assert source["fallback_reason"] == "Remote data failed integrity validation" + assert source["etag"] is None + assert source["source_revision"] == _bundled_blob_id() + assert source["source_revision"] != git_blob_id(shrunk_body) diff --git a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx index 101612993b0..3566ec2c3f2 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx @@ -68,6 +68,7 @@ describe("PriceDataReload", () => { expect(screen.getByText("ETag:")).toBeInTheDocument(); expect(screen.getByText('W/"eb8e9a53f4cc284b"')).toBeInTheDocument(); expect(screen.getByText("Loaded at:")).toBeInTheDocument(); + expect(screen.getByText(/worker that answered this request/)).toBeInTheDocument(); expect(screen.getByText(new Date(provenance.loaded_at).toLocaleString())).toBeInTheDocument(); }); @@ -91,6 +92,7 @@ describe("PriceDataReload", () => { expect(screen.queryByText("Source revision:")).not.toBeInTheDocument(); expect(screen.queryByText("ETag:")).not.toBeInTheDocument(); expect(screen.queryByText("Loaded at:")).not.toBeInTheDocument(); + expect(screen.queryByText(/worker that answered this request/)).not.toBeInTheDocument(); }); it("confirms an immediate reload and refreshes dependent data", async () => { diff --git a/ui/litellm-dashboard/src/components/price_data_reload.tsx b/ui/litellm-dashboard/src/components/price_data_reload.tsx index e5977a1b6e3..1363e306a31 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.tsx @@ -132,6 +132,15 @@ const CostMapProvenanceRows: React.FC<{ sourceInfo: CostMapSourceInfo }> = ({ so {formatDateTime(sourceInfo.loaded_at)}
)} + + {sourceInfo.loaded_at && ( +
+ + + Reported by the worker that answered this request. Other workers pick up a reload on their next poll + +
+ )} ); From 95402ccb711cdcd93f1c296579b18ec8bd32cab4 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:36:40 -0700 Subject: [PATCH 021/105] test(azure_ai): move the Foundry catalog metadata test into the mapped azure_ai directory The new metadata test sat at the top of tests/test_litellm. The azure_ai metadata tests live in tests/test_litellm/llms/azure_ai next to the cost calculator test, so this moves it there and bumps its repo-root lookup by the two extra directory levels. No test changes. --- .../azure_ai}/test_azure_ai_foundry_catalog_model_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/test_litellm/{ => llms/azure_ai}/test_azure_ai_foundry_catalog_model_metadata.py (99%) diff --git a/tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py similarity index 99% rename from tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py rename to tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py index 9c5ca26a89c..1b4e83438a6 100644 --- a/tests/test_litellm/test_azure_ai_foundry_catalog_model_metadata.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py @@ -8,7 +8,7 @@ from pydantic import TypeAdapter from litellm import cost_per_token, get_model_info from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider -REPO_ROOT: Final = Path(__file__).parents[2] +REPO_ROOT: Final = Path(__file__).parents[4] COST_MAP_ADAPTER: Final = TypeAdapter(dict[str, dict[str, object]]) AZURE_OPENAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" FOUNDRY_AOAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/" From ba91588b15d308daf229f464b9aa89a0f7480afd Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 7 Sep 2026 18:38:12 -0700 Subject: [PATCH 022/105] fix(proxy): price one cost estimate at one moment The totals, the per-token-type lines and the reported rates each resolved off-peak pricing on their own clock read, so a quote taken as a window opened could bill on one side of the boundary and report rates from the other. /cost/estimate now pins a billing moment for the whole quote, and every rate lookup answers for the pinned moment instead of reading the clock again Claude-Session: https://claude.ai/code/session_011Tn3657NkV6ojLqewL64Kb --- litellm/_internal_context.py | 24 +++++++++++ .../litellm_core_utils/llm_cost_calc/utils.py | 9 ++-- .../cost_tracking_settings.py | 43 +++++++++++-------- .../llm_cost_calc/test_llm_cost_calc_utils.py | 42 ++++++++++++++++++ .../test_cost_tracking_settings.py | 30 +++++++++++++ 5 files changed, 126 insertions(+), 22 deletions(-) diff --git a/litellm/_internal_context.py b/litellm/_internal_context.py index f856fe0f2b3..8132008731f 100644 --- a/litellm/_internal_context.py +++ b/litellm/_internal_context.py @@ -6,9 +6,33 @@ be settable from user input. Context variables are scoped to the current asyncio task and cannot be injected via HTTP request bodies. """ +from collections.abc import Generator +from contextlib import contextmanager from contextvars import ContextVar +from datetime import datetime, timezone from typing import Final # When True, suppresses async logging and billing for internal sub-calls # (e.g., emulated file-search steps that make nested LLM calls). is_internal_call: Final[ContextVar[bool]] = ContextVar("is_internal_call", default=False) + +# One request prices its totals, its per-token-type lines and the rates it reports on +# separate code paths. Each reads the clock for off-peak pricing, so without a pinned +# moment they can land on either side of a window boundary and disagree with each other. +_billing_time: Final[ContextVar[datetime | None]] = ContextVar("billing_time", default=None) + + +@contextmanager +def pinned_billing_time(moment: datetime) -> Generator[None]: + """Price every rate lookup inside this block at ``moment`` rather than at each one's own clock read.""" + token: Final = _billing_time.set(moment) + try: + yield + finally: + _billing_time.reset(token) + + +def current_billing_time() -> datetime: + """The pinned billing moment, or now in UTC outside a pinned block.""" + pinned: Final = _billing_time.get() + return pinned if pinned is not None else datetime.now(timezone.utc) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 2bb15fb4c48..e2168528e6b 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -10,6 +10,7 @@ from typing import Any, Final, Literal, TypedDict, cast from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import litellm +from litellm._internal_context import current_billing_time from litellm._logging import verbose_logger from litellm.litellm_core_utils.llm_cost_calc.tiered_pricing import ( select_tier_for_input, @@ -306,7 +307,7 @@ def _is_within_off_peak_window(off_peak_hours_utc: str | Sequence[str], current_ than being localised, so callers must pass datetime.now(timezone.utc), never datetime.now(), or every window shifts by the host's offset. """ - reference: Final = current_time if current_time is not None else datetime.now(timezone.utc) + reference: Final = current_time if current_time is not None else current_billing_time() now: Final = (reference.astimezone(timezone.utc) if reference.tzinfo is not None else reference).time() windows: Final = (off_peak_hours_utc,) if isinstance(off_peak_hours_utc, str) else off_peak_hours_utc for window in windows: @@ -393,7 +394,7 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None = rules: the flat hours_utc windows, which apply every day, or any entry in windows, whose hours apply only on its weekdays. """ - reference: Final = current_time if current_time is not None else datetime.now(timezone.utc) + reference: Final = current_time if current_time is not None else current_billing_time() reference_utc: Final = ( reference.astimezone(timezone.utc) if reference.tzinfo is not None else reference.replace(tzinfo=timezone.utc) ) @@ -1187,7 +1188,7 @@ def generic_cost_per_token( usage.prompt_tokens - cache_hit - audio_tokens - cache_creation - image_tokens - video_tokens, 0 ) - billing_time: Final = current_time if current_time is not None else datetime.now(timezone.utc) + billing_time: Final = current_time if current_time is not None else current_billing_time() ( prompt_base_cost, completion_base_cost, @@ -1379,7 +1380,7 @@ def _cost_map_billed_rates( vertex_location: str | None, current_time: datetime | None, ) -> BilledTokenRates: - billing_time: Final = current_time if current_time is not None else datetime.now(timezone.utc) + billing_time: Final = current_time if current_time is not None else current_billing_time() ( prompt_base_cost, completion_base_cost, diff --git a/litellm/proxy/management_endpoints/cost_tracking_settings.py b/litellm/proxy/management_endpoints/cost_tracking_settings.py index 17d82fd17e3..1faa66584d5 100644 --- a/litellm/proxy/management_endpoints/cost_tracking_settings.py +++ b/litellm/proxy/management_endpoints/cost_tracking_settings.py @@ -18,6 +18,7 @@ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel import litellm +from litellm._internal_context import current_billing_time, pinned_billing_time from litellm._logging import verbose_proxy_logger from litellm.cost_calculator import completion_cost from litellm.litellm_core_utils.llm_cost_calc.utils import get_billed_token_rates @@ -631,33 +632,39 @@ async def estimate_cost( function_id="cost-estimate", ) - # Use completion_cost which handles all the logic including margins/discounts - try: - cost_per_request: Final = completion_cost( - completion_response=mock_response, + # The totals, the per-token-type lines and the reported rates each resolve pricing on their + # own path. Pinning one moment keeps an off-peak window that opens mid-quote from splitting them. + billed_at: Final = current_billing_time() + with pinned_billing_time(billed_at): + # Use completion_cost which handles all the logic including margins/discounts + try: + cost_per_request: Final = completion_cost( + completion_response=mock_response, + model=resolved_model, + custom_llm_provider=resolved_provider, + custom_cost_per_token=resolved.custom_cost_per_token, + litellm_logging_obj=litellm_logging_obj, + ) + except Exception as e: + raise HTTPException( + status_code=404, + detail={ + "error": f"Could not calculate cost for model '{request.model}' (resolved to '{resolved_model}'): {e}" + }, + ) + + rates: Final = get_billed_token_rates( model=resolved_model, custom_llm_provider=resolved_provider, + usage=usage, custom_cost_per_token=resolved.custom_cost_per_token, - litellm_logging_obj=litellm_logging_obj, - ) - except Exception as e: - raise HTTPException( - status_code=404, - detail={ - "error": f"Could not calculate cost for model '{request.model}' (resolved to '{resolved_model}'): {e}" - }, + current_time=billed_at, ) per_request: Final = _cost_lines(cost_per_request, litellm_logging_obj.cost_breakdown) daily: Final = per_request.times(request.num_requests_per_day) monthly: Final = per_request.times(request.num_requests_per_month) - rates: Final = get_billed_token_rates( - model=resolved_model, - custom_llm_provider=resolved_provider, - usage=usage, - custom_cost_per_token=resolved.custom_cost_per_token, - ) model_info: Final = _lookup_model_info(resolved_model) mapped_provider: Final = model_info.get("litellm_provider") if model_info is not None else None custom_llm_provider: Final = mapped_provider if mapped_provider is not None else resolved_provider diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 4de3c059e63..90178428018 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -1,9 +1,11 @@ import json +from datetime import datetime, timezone import pytest from fastapi.testclient import TestClient import litellm +from litellm._internal_context import pinned_billing_time from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import ( StandardBuiltInToolCostTracking, ) @@ -3981,6 +3983,46 @@ def test_billed_token_rates_follow_the_token_tier_the_breakdown_bills_at(monkeyp assert breakdown.reasoning_cost == pytest.approx(200 * rates.output_cost_per_reasoning_token) +def test_a_pinned_billing_time_prices_the_totals_and_the_reported_rates_at_one_moment(monkeypatch): + """Totals and reported rates resolve off-peak pricing on separate paths that each read the + clock, so a window opening between the two reads used to leave them describing one request + at two different prices. Pinned, both must answer for the pinned moment.""" + monkeypatch.setitem( + litellm.model_cost, + "off-peak-model", + { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "off_peak_pricing": { + "hours_utc": "02:00-03:00", + "input_cost_per_token": 1e-6, + "output_cost_per_token": 5e-6, + }, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) + + with pinned_billing_time(datetime(2026, 1, 1, 2, 30, tzinfo=timezone.utc)): + off_peak_prompt_cost, off_peak_completion_cost = generic_cost_per_token( + model="off-peak-model", usage=usage, custom_llm_provider="openai" + ) + off_peak_rates = get_billed_token_rates(model="off-peak-model", custom_llm_provider="openai", usage=usage) + with pinned_billing_time(datetime(2026, 1, 1, 12, 30, tzinfo=timezone.utc)): + peak_prompt_cost, peak_completion_cost = generic_cost_per_token( + model="off-peak-model", usage=usage, custom_llm_provider="openai" + ) + peak_rates = get_billed_token_rates(model="off-peak-model", custom_llm_provider="openai", usage=usage) + + assert off_peak_rates.input_cost_per_token == pytest.approx(1e-6) + assert peak_rates.input_cost_per_token == pytest.approx(3e-6) + assert off_peak_prompt_cost == pytest.approx(1000 * off_peak_rates.input_cost_per_token) + assert off_peak_completion_cost == pytest.approx(500 * off_peak_rates.output_cost_per_token) + assert peak_prompt_cost == pytest.approx(1000 * peak_rates.input_cost_per_token) + assert peak_completion_cost == pytest.approx(500 * peak_rates.output_cost_per_token) + + def test_billed_token_rates_are_none_for_an_unpriced_model(): usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15) diff --git a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py index 9847c4092df..e9485f3a044 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py @@ -4,6 +4,7 @@ Tests for cost tracking settings management endpoints. Tests the GET and PATCH endpoints for managing cost discount configuration. """ +from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -12,6 +13,7 @@ from pydantic import ValidationError import litellm +from litellm._internal_context import pinned_billing_time from litellm.proxy._types import CostEstimateRequest from litellm.proxy.management_endpoints.cost_tracking_settings import router from litellm.proxy.proxy_server import app @@ -1108,6 +1110,34 @@ class TestEstimateCostCacheAndReasoningTokens: ) assert response.output_cost_per_request == pytest.approx(1_000 * response.output_cost_per_token) + @pytest.mark.asyncio + async def test_a_quote_prices_its_totals_and_its_rates_at_the_same_moment(self, monkeypatch): + """The totals and the reported rates resolve off-peak pricing on separate paths. A quote + taken as a window opens must not bill on one side of it and report rates from the other.""" + monkeypatch.setitem( + litellm.model_cost, + A_MAPPED_MODEL, + { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "off_peak_pricing": { + "hours_utc": "02:00-03:00", + "input_cost_per_token": 1e-6, + "output_cost_per_token": 5e-6, + }, + "litellm_provider": "openai", + "mode": "chat", + }, + ) + + with pinned_billing_time(datetime(2026, 1, 1, 2, 30, tzinfo=timezone.utc)): + response = await _estimate(None, model=A_MAPPED_MODEL) + + assert response.input_cost_per_token == pytest.approx(1e-6) + assert response.output_cost_per_token == pytest.approx(5e-6) + assert response.input_cost_per_request == pytest.approx(INPUT_TOKENS * response.input_cost_per_token) + assert response.output_cost_per_request == pytest.approx(OUTPUT_TOKENS * response.output_cost_per_token) + class TestCostEstimateRequestTokenSubsets: def test_cache_tokens_beyond_the_input_tokens_are_rejected(self): From 9c980b96d6f32fb2b1568e1e26659e8a8912b37e Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:44:04 -0700 Subject: [PATCH 023/105] fix(budget_reservation): exempt vertex and bedrock count-tokens routes from budget reservation --- litellm/proxy/spend_tracking/budget_reservation.py | 10 ++++++++-- .../proxy/spend_tracking/test_budget_reservation.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py index 31d7d236657..d985075464f 100644 --- a/litellm/proxy/spend_tracking/budget_reservation.py +++ b/litellm/proxy/spend_tracking/budget_reservation.py @@ -183,11 +183,17 @@ _UNBILLED_ROUTES: Final[frozenset[str]] = frozenset( "/openai/v1/responses/input_tokens", } ) -_UNBILLED_ROUTE_SUFFIXES: Final[tuple[str, ...]] = ("/v1/messages/count_tokens", ":countTokens") +_TOKEN_COUNTING_SEGMENTS: Final[frozenset[str]] = frozenset({"count_tokens", "count-tokens"}) +_TOKEN_COUNTING_ACTION: Final = "countTokens" + + +def _is_token_counting_route(route: str) -> bool: + resource, _, action = route.rsplit("/", 1)[-1].partition(":") + return resource in _TOKEN_COUNTING_SEGMENTS or action == _TOKEN_COUNTING_ACTION def _is_unbilled_route(route: str) -> bool: - return route in _UNBILLED_ROUTES or route.endswith(_UNBILLED_ROUTE_SUFFIXES) + return route in _UNBILLED_ROUTES or _is_token_counting_route(route) async def reserve_budget_for_request( diff --git a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py index 5e88268c283..de6c7c2a40a 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py +++ b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py @@ -17,6 +17,10 @@ TOKEN_COUNTING_ROUTES: Final = ( "/v1/messages/count_tokens", "/v1beta/models/gemini-3.8-flash:countTokens", "/models/gemini-3.8-flash:countTokens", + "/bedrock/v1/messages/count-tokens", + "/bedrock/model/us.anthropic.claude-sonnet-4-6/count-tokens", + "/vertex_ai/v1/projects/p/locations/us-east5/publishers/anthropic/models/count-tokens:rawPredict", + "/vertex-ai/v1/projects/p/locations/us-east5/publishers/anthropic/models/count-tokens:rawPredict", ) @@ -56,6 +60,11 @@ ANTHROPIC_MESSAGES: Final = [{"role": "user", "content": "hello!!!"}] COUNT_TOKENS_REQUESTS: Final[tuple[tuple[str, dict[str, object]], ...]] = ( ("/v1/messages/count_tokens", {"model": "claude-sonnet-5", "messages": ANTHROPIC_MESSAGES}), ("/v1beta/models/gemini-3.8-flash:countTokens", {"contents": [{"role": "user", "parts": [{"text": "hello!!!"}]}]}), + ( + "/vertex_ai/v1/projects/p/locations/us-east5/publishers/anthropic/models/count-tokens:rawPredict", + {"model": "claude-sonnet-5", "messages": ANTHROPIC_MESSAGES}, + ), + ("/bedrock/v1/messages/count-tokens", {"model": "claude-sonnet-5", "messages": ANTHROPIC_MESSAGES}), ) TINY_BUDGET_KEY_TOKEN: Final = "hashed-count-tokens-key" From 0c6d4c539942f5f5ac2a723735d020f6ad91444f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:06:30 -0700 Subject: [PATCH 024/105] feat(cost_map): say on the card that Last run is deployment-wide while provenance is per worker --- ui/litellm-dashboard/src/components/price_data_reload.test.tsx | 1 + ui/litellm-dashboard/src/components/price_data_reload.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx index 3566ec2c3f2..4828d557053 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.test.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.test.tsx @@ -69,6 +69,7 @@ describe("PriceDataReload", () => { expect(screen.getByText('W/"eb8e9a53f4cc284b"')).toBeInTheDocument(); expect(screen.getByText("Loaded at:")).toBeInTheDocument(); expect(screen.getByText(/worker that answered this request/)).toBeInTheDocument(); + expect(screen.getByText(/Last run time is the latest reload any worker recorded/)).toBeInTheDocument(); expect(screen.getByText(new Date(provenance.loaded_at).toLocaleString())).toBeInTheDocument(); }); diff --git a/ui/litellm-dashboard/src/components/price_data_reload.tsx b/ui/litellm-dashboard/src/components/price_data_reload.tsx index 1363e306a31..bd2fb6721e0 100644 --- a/ui/litellm-dashboard/src/components/price_data_reload.tsx +++ b/ui/litellm-dashboard/src/components/price_data_reload.tsx @@ -137,7 +137,8 @@ const CostMapProvenanceRows: React.FC<{ sourceInfo: CostMapSourceInfo }> = ({ so
- Reported by the worker that answered this request. Other workers pick up a reload on their next poll + Reported by the worker that answered this request. Other workers pick up a reload on their next poll, and the + Last run time is the latest reload any worker recorded
)} From 415bdbfd8f6ba9bd0422087cc34509bde962ce55 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 20:46:39 -0700 Subject: [PATCH 025/105] fix(azure_ai): charge the Model Router fee once and correct catalog limits The router fee was folded into azure_ai.cost_per_token and then added again by the additional_costs hook, so every routed request paid it twice. The hook now owns the fee, the entry named by the deployment supplies the price, and a response priced as the router entry itself is not charged again model-router, gpt-chat-latest and cohere-command-a carry the limits from the Foundry models page, and model-router and grok-4-20-* carry their retirement dates. The router tests now run at the completion_cost level with a Logging object, which is the path the proxy takes, and fail at the merge base --- litellm/cost_calculator.py | 9 +- litellm/llms/azure_ai/cost_calculator.py | 87 ++- ...odel_prices_and_context_window_backup.json | 11 +- model_prices_and_context_window.json | 11 +- .../azure_ai/test_azure_ai_cost_calculator.py | 561 ++++++------------ ...azure_ai_foundry_catalog_model_metadata.py | 30 +- 6 files changed, 246 insertions(+), 463 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9a9d2ceda03..fc896a098b3 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -45,6 +45,9 @@ from litellm.llms.azure.cost_calculation import ( from litellm.llms.azure_ai.cost_calculator import ( cost_per_token as azure_ai_cost_per_token, ) +from litellm.llms.azure_ai.cost_calculator import ( + is_router_fee_entry as azure_ai_is_router_fee_entry, +) from litellm.llms.base_llm.search.transformation import SearchResponse from litellm.llms.bedrock.cost_calculation import ( cost_per_token as bedrock_cost_per_token, @@ -338,8 +341,6 @@ def cost_per_token( ### VERTEX LOCATION ### vertex_location: str | None = None, # for Vertex AI regional-endpoint uplift (e.g. "us-east5", "global") response: Any | None = None, - ### REQUEST MODEL ### - request_model: str | None = None, # original request model for router detection ) -> tuple[float, float]: """ Calculates the cost per token for a given model, prompt tokens, and completion tokens. @@ -661,7 +662,6 @@ def cost_per_token( model=model, usage=usage_block, response_time_ms=response_time_ms, - request_model=request_model, service_tier=service_tier, ) else: @@ -1659,11 +1659,10 @@ def completion_cost( data_residency=data_residency, vertex_location=vertex_location, response=completion_response, - request_model=request_model_for_cost, ) # Get additional costs from provider (e.g., routing fees, infrastructure costs) - if custom_llm_provider == "azure_ai": + if custom_llm_provider == "azure_ai" and not azure_ai_is_router_fee_entry(model): model_for_additional_costs = request_model_for_cost if completion_response is not None: hidden_params = getattr(completion_response, "_hidden_params", None) or {} diff --git a/litellm/llms/azure_ai/cost_calculator.py b/litellm/llms/azure_ai/cost_calculator.py index 141148f06e7..8a48860c0c4 100644 --- a/litellm/llms/azure_ai/cost_calculator.py +++ b/litellm/llms/azure_ai/cost_calculator.py @@ -31,6 +31,18 @@ def _is_azure_model_router(model: str) -> bool: return "model-router" in model_lower or "model_router" in model_lower or model_lower == "azure-model-router" +ROUTER_FEE_ENTRY_NAMES: Final = frozenset({"model-router", "model_router"}) + + +def is_router_fee_entry(model: str) -> bool: + return model.lower().removeprefix("azure_ai/") in ROUTER_FEE_ENTRY_NAMES + + +def _router_fee_entry_name(model: str) -> str: + entry_name: Final = model.lower().removeprefix("azure_ai/") + return entry_name if entry_name in ROUTER_FEE_ENTRY_NAMES else "model_router" + + def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> float: """ Calculate the flat cost for Azure AI Foundry Model Router. @@ -44,26 +56,39 @@ def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> fl """ if not _is_azure_model_router(model): return 0.0 - - # Get the model router pricing from model_prices_and_context_window.json - # Use "model_router" as the key (without actual model name suffix) - model_info: Final = get_model_info(model="model_router", custom_llm_provider="azure_ai") + model_info: Final = get_model_info(model=_router_fee_entry_name(model), custom_llm_provider="azure_ai") router_flat_cost_per_token: Final = model_info.get("input_cost_per_token", 0) - if router_flat_cost_per_token and router_flat_cost_per_token > 0: return prompt_tokens * router_flat_cost_per_token - return 0.0 -ROUTER_FEE_ENTRY_NAMES: Final = frozenset({"model-router", "model_router"}) +def cost_per_token( + model: str, + usage: Usage, + response_time_ms: float | None = 0.0, + service_tier: str | None = None, +) -> tuple[float, float]: + """ + Price the response model's own tokens for Azure AI. + The Azure AI Foundry Model Router fee is not part of this: completion_cost charges it once through + AzureModelRouterConfig.calculate_additional_costs as the "Azure Model Router Flat Cost" line of the cost + breakdown, and a response priced as the router entry itself already carries it. A router deployment name + that is missing from the cost map prices at zero here so that line item is the whole cost. -def _prices_router_fee_itself(model: str) -> bool: - return model.lower().rsplit("/", 1)[-1] in ROUTER_FEE_ENTRY_NAMES + Args: + model: str, the model name without provider prefix (from response) + usage: LiteLLM Usage block + response_time_ms: Optional response time in milliseconds + service_tier: Optional service tier the request was priced on + Returns: + Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd -def _base_cost_per_token(model: str, usage: Usage, service_tier: str | None) -> tuple[float, float] | None: + Raises: + ValueError: If a model that is not a Model Router name is missing from the cost map + """ try: return generic_cost_per_token( model=model, usage=usage, custom_llm_provider="azure_ai", service_tier=service_tier @@ -72,44 +97,6 @@ def _base_cost_per_token(model: str, usage: Usage, service_tier: str | None) -> if not _is_azure_model_router(model): raise verbose_logger.debug( - "Azure AI Model Router: model '%s' not in cost map, calculating routing flat cost only. Error: %s", model, e + "Azure AI Model Router: model '%s' not in cost map, only the routing fee applies. Error: %s", model, e ) - return None - - -def cost_per_token( - model: str, - usage: Usage, - response_time_ms: float | None = 0.0, - request_model: str | None = None, - service_tier: str | None = None, -) -> tuple[float, float]: - """ - Calculate the cost per token for Azure AI models. - - For Azure AI Foundry Model Router the routing fee (the azure_ai/model_router entry, $0.14 per - million input tokens) is added on top of the routed model's cost. When the response model is - the router entry itself, generic_cost_per_token has already charged that fee. - - Args: - model: str, the model name without provider prefix (from response) - usage: LiteLLM Usage block - response_time_ms: Optional response time in milliseconds - request_model: Optional[str], the original request model name (to detect router usage) - - Returns: - Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd - - Raises: - ValueError: If the model is not found in the cost map and cost cannot be calculated - (except for Model Router models where we return just the routing flat cost) - """ - is_router_request: Final = _is_azure_model_router(model) or ( - request_model is not None and _is_azure_model_router(request_model) - ) - base_cost: Final = _base_cost_per_token(model=model, usage=usage, service_tier=service_tier) - prompt_cost, completion_cost = base_cost if base_cost is not None else (0.0, 0.0) - if not is_router_request or (base_cost is not None and _prices_router_fee_itself(model)): - return prompt_cost, completion_cost - router_flat_cost: Final = calculate_azure_model_router_flat_cost(request_model or model, usage.prompt_tokens) - return prompt_cost + router_flat_cost, completion_cost + return 0.0, 0.0 diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 6649fa831d7..483ebd2431c 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -3586,7 +3586,7 @@ "deprecation_date": "2026-12-02", "input_cost_per_token": 5e-06, "litellm_provider": "azure_ai", - "max_input_tokens": 200000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "chat", @@ -4068,10 +4068,11 @@ "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" }, "azure_ai/model-router": { + "deprecation_date": "2027-05-20", "input_cost_per_token": 1.4e-07, "output_cost_per_token": 0, "litellm_provider": "azure_ai", - "max_input_tokens": 1048576, + "max_input_tokens": 200000, "max_output_tokens": 32768, "max_tokens": 32768, "mode": "chat", @@ -10393,8 +10394,8 @@ "input_cost_per_token": 2.5e-06, "litellm_provider": "azure_ai", "max_input_tokens": 131072, - "max_output_tokens": 4096, - "max_tokens": 4096, + "max_output_tokens": 8182, + "max_tokens": 8182, "mode": "chat", "output_cost_per_token": 1e-05, "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/", @@ -10753,6 +10754,7 @@ "supports_web_search": true }, "azure_ai/grok-4-20-reasoning": { + "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", "max_input_tokens": 262000, @@ -10769,6 +10771,7 @@ "supports_reasoning": true }, "azure_ai/grok-4-20-non-reasoning": { + "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", "max_input_tokens": 262000, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 6649fa831d7..483ebd2431c 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -3586,7 +3586,7 @@ "deprecation_date": "2026-12-02", "input_cost_per_token": 5e-06, "litellm_provider": "azure_ai", - "max_input_tokens": 200000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "chat", @@ -4068,10 +4068,11 @@ "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" }, "azure_ai/model-router": { + "deprecation_date": "2027-05-20", "input_cost_per_token": 1.4e-07, "output_cost_per_token": 0, "litellm_provider": "azure_ai", - "max_input_tokens": 1048576, + "max_input_tokens": 200000, "max_output_tokens": 32768, "max_tokens": 32768, "mode": "chat", @@ -10393,8 +10394,8 @@ "input_cost_per_token": 2.5e-06, "litellm_provider": "azure_ai", "max_input_tokens": 131072, - "max_output_tokens": 4096, - "max_tokens": 4096, + "max_output_tokens": 8182, + "max_tokens": 8182, "mode": "chat", "output_cost_per_token": 1e-05, "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/", @@ -10753,6 +10754,7 @@ "supports_web_search": true }, "azure_ai/grok-4-20-reasoning": { + "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", "max_input_tokens": 262000, @@ -10769,6 +10771,7 @@ "supports_reasoning": true }, "azure_ai/grok-4-20-non-reasoning": { + "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", "max_input_tokens": 262000, diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py index 80cd99bd46b..20d0ec03a2a 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py @@ -2,20 +2,25 @@ Test Azure AI cost calculator, especially Model Router flat cost. """ +from datetime import datetime +from typing import Final + import pytest +import litellm +from litellm.cost_calculator import completion_cost +from litellm.litellm_core_utils.litellm_logging import Logging from litellm.llms.azure_ai.cost_calculator import ( _is_azure_model_router, + calculate_azure_model_router_flat_cost, cost_per_token, ) -from litellm.types.utils import Usage +from litellm.types.utils import Choices, Message, ModelResponse, Usage from litellm.utils import get_model_info # Get the flat cost from model_prices_and_context_window.json _model_info = get_model_info(model="model_router", custom_llm_provider="azure_ai") -AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS = ( - _model_info.get("input_cost_per_token", 0) * 1_000_000 -) +AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS = _model_info.get("input_cost_per_token", 0) * 1_000_000 class TestAzureModelRouterDetection: @@ -80,377 +85,172 @@ class TestAzureModelRouterPrefix: assert result == expected +ROUTER_FEE_PER_TOKEN: Final = AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 +ROUTED_MODEL: Final = "gpt-4.1-nano-2025-04-14" +ROUTED_USAGE: Final = Usage(prompt_tokens=5000, completion_tokens=2000, total_tokens=7000) +ROUTED_FEE: Final = 5000 * ROUTER_FEE_PER_TOKEN + + +def _router_logging(request_model: str) -> Logging: + return Logging( + model=request_model, + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.now(), + litellm_call_id="test-123", + function_id="test-function", + ) + + +def _azure_ai_response(response_model: str, litellm_model_name: str | None = None) -> ModelResponse: + response: Final = ModelResponse( + id="test-123", + choices=[Choices(finish_reason="stop", index=0, message=Message(role="assistant", content="Hello"))], + created=1234567890, + model=response_model, + object="chat.completion", + usage=ROUTED_USAGE, + ) + response._hidden_params = ( + {"custom_llm_provider": "azure_ai"} + if litellm_model_name is None + else {"custom_llm_provider": "azure_ai", "litellm_model_name": litellm_model_name} + ) + return response + + +def _routed_model_cost() -> tuple[float, float]: + routed_info: Final = get_model_info(model=ROUTED_MODEL, custom_llm_provider="azure_ai") + return ( + ROUTED_USAGE.prompt_tokens * (routed_info["input_cost_per_token"] or 0.0), + ROUTED_USAGE.completion_tokens * (routed_info["output_cost_per_token"] or 0.0), + ) + + +@pytest.mark.usefixtures("local_model_cost_map") class TestAzureModelRouterFlatCost: - """Test Azure AI Foundry Model Router flat cost calculation.""" + """cost_per_token prices the response model only; the router fee is the cost breakdown's own line item.""" - def test_model_router_flat_cost_basic(self): - """Test that flat cost is added for Model Router requests.""" - model = "azure-model-router" - usage = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500, + def test_unmapped_router_deployment_name_prices_at_zero(self) -> None: + usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) + assert cost_per_token(model="azure-model-router", usage=usage) == (0.0, 0.0) + + @pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) + def test_router_entry_prices_its_own_fee(self, router_entry_name: str) -> None: + usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) + prompt_cost, completion_cost_usd = cost_per_token(model=router_entry_name, usage=usage) + assert prompt_cost == pytest.approx(0.14, rel=1e-9) + assert completion_cost_usd == 0.0 + + def test_routed_model_is_priced_as_itself(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + prompt_cost, completion_cost_usd = cost_per_token(model=ROUTED_MODEL, usage=ROUTED_USAGE) + assert routed_prompt_cost > 0 + assert prompt_cost == pytest.approx(routed_prompt_cost, rel=1e-9) + assert completion_cost_usd == pytest.approx(routed_completion_cost, rel=1e-9) + + def test_unmapped_model_that_is_not_a_router_name_raises(self) -> None: + usage = Usage(prompt_tokens=10, completion_tokens=10, total_tokens=20) + with pytest.raises(Exception, match="no-such-azure-ai-model"): + cost_per_token(model="no-such-azure-ai-model", usage=usage) + + def test_flat_cost_helper(self) -> None: + assert calculate_azure_model_router_flat_cost( + model="azure-model-router", prompt_tokens=10_000 + ) == pytest.approx(0.0014, rel=1e-9) + assert calculate_azure_model_router_flat_cost(model="gpt-5-nano", prompt_tokens=10_000) == 0.0 + + def test_flat_cost_reads_the_fee_from_the_deployment_named_entry(self) -> None: + litellm.register_model( + {"azure_ai/model-router": {"input_cost_per_token": 2e-07, "litellm_provider": "azure_ai", "mode": "chat"}} ) - - prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) - - # Calculate expected flat cost - expected_flat_cost = ( - usage.prompt_tokens - * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS - / 1_000_000 + litellm.get_model_info.cache_clear() + assert calculate_azure_model_router_flat_cost(model="model-router", prompt_tokens=1_000_000) == pytest.approx( + 0.2, rel=1e-9 ) - - # Flat cost should be $0.00014 (1000 tokens × $0.14 / 1M tokens) - assert expected_flat_cost == pytest.approx(0.00014, rel=1e-9) - - # Prompt cost should include the flat cost - # (plus any base cost from the actual model used, which might be 0 if not in model_cost) - assert prompt_cost >= expected_flat_cost - print( - f"Model Router flat cost for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" - ) - print(f"Total prompt cost: ${prompt_cost:.6f}") - - def test_model_router_flat_cost_large_request(self): - """Test flat cost calculation for larger requests.""" - model = "model-router" - usage = Usage( - prompt_tokens=100_000, - completion_tokens=50_000, - total_tokens=150_000, - ) - - prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) - - # Calculate expected flat cost - expected_flat_cost = ( - usage.prompt_tokens - * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS - / 1_000_000 - ) - - # Flat cost should be $0.014 (100k tokens × $0.14 / 1M tokens) - assert expected_flat_cost == pytest.approx(0.014, rel=1e-9) - # Use approx for floating-point comparison - assert prompt_cost >= expected_flat_cost or prompt_cost == pytest.approx( - expected_flat_cost, rel=1e-9 - ) - print( - f"Model Router flat cost for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" - ) - print(f"Total prompt cost: ${prompt_cost:.6f}") - - def test_model_router_flat_cost_1m_tokens(self): - """Test flat cost for exactly 1 million input tokens.""" - model = "azure-model-router" - usage = Usage( - prompt_tokens=1_000_000, - completion_tokens=100_000, - total_tokens=1_100_000, - ) - - prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) - - # Calculate expected flat cost - expected_flat_cost = AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS - - # Flat cost should be exactly $0.14 for 1M tokens - assert expected_flat_cost == pytest.approx(0.14, rel=1e-9) - assert prompt_cost >= expected_flat_cost - print(f"Model Router flat cost for 1M tokens: ${expected_flat_cost:.6f}") - print(f"Total prompt cost: ${prompt_cost:.6f}") - - def test_non_model_router_no_flat_cost(self): - """Test that non-Model Router models don't get the flat cost.""" - model = "gpt-4o" - usage = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500, - ) - - prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) - - # No flat cost should be added for non-Model Router models - # The cost might be 0 or based on the model's pricing - print(f"Non-Model Router prompt cost: ${prompt_cost:.6f}") - # We just ensure it doesn't crash and returns valid values - assert prompt_cost >= 0 - assert completion_cost >= 0 - - def test_model_router_with_cached_tokens(self): - """Test Model Router flat cost with cached tokens.""" - model = "azure-model-router" - usage = Usage( - prompt_tokens=2000, - completion_tokens=800, - total_tokens=2800, - cache_read_input_tokens=500, - cache_creation_input_tokens=200, - ) - - prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) - - # Flat cost is based on ALL prompt tokens (including cached) - expected_flat_cost = ( - usage.prompt_tokens - * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS - / 1_000_000 - ) - - assert expected_flat_cost == pytest.approx(0.00028, rel=1e-9) - assert prompt_cost >= expected_flat_cost - print( - f"Model Router flat cost with caching for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" - ) - print(f"Total prompt cost: ${prompt_cost:.6f}") - - def test_router_flat_cost_when_response_has_actual_model(self): - """ - Test that router flat cost is added when request was via router but response - contains the actual model (e.g., gpt-5-nano). - - This is the key fix: Azure returns the actual model in the response, but we - must still add the router flat cost because the request was made via model router. - """ - usage = Usage( - prompt_tokens=10000, - completion_tokens=5000, - total_tokens=15000, - ) - - # Response model is the actual model Azure used (not a router name) - response_model = "gpt-5-nano-2025-08-07" - # Request model is the router - user called azure_ai/model_router/model-router - request_model = "azure_ai/model_router/model-router" - - prompt_cost, completion_cost = cost_per_token( - model=response_model, - usage=usage, - request_model=request_model, - ) - - # Expected: model cost (from gpt-5-nano) + router flat cost - expected_flat_cost = ( - usage.prompt_tokens - * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS - / 1_000_000 - ) - assert expected_flat_cost == pytest.approx(0.0014, rel=1e-9) - - # Total cost should be model cost + flat cost - total_cost = prompt_cost + completion_cost - assert total_cost >= expected_flat_cost - - # Prompt cost should include both model prompt cost and router flat cost - assert prompt_cost >= expected_flat_cost + assert calculate_azure_model_router_flat_cost( + model="azure-model-router", prompt_tokens=1_000_000 + ) == pytest.approx(0.14, rel=1e-9) +@pytest.mark.usefixtures("local_model_cost_map") class TestAzureModelRouterCostBreakdown: - """Test that Azure Model Router flat cost is tracked in cost breakdown.""" + """completion_cost charges the router fee exactly once, as the cost breakdown's additional cost line.""" - def test_flat_cost_calculation_helper(self): - """Test that flat cost can be calculated using the helper function.""" - from litellm.llms.azure_ai.cost_calculator import ( - calculate_azure_model_router_flat_cost, - ) - - model = "azure-model-router" - prompt_tokens = 10000 - - # Calculate flat cost using helper function - flat_cost = calculate_azure_model_router_flat_cost( - model=model, prompt_tokens=prompt_tokens - ) - - # Expected flat cost - expected_flat_cost = ( - prompt_tokens * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 - ) - - assert flat_cost > 0 - assert flat_cost == pytest.approx(expected_flat_cost, rel=1e-9) - print(f"Flat cost calculated: ${flat_cost:.6f}") - - def test_flat_cost_integration_with_completion_cost(self): - """Test that flat cost is properly integrated into completion_cost calculation.""" - import litellm - from litellm.cost_calculator import completion_cost - from litellm.types.utils import Choices, Message, ModelResponse, Usage - - # Create a mock response for azure_ai model router - response = ModelResponse( - id="test-123", - choices=[ - Choices( - finish_reason="stop", - index=0, - message=Message( - role="assistant", - content="Test response", - ), - ) - ], - created=1234567890, - model="azure-model-router", - object="chat.completion", - usage=Usage( - prompt_tokens=5000, - completion_tokens=2000, - total_tokens=7000, - ), - ) - - # Set hidden params for provider - response._hidden_params = {"custom_llm_provider": "azure_ai"} - - # Calculate cost + def test_unmapped_router_deployment_name_costs_only_the_fee(self) -> None: cost = completion_cost( - completion_response=response, + completion_response=_azure_ai_response("azure-model-router"), model="azure-model-router", custom_llm_provider="azure_ai", ) + assert cost == pytest.approx(ROUTED_FEE, rel=1e-9) - # Expected flat cost - expected_flat_cost = ( - 5000 * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 - ) - - # Cost should include the flat cost (use approx for floating-point comparison) - assert cost >= expected_flat_cost or cost == pytest.approx( - expected_flat_cost, rel=1e-9 - ) - print(f"Total cost with flat fee: ${cost:.6f}") - print(f"Expected minimum flat cost: ${expected_flat_cost:.6f}") - - def test_additional_costs_in_cost_breakdown(self): - """Test that Azure Model Router flat cost appears in additional_costs dict.""" - from datetime import datetime - - from litellm.cost_calculator import completion_cost - from litellm.litellm_core_utils.litellm_logging import Logging - from litellm.types.utils import Choices, Message, ModelResponse, Usage - - # Create logging object with required parameters - logging_obj = Logging( - model="azure-model-router", - messages=[{"role": "user", "content": "Hello"}], - stream=False, - call_type="completion", - start_time=datetime.now(), - litellm_call_id="test-123", - function_id="test-function", - ) - - # Create a mock response for azure_ai model router - response = ModelResponse( - id="test-123", - choices=[ - Choices( - finish_reason="stop", - index=0, - message=Message( - role="assistant", - content="Test response", - ), - ) - ], - created=1234567890, - model="azure-model-router", - object="chat.completion", - usage=Usage( - prompt_tokens=5000, - completion_tokens=2000, - total_tokens=7000, - ), - ) - - # Set hidden params for provider - response._hidden_params = {"custom_llm_provider": "azure_ai"} - - # Calculate cost with logging object + def test_fee_is_the_breakdown_line_item_for_an_unmapped_router_name(self) -> None: + logging_obj = _router_logging("azure-model-router") cost = completion_cost( - completion_response=response, + completion_response=_azure_ai_response("azure-model-router"), model="azure-model-router", custom_llm_provider="azure_ai", litellm_logging_obj=logging_obj, ) - - # Check that cost breakdown contains additional_costs - assert hasattr(logging_obj, "cost_breakdown") - assert logging_obj.cost_breakdown is not None - assert "additional_costs" in logging_obj.cost_breakdown - assert isinstance(logging_obj.cost_breakdown["additional_costs"], dict) - - # Check that the Azure Model Router flat cost is in additional_costs - additional_costs = logging_obj.cost_breakdown["additional_costs"] - assert "Azure Model Router Flat Cost" in additional_costs - - # Verify the flat cost value - expected_flat_cost = ( - 5000 * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + breakdown = logging_obj.cost_breakdown + assert breakdown is not None + assert breakdown["input_cost"] == 0.0 + assert breakdown.get("additional_costs") == pytest.approx( + {"Azure Model Router Flat Cost": ROUTED_FEE}, rel=1e-9 ) - actual_flat_cost = additional_costs["Azure Model Router Flat Cost"] - assert actual_flat_cost == pytest.approx(expected_flat_cost, rel=1e-9) + assert cost == pytest.approx(ROUTED_FEE, rel=1e-9) - print(f"Additional costs in breakdown: {additional_costs}") - print(f"Azure Model Router Flat Cost: ${actual_flat_cost:.6f}") - - def test_additional_costs_when_response_has_actual_model_via_hidden_params(self): - """additional_costs populated when response has actual model but request was via model router (hidden_params).""" - from datetime import datetime - - from litellm.cost_calculator import completion_cost - from litellm.litellm_core_utils.litellm_logging import Logging - from litellm.types.utils import Choices, Message, ModelResponse, Usage - - logging_obj = Logging( - model="gpt-4.1-nano-2025-04-14", - messages=[{"role": "user", "content": "Hello"}], - stream=False, - call_type="completion", - start_time=datetime.now(), - litellm_call_id="test-123", - function_id="test-function", - ) - response = ModelResponse( - id="test-123", - choices=[ - Choices( - finish_reason="stop", - index=0, - message=Message(role="assistant", content="Hello"), - ) - ], - created=1234567890, - model="gpt-4.1-nano-2025-04-14", - object="chat.completion", - usage=Usage(prompt_tokens=5000, completion_tokens=2000, total_tokens=7000), - ) - response._hidden_params = { - "custom_llm_provider": "azure_ai", - "litellm_model_name": "azure_ai/model-router", - } + def test_router_request_with_routed_response_charges_the_fee_once(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + logging_obj = _router_logging("model-router") cost = completion_cost( - completion_response=response, - model="gpt-4.1-nano-2025-04-14", + completion_response=_azure_ai_response(ROUTED_MODEL), + model=ROUTED_MODEL, custom_llm_provider="azure_ai", litellm_logging_obj=logging_obj, ) - expected_flat_cost = ( - 5000 * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + breakdown = logging_obj.cost_breakdown + assert breakdown is not None + assert breakdown["input_cost"] == pytest.approx(routed_prompt_cost, rel=1e-9) + assert breakdown["output_cost"] == pytest.approx(routed_completion_cost, rel=1e-9) + assert breakdown.get("additional_costs") == pytest.approx( + {"Azure Model Router Flat Cost": ROUTED_FEE}, rel=1e-9 ) - assert cost >= expected_flat_cost - assert logging_obj.cost_breakdown is not None - assert "additional_costs" in logging_obj.cost_breakdown - assert ( - "Azure Model Router Flat Cost" - in logging_obj.cost_breakdown["additional_costs"] + assert cost == pytest.approx(routed_prompt_cost + routed_completion_cost + ROUTED_FEE, rel=1e-9) + + def test_routed_response_named_by_hidden_params_charges_the_fee_once(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + logging_obj = _router_logging(ROUTED_MODEL) + cost = completion_cost( + completion_response=_azure_ai_response(ROUTED_MODEL, litellm_model_name="azure_ai/model-router"), + model=ROUTED_MODEL, + custom_llm_provider="azure_ai", + litellm_logging_obj=logging_obj, ) - assert logging_obj.cost_breakdown["additional_costs"][ - "Azure Model Router Flat Cost" - ] == pytest.approx(expected_flat_cost, rel=1e-9) + breakdown = logging_obj.cost_breakdown + assert breakdown is not None + assert breakdown["input_cost"] == pytest.approx(routed_prompt_cost, rel=1e-9) + assert breakdown.get("additional_costs") == pytest.approx( + {"Azure Model Router Flat Cost": ROUTED_FEE}, rel=1e-9 + ) + assert cost == pytest.approx(routed_prompt_cost + routed_completion_cost + ROUTED_FEE, rel=1e-9) + + @pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) + def test_response_priced_as_the_router_entry_charges_the_fee_once(self, router_entry_name: str) -> None: + logging_obj = _router_logging(router_entry_name) + cost = completion_cost( + completion_response=_azure_ai_response(router_entry_name), + model=router_entry_name, + custom_llm_provider="azure_ai", + litellm_logging_obj=logging_obj, + ) + breakdown = logging_obj.cost_breakdown + assert breakdown is not None + assert "additional_costs" not in breakdown + assert breakdown["input_cost"] == pytest.approx(ROUTED_FEE, rel=1e-9) + assert cost == pytest.approx(ROUTED_FEE, rel=1e-9) class TestAzureAIServiceTierCostCalculation: @@ -459,26 +259,27 @@ class TestAzureAIServiceTierCostCalculation: @pytest.fixture(autouse=True) def register_test_model(self): import litellm - litellm.register_model(model_cost={ - "test-azure-ai-model": { - "input_cost_per_token": 0.001, - "output_cost_per_token": 0.002, - "input_cost_per_token_priority": 0.01, - "output_cost_per_token_priority": 0.02, - "input_cost_per_token_flex": 0.0005, - "output_cost_per_token_flex": 0.001, - "litellm_provider": "azure_ai", - "max_tokens": 8192, + + litellm.register_model( + model_cost={ + "test-azure-ai-model": { + "input_cost_per_token": 0.001, + "output_cost_per_token": 0.002, + "input_cost_per_token_priority": 0.01, + "output_cost_per_token_priority": 0.02, + "input_cost_per_token_flex": 0.0005, + "output_cost_per_token_flex": 0.001, + "litellm_provider": "azure_ai", + "max_tokens": 8192, + } } - }) + ) def test_service_tier_priority_higher_cost(self): """Priority tier should cost more than standard for azure_ai.""" usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) - standard_prompt, standard_completion = cost_per_token( - model="test-azure-ai-model", usage=usage - ) + standard_prompt, standard_completion = cost_per_token(model="test-azure-ai-model", usage=usage) priority_prompt, priority_completion = cost_per_token( model="test-azure-ai-model", usage=usage, service_tier="priority" ) @@ -490,12 +291,8 @@ class TestAzureAIServiceTierCostCalculation: """Flex tier should cost less than standard for azure_ai.""" usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) - standard_prompt, standard_completion = cost_per_token( - model="test-azure-ai-model", usage=usage - ) - flex_prompt, flex_completion = cost_per_token( - model="test-azure-ai-model", usage=usage, service_tier="flex" - ) + standard_prompt, standard_completion = cost_per_token(model="test-azure-ai-model", usage=usage) + flex_prompt, flex_completion = cost_per_token(model="test-azure-ai-model", usage=usage, service_tier="flex") assert flex_prompt < standard_prompt assert flex_completion < standard_completion @@ -528,29 +325,3 @@ def test_mai_thinking_1_model_info_and_cost(local_model_cost_map): assert model_info["supports_function_calling"] is True assert prompt_cost == pytest.approx(2.0) assert completion_cost == pytest.approx(8.0) - - -@pytest.mark.usefixtures("local_model_cost_map") -@pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) -def test_router_entry_as_response_model_charges_the_fee_once(router_entry_name: str) -> None: - usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) - prompt_cost, completion_cost = cost_per_token(model=router_entry_name, usage=usage) - assert prompt_cost == pytest.approx(0.14, rel=1e-9) - assert completion_cost == 0.0 - - -@pytest.mark.usefixtures("local_model_cost_map") -def test_unmapped_router_deployment_name_still_charges_the_fee() -> None: - usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) - prompt_cost, completion_cost = cost_per_token(model="azure-model-router", usage=usage) - assert prompt_cost == pytest.approx(0.14, rel=1e-9) - assert completion_cost == 0.0 - - -@pytest.mark.usefixtures("local_model_cost_map") -def test_routed_model_response_adds_the_fee_on_top() -> None: - usage = Usage(prompt_tokens=1_000_000, completion_tokens=0, total_tokens=1_000_000) - routed_prompt_cost, _ = cost_per_token(model="gpt-5-nano", usage=usage) - prompt_cost, _ = cost_per_token(model="gpt-5-nano", usage=usage, request_model="azure_ai/model-router") - assert routed_prompt_cost > 0 - assert prompt_cost == pytest.approx(routed_prompt_cost + 0.14, rel=1e-9) diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py index 1b4e83438a6..fab9be1b42c 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py @@ -26,6 +26,7 @@ class TokenPricedCatalogModel: max_input_tokens: int max_output_tokens: int cache_read_input_token_cost: float | None + deprecation_date: str | None supported_flags: tuple[str, ...] @@ -36,9 +37,10 @@ TOKEN_PRICED_MODELS: Final = ( source=AZURE_OPENAI_PRICING, input_cost_per_token=5e-06, output_cost_per_token=3e-05, - max_input_tokens=200000, + max_input_tokens=272000, max_output_tokens=128000, cache_read_input_token_cost=5e-07, + deprecation_date="2026-12-02", supported_flags=( "supports_function_calling", "supports_prompt_caching", @@ -58,7 +60,13 @@ TOKEN_PRICED_MODELS: Final = ( max_input_tokens=200000, max_output_tokens=100000, cache_read_input_token_cost=3.75e-07, - supported_flags=("supports_function_calling", "supports_prompt_caching", "supports_reasoning", "supports_vision"), + deprecation_date="2026-11-15", + supported_flags=( + "supports_function_calling", + "supports_prompt_caching", + "supports_reasoning", + "supports_vision", + ), ), TokenPricedCatalogModel( catalog_name="model-router", @@ -66,9 +74,10 @@ TOKEN_PRICED_MODELS: Final = ( source=FOUNDRY_AOAI_PRICING, input_cost_per_token=1.4e-07, output_cost_per_token=0.0, - max_input_tokens=1048576, + max_input_tokens=200000, max_output_tokens=32768, cache_read_input_token_cost=None, + deprecation_date="2027-05-20", supported_flags=(), ), TokenPricedCatalogModel( @@ -78,8 +87,9 @@ TOKEN_PRICED_MODELS: Final = ( input_cost_per_token=2.5e-06, output_cost_per_token=1e-05, max_input_tokens=131072, - max_output_tokens=4096, + max_output_tokens=8182, cache_read_input_token_cost=None, + deprecation_date=None, supported_flags=("supports_function_calling", "supports_tool_choice"), ), TokenPricedCatalogModel( @@ -91,6 +101,7 @@ TOKEN_PRICED_MODELS: Final = ( max_input_tokens=262000, max_output_tokens=8192, cache_read_input_token_cost=None, + deprecation_date="2027-04-06", supported_flags=( "supports_function_calling", "supports_reasoning", @@ -109,6 +120,7 @@ TOKEN_PRICED_MODELS: Final = ( max_input_tokens=262000, max_output_tokens=8192, cache_read_input_token_cost=None, + deprecation_date="2027-04-06", supported_flags=( "supports_function_calling", "supports_response_schema", @@ -146,7 +158,9 @@ def test_azure_ai_catalog_name_is_priced_and_routed(spec: TokenPricedCatalogMode @pytest.mark.usefixtures("local_model_cost_map") @pytest.mark.parametrize( - "spec", [spec for spec in TOKEN_PRICED_MODELS if spec.catalog_name != "model-router"], ids=lambda spec: spec.catalog_name + "spec", + [spec for spec in TOKEN_PRICED_MODELS if spec.catalog_name != "model-router"], + ids=lambda spec: spec.catalog_name, ) def test_azure_ai_catalog_name_costs_a_million_tokens_at_list_price(spec: TokenPricedCatalogModel) -> None: prompt_cost, completion_cost = cost_per_token( @@ -174,3 +188,9 @@ def test_azure_ai_catalog_entry_source_and_backup_match(catalog_name: str) -> No assert str(main_entry["source"]).startswith("https://azure.microsoft.com/en-us/pricing/details/") assert backup_entry == main_entry + + +@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) +def test_azure_ai_catalog_entry_carries_its_retirement_date(spec: TokenPricedCatalogModel) -> None: + entry = _cost_map_entry(REPO_ROOT / "model_prices_and_context_window.json", spec.catalog_name) + assert entry.get("deprecation_date") == spec.deprecation_date From c02f2dc0feff1f95d61b1be699565a835402a122 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:10:54 -0700 Subject: [PATCH 026/105] fix(azure_ai): keep the request_model keyword on cost_per_token Restores the public keyword removed at 415bdbfd8f. A direct caller that names the Model Router as the request model gets the routing fee folded into the prompt cost once; completion_cost never passes it and charges the fee through the additional-costs hook as before --- litellm/cost_calculator.py | 3 + litellm/llms/azure_ai/cost_calculator.py | 62 +++++++++++-------- .../azure_ai/test_azure_ai_cost_calculator.py | 33 ++++++++++ 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index fc896a098b3..e135503d11d 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -341,6 +341,8 @@ def cost_per_token( ### VERTEX LOCATION ### vertex_location: str | None = None, # for Vertex AI regional-endpoint uplift (e.g. "us-east5", "global") response: Any | None = None, + ### REQUEST MODEL ### + request_model: str | None = None, # original request model for router detection ) -> tuple[float, float]: """ Calculates the cost per token for a given model, prompt tokens, and completion tokens. @@ -662,6 +664,7 @@ def cost_per_token( model=model, usage=usage_block, response_time_ms=response_time_ms, + request_model=request_model, service_tier=service_tier, ) else: diff --git a/litellm/llms/azure_ai/cost_calculator.py b/litellm/llms/azure_ai/cost_calculator.py index 8a48860c0c4..e57ba055587 100644 --- a/litellm/llms/azure_ai/cost_calculator.py +++ b/litellm/llms/azure_ai/cost_calculator.py @@ -63,32 +63,7 @@ def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> fl return 0.0 -def cost_per_token( - model: str, - usage: Usage, - response_time_ms: float | None = 0.0, - service_tier: str | None = None, -) -> tuple[float, float]: - """ - Price the response model's own tokens for Azure AI. - - The Azure AI Foundry Model Router fee is not part of this: completion_cost charges it once through - AzureModelRouterConfig.calculate_additional_costs as the "Azure Model Router Flat Cost" line of the cost - breakdown, and a response priced as the router entry itself already carries it. A router deployment name - that is missing from the cost map prices at zero here so that line item is the whole cost. - - Args: - model: str, the model name without provider prefix (from response) - usage: LiteLLM Usage block - response_time_ms: Optional response time in milliseconds - service_tier: Optional service tier the request was priced on - - Returns: - Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd - - Raises: - ValueError: If a model that is not a Model Router name is missing from the cost map - """ +def _response_model_cost(model: str, usage: Usage, service_tier: str | None) -> tuple[float, float]: try: return generic_cost_per_token( model=model, usage=usage, custom_llm_provider="azure_ai", service_tier=service_tier @@ -100,3 +75,38 @@ def cost_per_token( "Azure AI Model Router: model '%s' not in cost map, only the routing fee applies. Error: %s", model, e ) return 0.0, 0.0 + + +def cost_per_token( + model: str, + usage: Usage, + response_time_ms: float | None = 0.0, + request_model: str | None = None, + service_tier: str | None = None, +) -> tuple[float, float]: + """ + Price the response model's own tokens for Azure AI, plus the Model Router fee when the caller names the + router as the request model. + + completion_cost never passes request_model: it charges the fee once through + AzureModelRouterConfig.calculate_additional_costs as the "Azure Model Router Flat Cost" line of the cost + breakdown. A response priced as the router entry itself already carries the fee, so request_model adds + nothing on top of it, and a router deployment name that is missing from the cost map prices at zero here. + + Args: + model: str, the model name without provider prefix (from response) + usage: LiteLLM Usage block + response_time_ms: Optional response time in milliseconds + request_model: Optional[str], the original request model name; a Model Router name adds the routing fee + service_tier: Optional service tier the request was priced on + + Returns: + Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd + + Raises: + ValueError: If a model that is not a Model Router name is missing from the cost map + """ + prompt_cost, completion_cost = _response_model_cost(model=model, usage=usage, service_tier=service_tier) + if request_model is None or not _is_azure_model_router(request_model) or is_router_fee_entry(model): + return prompt_cost, completion_cost + return prompt_cost + calculate_azure_model_router_flat_cost(request_model, usage.prompt_tokens), completion_cost diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py index 20d0ec03a2a..0deb79d14d3 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py @@ -155,6 +155,39 @@ class TestAzureModelRouterFlatCost: with pytest.raises(Exception, match="no-such-azure-ai-model"): cost_per_token(model="no-such-azure-ai-model", usage=usage) + def test_request_model_through_the_router_adds_the_fee_once(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + prompt_cost, completion_cost_usd = cost_per_token( + model=ROUTED_MODEL, usage=ROUTED_USAGE, request_model="azure_ai/model-router" + ) + assert prompt_cost == pytest.approx(routed_prompt_cost + ROUTED_FEE, rel=1e-9) + assert completion_cost_usd == pytest.approx(routed_completion_cost, rel=1e-9) + + def test_request_model_that_is_not_the_router_adds_nothing(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + assert cost_per_token( + model=ROUTED_MODEL, usage=ROUTED_USAGE, request_model=f"azure_ai/{ROUTED_MODEL}" + ) == pytest.approx((routed_prompt_cost, routed_completion_cost), rel=1e-9) + + @pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) + def test_request_model_does_not_double_the_router_entry(self, router_entry_name: str) -> None: + prompt_cost, completion_cost_usd = cost_per_token( + model=router_entry_name, usage=ROUTED_USAGE, request_model=f"azure_ai/{router_entry_name}" + ) + assert prompt_cost == pytest.approx(ROUTED_FEE, rel=1e-9) + assert completion_cost_usd == 0.0 + + def test_public_cost_per_token_keeps_the_request_model_keyword(self) -> None: + routed_prompt_cost, routed_completion_cost = _routed_model_cost() + prompt_cost, completion_cost_usd = litellm.cost_per_token( + model=ROUTED_MODEL, + custom_llm_provider="azure_ai", + usage_object=ROUTED_USAGE, + request_model="azure_ai/model-router", + ) + assert prompt_cost == pytest.approx(routed_prompt_cost + ROUTED_FEE, rel=1e-9) + assert completion_cost_usd == pytest.approx(routed_completion_cost, rel=1e-9) + def test_flat_cost_helper(self) -> None: assert calculate_azure_model_router_flat_cost( model="azure-model-router", prompt_tokens=10_000 From 55c10c1983c92dbad1d62dfc3c14aab94696639a Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:34:34 -0700 Subject: [PATCH 027/105] fix(azure_ai): charge the router fee once for any router name and price grok-4-20 cache reads Direct litellm.cost_per_token callers that name a Model Router deployment as the model get the routing fee again, as they did before this branch, and the fee is still charged exactly once on every completion_cost path. The grok-4-20 entries bill cached prompt tokens at the input rate, since Azure has no cached-input meter for them, and the model_router twin carries the same limits and retirement date as model-router. The catalog test now exercises the cost calculator and map relations instead of pinning map fields. --- litellm/cost_calculator.py | 4 +- litellm/llms/azure_ai/cost_calculator.py | 33 ++- ...odel_prices_and_context_window_backup.json | 6 + model_prices_and_context_window.json | 6 + .../azure_ai/test_azure_ai_cost_calculator.py | 31 ++- ...azure_ai_foundry_catalog_model_metadata.py | 219 ++++++------------ 6 files changed, 125 insertions(+), 174 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index e135503d11d..8a00ffa4d37 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -46,7 +46,7 @@ from litellm.llms.azure_ai.cost_calculator import ( cost_per_token as azure_ai_cost_per_token, ) from litellm.llms.azure_ai.cost_calculator import ( - is_router_fee_entry as azure_ai_is_router_fee_entry, + is_azure_model_router as azure_ai_is_model_router_name, ) from litellm.llms.base_llm.search.transformation import SearchResponse from litellm.llms.bedrock.cost_calculation import ( @@ -1665,7 +1665,7 @@ def completion_cost( ) # Get additional costs from provider (e.g., routing fees, infrastructure costs) - if custom_llm_provider == "azure_ai" and not azure_ai_is_router_fee_entry(model): + if custom_llm_provider == "azure_ai" and not azure_ai_is_model_router_name(model): model_for_additional_costs = request_model_for_cost if completion_response is not None: hidden_params = getattr(completion_response, "_hidden_params", None) or {} diff --git a/litellm/llms/azure_ai/cost_calculator.py b/litellm/llms/azure_ai/cost_calculator.py index e57ba055587..5934525eca3 100644 --- a/litellm/llms/azure_ai/cost_calculator.py +++ b/litellm/llms/azure_ai/cost_calculator.py @@ -11,7 +11,7 @@ from litellm.types.utils import Usage from litellm.utils import get_model_info -def _is_azure_model_router(model: str) -> bool: +def is_azure_model_router(model: str) -> bool: """ Check if the model is Azure AI Foundry Model Router. @@ -54,7 +54,7 @@ def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> fl Returns: float: The flat cost in USD, or 0.0 if not applicable """ - if not _is_azure_model_router(model): + if not is_azure_model_router(model): return 0.0 model_info: Final = get_model_info(model=_router_fee_entry_name(model), custom_llm_provider="azure_ai") router_flat_cost_per_token: Final = model_info.get("input_cost_per_token", 0) @@ -69,7 +69,7 @@ def _response_model_cost(model: str, usage: Usage, service_tier: str | None) -> model=model, usage=usage, custom_llm_provider="azure_ai", service_tier=service_tier ) except Exception as e: - if not _is_azure_model_router(model): + if not is_azure_model_router(model): raise verbose_logger.debug( "Azure AI Model Router: model '%s' not in cost map, only the routing fee applies. Error: %s", model, e @@ -77,6 +77,16 @@ def _response_model_cost(model: str, usage: Usage, service_tier: str | None) -> return 0.0, 0.0 +def _router_fee_name(model: str, request_model: str | None) -> str | None: + if is_router_fee_entry(model): + return None + if is_azure_model_router(model): + return model + if request_model is not None and is_azure_model_router(request_model): + return request_model + return None + + def cost_per_token( model: str, usage: Usage, @@ -85,13 +95,15 @@ def cost_per_token( service_tier: str | None = None, ) -> tuple[float, float]: """ - Price the response model's own tokens for Azure AI, plus the Model Router fee when the caller names the - router as the request model. + Price the response model's own tokens for Azure AI, plus the Model Router fee exactly once when either the + priced name or request_model is a Model Router name. - completion_cost never passes request_model: it charges the fee once through + A response priced as the router entry itself already carries the fee, so nothing is added on top of it. A + router deployment name that is missing from the cost map prices at the fee alone. + + completion_cost passes only the priced name: when that name is a routed model it adds the fee itself through AzureModelRouterConfig.calculate_additional_costs as the "Azure Model Router Flat Cost" line of the cost - breakdown. A response priced as the router entry itself already carries the fee, so request_model adds - nothing on top of it, and a router deployment name that is missing from the cost map prices at zero here. + breakdown, and when the name is router-shaped the fee is already in the prompt cost returned here. Args: model: str, the model name without provider prefix (from response) @@ -107,6 +119,7 @@ def cost_per_token( ValueError: If a model that is not a Model Router name is missing from the cost map """ prompt_cost, completion_cost = _response_model_cost(model=model, usage=usage, service_tier=service_tier) - if request_model is None or not _is_azure_model_router(request_model) or is_router_fee_entry(model): + fee_name: Final = _router_fee_name(model=model, request_model=request_model) + if fee_name is None: return prompt_cost, completion_cost - return prompt_cost + calculate_azure_model_router_flat_cost(request_model, usage.prompt_tokens), completion_cost + return prompt_cost + calculate_azure_model_router_flat_cost(fee_name, usage.prompt_tokens), completion_cost diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 483ebd2431c..674a8b98304 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4060,9 +4060,13 @@ "supports_minimal_reasoning_effort": false }, "azure_ai/model_router": { + "deprecation_date": "2027-05-20", "input_cost_per_token": 1.4e-07, "output_cost_per_token": 0, "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 32768, + "max_tokens": 32768, "mode": "chat", "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" @@ -10754,6 +10758,7 @@ "supports_web_search": true }, "azure_ai/grok-4-20-reasoning": { + "cache_read_input_token_cost": 1.25e-06, "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", @@ -10771,6 +10776,7 @@ "supports_reasoning": true }, "azure_ai/grok-4-20-non-reasoning": { + "cache_read_input_token_cost": 1.25e-06, "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 483ebd2431c..674a8b98304 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4060,9 +4060,13 @@ "supports_minimal_reasoning_effort": false }, "azure_ai/model_router": { + "deprecation_date": "2027-05-20", "input_cost_per_token": 1.4e-07, "output_cost_per_token": 0, "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 32768, + "max_tokens": 32768, "mode": "chat", "source": "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/", "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" @@ -10754,6 +10758,7 @@ "supports_web_search": true }, "azure_ai/grok-4-20-reasoning": { + "cache_read_input_token_cost": 1.25e-06, "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", @@ -10771,6 +10776,7 @@ "supports_reasoning": true }, "azure_ai/grok-4-20-non-reasoning": { + "cache_read_input_token_cost": 1.25e-06, "deprecation_date": "2027-04-06", "input_cost_per_token": 1.25e-06, "litellm_provider": "azure_ai", diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py index 0deb79d14d3..7df14b91741 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py @@ -11,9 +11,9 @@ import litellm from litellm.cost_calculator import completion_cost from litellm.litellm_core_utils.litellm_logging import Logging from litellm.llms.azure_ai.cost_calculator import ( - _is_azure_model_router, calculate_azure_model_router_flat_cost, cost_per_token, + is_azure_model_router, ) from litellm.types.utils import Choices, Message, ModelResponse, Usage from litellm.utils import get_model_info @@ -54,7 +54,7 @@ class TestAzureModelRouterDetection: ) def test_is_azure_model_router(self, model: str, expected: bool): """Test Azure Model Router detection.""" - assert _is_azure_model_router(model) == expected + assert is_azure_model_router(model) == expected class TestAzureModelRouterPrefix: @@ -130,11 +130,21 @@ def _routed_model_cost() -> tuple[float, float]: @pytest.mark.usefixtures("local_model_cost_map") class TestAzureModelRouterFlatCost: - """cost_per_token prices the response model only; the router fee is the cost breakdown's own line item.""" + """cost_per_token charges the router fee once, for whichever router name the caller gives it.""" - def test_unmapped_router_deployment_name_prices_at_zero(self) -> None: + def test_unmapped_router_deployment_name_prices_the_fee(self) -> None: usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) - assert cost_per_token(model="azure-model-router", usage=usage) == (0.0, 0.0) + prompt_cost, completion_cost_usd = cost_per_token(model="azure-model-router", usage=usage) + assert prompt_cost == pytest.approx(1000 * ROUTER_FEE_PER_TOKEN, rel=1e-9) + assert completion_cost_usd == 0.0 + + def test_router_deployment_name_as_both_names_charges_the_fee_once(self) -> None: + usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) + prompt_cost, completion_cost_usd = cost_per_token( + model="model_router/my-deployment", usage=usage, request_model="azure_ai/model_router/my-deployment" + ) + assert prompt_cost == pytest.approx(1000 * ROUTER_FEE_PER_TOKEN, rel=1e-9) + assert completion_cost_usd == 0.0 @pytest.mark.parametrize("router_entry_name", ["model_router", "model-router"]) def test_router_entry_prices_its_own_fee(self, router_entry_name: str) -> None: @@ -209,7 +219,8 @@ class TestAzureModelRouterFlatCost: @pytest.mark.usefixtures("local_model_cost_map") class TestAzureModelRouterCostBreakdown: - """completion_cost charges the router fee exactly once, as the cost breakdown's additional cost line.""" + """completion_cost charges the router fee exactly once: as the breakdown's additional cost line when a routed + model is priced as itself, inside the input cost when the priced name is the router.""" def test_unmapped_router_deployment_name_costs_only_the_fee(self) -> None: cost = completion_cost( @@ -219,7 +230,7 @@ class TestAzureModelRouterCostBreakdown: ) assert cost == pytest.approx(ROUTED_FEE, rel=1e-9) - def test_fee_is_the_breakdown_line_item_for_an_unmapped_router_name(self) -> None: + def test_unmapped_router_name_carries_the_fee_as_its_input_cost(self) -> None: logging_obj = _router_logging("azure-model-router") cost = completion_cost( completion_response=_azure_ai_response("azure-model-router"), @@ -229,10 +240,8 @@ class TestAzureModelRouterCostBreakdown: ) breakdown = logging_obj.cost_breakdown assert breakdown is not None - assert breakdown["input_cost"] == 0.0 - assert breakdown.get("additional_costs") == pytest.approx( - {"Azure Model Router Flat Cost": ROUTED_FEE}, rel=1e-9 - ) + assert breakdown["input_cost"] == pytest.approx(ROUTED_FEE, rel=1e-9) + assert "additional_costs" not in breakdown assert cost == pytest.approx(ROUTED_FEE, rel=1e-9) def test_router_request_with_routed_response_charges_the_fee_once(self) -> None: diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py index fab9be1b42c..19b082edd8a 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py @@ -5,131 +5,34 @@ from typing import Final import pytest from pydantic import TypeAdapter -from litellm import cost_per_token, get_model_info +from litellm import completion_cost, cost_per_token from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider +from litellm.types.utils import TranscriptionResponse REPO_ROOT: Final = Path(__file__).parents[4] +MAIN_COST_MAP: Final = REPO_ROOT / "model_prices_and_context_window.json" +BACKUP_COST_MAP: Final = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json" COST_MAP_ADAPTER: Final = TypeAdapter(dict[str, dict[str, object]]) -AZURE_OPENAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" -FOUNDRY_AOAI_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/aoai/" -FOUNDRY_COHERE_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/cohere/" -FOUNDRY_GROK_PRICING: Final = "https://azure.microsoft.com/en-us/pricing/details/ai-foundry-models/grok/" +AZURE_PRICING_PREFIX: Final = "https://azure.microsoft.com/en-us/pricing/details/" +A_MILLION: Final = 1_000_000 @dataclass(frozen=True, slots=True) class TokenPricedCatalogModel: catalog_name: str - mode: str - source: str - input_cost_per_token: float - output_cost_per_token: float - max_input_tokens: int - max_output_tokens: int - cache_read_input_token_cost: float | None - deprecation_date: str | None - supported_flags: tuple[str, ...] + dollars_per_million_input: float + dollars_per_million_output: float TOKEN_PRICED_MODELS: Final = ( - TokenPricedCatalogModel( - catalog_name="gpt-chat-latest", - mode="chat", - source=AZURE_OPENAI_PRICING, - input_cost_per_token=5e-06, - output_cost_per_token=3e-05, - max_input_tokens=272000, - max_output_tokens=128000, - cache_read_input_token_cost=5e-07, - deprecation_date="2026-12-02", - supported_flags=( - "supports_function_calling", - "supports_prompt_caching", - "supports_reasoning", - "supports_response_schema", - "supports_tool_choice", - "supports_vision", - "supports_web_search", - ), - ), - TokenPricedCatalogModel( - catalog_name="codex-mini", - mode="responses", - source=AZURE_OPENAI_PRICING, - input_cost_per_token=1.5e-06, - output_cost_per_token=6e-06, - max_input_tokens=200000, - max_output_tokens=100000, - cache_read_input_token_cost=3.75e-07, - deprecation_date="2026-11-15", - supported_flags=( - "supports_function_calling", - "supports_prompt_caching", - "supports_reasoning", - "supports_vision", - ), - ), - TokenPricedCatalogModel( - catalog_name="model-router", - mode="chat", - source=FOUNDRY_AOAI_PRICING, - input_cost_per_token=1.4e-07, - output_cost_per_token=0.0, - max_input_tokens=200000, - max_output_tokens=32768, - cache_read_input_token_cost=None, - deprecation_date="2027-05-20", - supported_flags=(), - ), - TokenPricedCatalogModel( - catalog_name="cohere-command-a", - mode="chat", - source=FOUNDRY_COHERE_PRICING, - input_cost_per_token=2.5e-06, - output_cost_per_token=1e-05, - max_input_tokens=131072, - max_output_tokens=8182, - cache_read_input_token_cost=None, - deprecation_date=None, - supported_flags=("supports_function_calling", "supports_tool_choice"), - ), - TokenPricedCatalogModel( - catalog_name="grok-4-20-reasoning", - mode="chat", - source=FOUNDRY_GROK_PRICING, - input_cost_per_token=1.25e-06, - output_cost_per_token=2.5e-06, - max_input_tokens=262000, - max_output_tokens=8192, - cache_read_input_token_cost=None, - deprecation_date="2027-04-06", - supported_flags=( - "supports_function_calling", - "supports_reasoning", - "supports_response_schema", - "supports_tool_choice", - "supports_vision", - "supports_web_search", - ), - ), - TokenPricedCatalogModel( - catalog_name="grok-4-20-non-reasoning", - mode="chat", - source=FOUNDRY_GROK_PRICING, - input_cost_per_token=1.25e-06, - output_cost_per_token=2.5e-06, - max_input_tokens=262000, - max_output_tokens=8192, - cache_read_input_token_cost=None, - deprecation_date="2027-04-06", - supported_flags=( - "supports_function_calling", - "supports_response_schema", - "supports_tool_choice", - "supports_vision", - "supports_web_search", - ), - ), + TokenPricedCatalogModel("gpt-chat-latest", 5.0, 30.0), + TokenPricedCatalogModel("codex-mini", 1.5, 6.0), + TokenPricedCatalogModel("model-router", 0.14, 0.0), + TokenPricedCatalogModel("cohere-command-a", 2.5, 10.0), + TokenPricedCatalogModel("grok-4-20-reasoning", 1.25, 2.5), + TokenPricedCatalogModel("grok-4-20-non-reasoning", 1.25, 2.5), ) +GROK_4_20_NAMES: Final = ("grok-4-20-reasoning", "grok-4-20-non-reasoning") CATALOG_NAMES: Final = tuple(spec.catalog_name for spec in TOKEN_PRICED_MODELS) + ("whisper",) @@ -137,60 +40,74 @@ def _cost_map_entry(path: Path, catalog_name: str) -> dict[str, object]: return COST_MAP_ADAPTER.validate_json(path.read_bytes())[f"azure_ai/{catalog_name}"] +@pytest.mark.parametrize("catalog_name", CATALOG_NAMES) +def test_azure_ai_catalog_name_routes_to_azure_ai(catalog_name: str) -> None: + routed_model, provider, _, _ = get_llm_provider(model=f"azure_ai/{catalog_name}") + assert (routed_model, provider) == (catalog_name, "azure_ai") + + @pytest.mark.usefixtures("local_model_cost_map") @pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) -def test_azure_ai_catalog_name_is_priced_and_routed(spec: TokenPricedCatalogModel) -> None: - routed_model, provider, _, _ = get_llm_provider(model=f"azure_ai/{spec.catalog_name}") - assert (routed_model, provider) == (spec.catalog_name, "azure_ai") - - info = get_model_info(model=routed_model, custom_llm_provider=provider) - assert info["litellm_provider"] == "azure_ai" - assert info["mode"] == spec.mode - assert info["input_cost_per_token"] == spec.input_cost_per_token - assert info["output_cost_per_token"] == spec.output_cost_per_token - assert info["cache_read_input_token_cost"] == spec.cache_read_input_token_cost - assert info["max_input_tokens"] == spec.max_input_tokens - assert info["max_output_tokens"] == spec.max_output_tokens - assert info["max_tokens"] == spec.max_output_tokens - for flag in spec.supported_flags: - assert info[flag] is True, flag +def test_azure_ai_catalog_name_costs_a_million_tokens_at_list_price(spec: TokenPricedCatalogModel) -> None: + prompt_cost, completion_cost_usd = cost_per_token( + model=f"azure_ai/{spec.catalog_name}", prompt_tokens=A_MILLION, completion_tokens=A_MILLION + ) + assert prompt_cost == pytest.approx(spec.dollars_per_million_input) + assert completion_cost_usd == pytest.approx(spec.dollars_per_million_output) @pytest.mark.usefixtures("local_model_cost_map") -@pytest.mark.parametrize( - "spec", - [spec for spec in TOKEN_PRICED_MODELS if spec.catalog_name != "model-router"], - ids=lambda spec: spec.catalog_name, -) -def test_azure_ai_catalog_name_costs_a_million_tokens_at_list_price(spec: TokenPricedCatalogModel) -> None: - prompt_cost, completion_cost = cost_per_token( - model=f"azure_ai/{spec.catalog_name}", prompt_tokens=1_000_000, completion_tokens=1_000_000 +@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) +def test_azure_ai_catalog_name_prices_the_same_in_any_casing(spec: TokenPricedCatalogModel) -> None: + lowercase_cost = cost_per_token(model=f"azure_ai/{spec.catalog_name}", prompt_tokens=A_MILLION, completion_tokens=0) + upper_cost = cost_per_token(model=f"azure_ai/{spec.catalog_name.upper()}", prompt_tokens=A_MILLION, completion_tokens=0) + assert upper_cost == lowercase_cost + + +@pytest.mark.usefixtures("local_model_cost_map") +@pytest.mark.parametrize("catalog_name", GROK_4_20_NAMES) +def test_azure_ai_grok_4_20_bills_cached_prompt_tokens_at_the_input_price(catalog_name: str) -> None: + uncached_prompt_cost, _ = cost_per_token(model=f"azure_ai/{catalog_name}", prompt_tokens=A_MILLION, completion_tokens=0) + cached_prompt_cost, _ = cost_per_token( + model=f"azure_ai/{catalog_name}", + prompt_tokens=A_MILLION, + completion_tokens=0, + cache_read_input_tokens=A_MILLION, ) - assert prompt_cost == pytest.approx(spec.input_cost_per_token * 1_000_000) - assert completion_cost == pytest.approx(spec.output_cost_per_token * 1_000_000) + assert uncached_prompt_cost > 0 + assert cached_prompt_cost == pytest.approx(uncached_prompt_cost) @pytest.mark.usefixtures("local_model_cost_map") def test_azure_ai_whisper_catalog_name_is_priced_per_second() -> None: - routed_model, provider, _, _ = get_llm_provider(model="azure_ai/whisper") - assert (routed_model, provider) == ("whisper", "azure_ai") - - info = get_model_info(model=routed_model, custom_llm_provider=provider) - assert info["mode"] == "audio_transcription" - assert info["input_cost_per_second"] == 0.0001 - assert info["output_cost_per_second"] == 0.0001 + transcription: Final = TranscriptionResponse(text="hello") + transcription._hidden_params = { # pyright: ignore[reportPrivateUsage] # TranscriptionResponse exposes no public hidden-params setter + "custom_llm_provider": "azure_ai", + "model": "azure_ai/whisper", + "audio_transcription_duration": 3600, + } + cost = completion_cost( + completion_response=transcription, + model="azure_ai/whisper", + custom_llm_provider="azure_ai", + call_type="atranscription", + ) + assert cost == pytest.approx(0.36) @pytest.mark.parametrize("catalog_name", CATALOG_NAMES) def test_azure_ai_catalog_entry_source_and_backup_match(catalog_name: str) -> None: - main_entry = _cost_map_entry(REPO_ROOT / "model_prices_and_context_window.json", catalog_name) - backup_entry = _cost_map_entry(REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json", catalog_name) + main_entry = _cost_map_entry(MAIN_COST_MAP, catalog_name) + backup_entry = _cost_map_entry(BACKUP_COST_MAP, catalog_name) - assert str(main_entry["source"]).startswith("https://azure.microsoft.com/en-us/pricing/details/") + assert str(main_entry["source"]).startswith(AZURE_PRICING_PREFIX) assert backup_entry == main_entry -@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) -def test_azure_ai_catalog_entry_carries_its_retirement_date(spec: TokenPricedCatalogModel) -> None: - entry = _cost_map_entry(REPO_ROOT / "model_prices_and_context_window.json", spec.catalog_name) - assert entry.get("deprecation_date") == spec.deprecation_date +def test_azure_ai_model_router_spellings_share_one_entry() -> None: + underscore_entry = _cost_map_entry(MAIN_COST_MAP, "model_router") + hyphen_entry = _cost_map_entry(MAIN_COST_MAP, "model-router") + + assert {k: v for k, v in underscore_entry.items() if k != "comment"} == { + k: v for k, v in hyphen_entry.items() if k != "comment" + } From 5706952588ee2b2445e864ce8a85af3339bb138b Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:22:51 -0700 Subject: [PATCH 028/105] fix(azure_ai): drop gpt-chat-latest effort levels, test prices via calculator litellm's azure_ai config rejects reasoning_effort for gpt-chat-latest and Azure documents a fixed reasoning level for it, so the entry no longer advertises reasoning_effort_levels. The catalog metadata tests compare cost_per_token and the whisper transcription cost with the entry the calculator read instead of with list-price literals, the pattern #40195 removed --- ...odel_prices_and_context_window_backup.json | 3 - model_prices_and_context_window.json | 3 - ...azure_ai_foundry_catalog_model_metadata.py | 80 +++++++++---------- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 674a8b98304..7e7d8a9e930 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -3591,9 +3591,6 @@ "max_tokens": 128000, "mode": "chat", "output_cost_per_token": 3e-05, - "reasoning_effort_levels": [ - "medium" - ], "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", "supported_endpoints": [ "/v1/chat/completions", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 674a8b98304..7e7d8a9e930 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -3591,9 +3591,6 @@ "max_tokens": 128000, "mode": "chat", "output_cost_per_token": 3e-05, - "reasoning_effort_levels": [ - "medium" - ], "source": "https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/", "supported_endpoints": [ "/v1/chat/completions", diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py index 19b082edd8a..84d5cd2a7d4 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_foundry_catalog_model_metadata.py @@ -1,11 +1,10 @@ -from dataclasses import dataclass from pathlib import Path from typing import Final import pytest from pydantic import TypeAdapter -from litellm import completion_cost, cost_per_token +from litellm import completion_cost, cost_per_token, get_model_info from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.types.utils import TranscriptionResponse @@ -15,31 +14,39 @@ BACKUP_COST_MAP: Final = REPO_ROOT / "litellm" / "model_prices_and_context_windo COST_MAP_ADAPTER: Final = TypeAdapter(dict[str, dict[str, object]]) AZURE_PRICING_PREFIX: Final = "https://azure.microsoft.com/en-us/pricing/details/" A_MILLION: Final = 1_000_000 +AN_HOUR_IN_SECONDS: Final = 3600 - -@dataclass(frozen=True, slots=True) -class TokenPricedCatalogModel: - catalog_name: str - dollars_per_million_input: float - dollars_per_million_output: float - - -TOKEN_PRICED_MODELS: Final = ( - TokenPricedCatalogModel("gpt-chat-latest", 5.0, 30.0), - TokenPricedCatalogModel("codex-mini", 1.5, 6.0), - TokenPricedCatalogModel("model-router", 0.14, 0.0), - TokenPricedCatalogModel("cohere-command-a", 2.5, 10.0), - TokenPricedCatalogModel("grok-4-20-reasoning", 1.25, 2.5), - TokenPricedCatalogModel("grok-4-20-non-reasoning", 1.25, 2.5), +TOKEN_PRICED_NAMES: Final = ( + "gpt-chat-latest", + "codex-mini", + "model-router", + "cohere-command-a", + "grok-4-20-reasoning", + "grok-4-20-non-reasoning", ) GROK_4_20_NAMES: Final = ("grok-4-20-reasoning", "grok-4-20-non-reasoning") -CATALOG_NAMES: Final = tuple(spec.catalog_name for spec in TOKEN_PRICED_MODELS) + ("whisper",) +CATALOG_NAMES: Final = TOKEN_PRICED_NAMES + ("whisper",) def _cost_map_entry(path: Path, catalog_name: str) -> dict[str, object]: return COST_MAP_ADAPTER.validate_json(path.read_bytes())[f"azure_ai/{catalog_name}"] +def _whisper_transcription_cost(duration_seconds: int) -> float: + transcription: Final = TranscriptionResponse(text="hello") + transcription._hidden_params = { # pyright: ignore[reportPrivateUsage] # TranscriptionResponse exposes no public hidden-params setter + "custom_llm_provider": "azure_ai", + "model": "azure_ai/whisper", + "audio_transcription_duration": duration_seconds, + } + return completion_cost( + completion_response=transcription, + model="azure_ai/whisper", + custom_llm_provider="azure_ai", + call_type="atranscription", + ) + + @pytest.mark.parametrize("catalog_name", CATALOG_NAMES) def test_azure_ai_catalog_name_routes_to_azure_ai(catalog_name: str) -> None: routed_model, provider, _, _ = get_llm_provider(model=f"azure_ai/{catalog_name}") @@ -47,20 +54,22 @@ def test_azure_ai_catalog_name_routes_to_azure_ai(catalog_name: str) -> None: @pytest.mark.usefixtures("local_model_cost_map") -@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) -def test_azure_ai_catalog_name_costs_a_million_tokens_at_list_price(spec: TokenPricedCatalogModel) -> None: +@pytest.mark.parametrize("catalog_name", TOKEN_PRICED_NAMES) +def test_azure_ai_catalog_name_charges_its_own_entry_per_token(catalog_name: str) -> None: + entry: Final = get_model_info(f"azure_ai/{catalog_name}") prompt_cost, completion_cost_usd = cost_per_token( - model=f"azure_ai/{spec.catalog_name}", prompt_tokens=A_MILLION, completion_tokens=A_MILLION + model=f"azure_ai/{catalog_name}", prompt_tokens=A_MILLION, completion_tokens=A_MILLION ) - assert prompt_cost == pytest.approx(spec.dollars_per_million_input) - assert completion_cost_usd == pytest.approx(spec.dollars_per_million_output) + assert prompt_cost > 0 + assert prompt_cost == pytest.approx(A_MILLION * entry["input_cost_per_token"]) + assert completion_cost_usd == pytest.approx(A_MILLION * entry["output_cost_per_token"]) @pytest.mark.usefixtures("local_model_cost_map") -@pytest.mark.parametrize("spec", TOKEN_PRICED_MODELS, ids=lambda spec: spec.catalog_name) -def test_azure_ai_catalog_name_prices_the_same_in_any_casing(spec: TokenPricedCatalogModel) -> None: - lowercase_cost = cost_per_token(model=f"azure_ai/{spec.catalog_name}", prompt_tokens=A_MILLION, completion_tokens=0) - upper_cost = cost_per_token(model=f"azure_ai/{spec.catalog_name.upper()}", prompt_tokens=A_MILLION, completion_tokens=0) +@pytest.mark.parametrize("catalog_name", TOKEN_PRICED_NAMES) +def test_azure_ai_catalog_name_prices_the_same_in_any_casing(catalog_name: str) -> None: + lowercase_cost = cost_per_token(model=f"azure_ai/{catalog_name}", prompt_tokens=A_MILLION, completion_tokens=0) + upper_cost = cost_per_token(model=f"azure_ai/{catalog_name.upper()}", prompt_tokens=A_MILLION, completion_tokens=0) assert upper_cost == lowercase_cost @@ -80,19 +89,10 @@ def test_azure_ai_grok_4_20_bills_cached_prompt_tokens_at_the_input_price(catalo @pytest.mark.usefixtures("local_model_cost_map") def test_azure_ai_whisper_catalog_name_is_priced_per_second() -> None: - transcription: Final = TranscriptionResponse(text="hello") - transcription._hidden_params = { # pyright: ignore[reportPrivateUsage] # TranscriptionResponse exposes no public hidden-params setter - "custom_llm_provider": "azure_ai", - "model": "azure_ai/whisper", - "audio_transcription_duration": 3600, - } - cost = completion_cost( - completion_response=transcription, - model="azure_ai/whisper", - custom_llm_provider="azure_ai", - call_type="atranscription", - ) - assert cost == pytest.approx(0.36) + one_second_cost: Final = _whisper_transcription_cost(1) + one_hour_cost: Final = _whisper_transcription_cost(AN_HOUR_IN_SECONDS) + assert one_second_cost > 0 + assert one_hour_cost == pytest.approx(AN_HOUR_IN_SECONDS * one_second_cost) @pytest.mark.parametrize("catalog_name", CATALOG_NAMES) From 3cadf2f8f7120bf10409a353ef08e4cdc6f78b80 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:35:19 -0700 Subject: [PATCH 029/105] test(azure_ai): charge the router fee over cached prompt tokens too --- .../llms/azure_ai/test_azure_ai_cost_calculator.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py index 7df14b91741..a43fc3332af 100644 --- a/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py +++ b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py @@ -138,6 +138,18 @@ class TestAzureModelRouterFlatCost: assert prompt_cost == pytest.approx(1000 * ROUTER_FEE_PER_TOKEN, rel=1e-9) assert completion_cost_usd == 0.0 + def test_unmapped_router_deployment_name_charges_the_fee_over_cached_prompt_tokens_too(self) -> None: + usage = Usage( + prompt_tokens=2000, + completion_tokens=800, + total_tokens=2800, + cache_read_input_tokens=500, + cache_creation_input_tokens=200, + ) + prompt_cost, completion_cost_usd = cost_per_token(model="azure-model-router", usage=usage) + assert prompt_cost == pytest.approx(2000 * ROUTER_FEE_PER_TOKEN, rel=1e-9) + assert completion_cost_usd == 0.0 + def test_router_deployment_name_as_both_names_charges_the_fee_once(self) -> None: usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) prompt_cost, completion_cost_usd = cost_per_token( From deaadc21d3c84a9eb32c64e6a1e96c5852afdbb6 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:08:03 -0700 Subject: [PATCH 030/105] fix(fireworks_ai): fold instructions and developer items into one leading system message on the Responses path --- .../fireworks_ai/responses/transformation.py | 44 ++++++++---- ...t_fireworks_ai_responses_transformation.py | 70 ++++++++++++++++++- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/litellm/llms/fireworks_ai/responses/transformation.py b/litellm/llms/fireworks_ai/responses/transformation.py index fb0587553d4..61ef0731900 100644 --- a/litellm/llms/fireworks_ai/responses/transformation.py +++ b/litellm/llms/fireworks_ai/responses/transformation.py @@ -5,6 +5,7 @@ from urllib.parse import unquote import httpx from openai.types.responses import EasyInputMessageParam, ResponseInputItemParam +from pydantic import TypeAdapter from litellm.llms.fireworks_ai.common_utils import ( resolve_fireworks_api_key, @@ -31,16 +32,32 @@ def _session_params(litellm_params: GenericLiteLLMParams) -> Mapping[str, object ) -def _developer_item_as_system(item: ResponseInputItemParam) -> ResponseInputItemParam: - if "role" not in item or item["role"] != "developer": - return item - return EasyInputMessageParam(role="system", content=item["content"], type="message") +_instructions_adapter: Final = TypeAdapter[str | None](str | None) -def _developer_items_as_system(input: str | ResponseInputParam) -> str | ResponseInputParam: - if isinstance(input, str): +def _instruction_text(item: ResponseInputItemParam) -> str | None: + if "role" not in item or (item["role"] != "system" and item["role"] != "developer"): + return None + content: Final = item["content"] + if isinstance(content, str): + return content + return "\n\n".join(part["text"] for part in content if part["type"] == "input_text") + + +def _with_single_leading_system_item( + input: str | ResponseInputParam, instructions: str | None +) -> str | ResponseInputParam: + items: Final = () if isinstance(input, str) else tuple(input) + instruction_texts: Final = tuple(text for text in (instructions, *map(_instruction_text, items)) if text) + if not instruction_texts: return input - return [_developer_item_as_system(item) for item in input] + leading: Final = EasyInputMessageParam(role="system", content="\n\n".join(instruction_texts), type="message") + rest: Final = ( + (EasyInputMessageParam(role="user", content=input),) + if isinstance(input, str) + else tuple(item for item in items if _instruction_text(item) is None) + ) + return [leading, *rest] class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): @@ -68,9 +85,6 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): base: Final = (api_base or get_secret_str("FIREWORKS_API_BASE") or FIREWORKS_AI_DEFAULT_API_BASE).rstrip("/") return f"{base}/responses" - def _validate_input_param(self, input: str | ResponseInputParam) -> str | ResponseInputParam: - return _developer_items_as_system(super()._validate_input_param(input)) - def transform_responses_api_request( self, model: str, @@ -79,10 +93,16 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): litellm_params: GenericLiteLLMParams, headers: dict, # mutable-ok: overrides the base class signature ) -> dict: # mutable-ok: overrides the base class signature + instructions: Final = _instructions_adapter.validate_python( + response_api_optional_request_params.get("instructions") + ) + folded_params: Final = { # mutable-ok: the base class takes the optional params as a dict + key: value for key, value in response_api_optional_request_params.items() if key != "instructions" + } return super().transform_responses_api_request( model=resolve_fireworks_resource_name(model), - input=input, - response_api_optional_request_params=response_api_optional_request_params, + input=_with_single_leading_system_item(self._validate_input_param(input), instructions), + response_api_optional_request_params=folded_params, litellm_params=litellm_params, headers=headers, ) diff --git a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py index b9408d44e9a..3462e041a19 100644 --- a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py @@ -162,7 +162,7 @@ def test_responses_call_forwards_previous_response_id_and_store() -> None: assert body["input"][0]["call_id"] == "call_abc123" -def test_responses_call_sends_developer_items_as_system_messages() -> None: +def test_responses_call_hoists_developer_items_into_one_leading_system_message() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): litellm.responses( @@ -176,12 +176,78 @@ def test_responses_call_sends_developer_items_as_system_messages() -> None: ) _, _, body = _sent_request(client) assert tuple(body["input"]) == ( - {"role": "user", "content": "Hi there"}, {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, + {"role": "user", "content": "Hi there"}, {"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]}, ) +def test_responses_call_folds_instructions_and_developer_item_into_one_leading_system_message() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b", + instructions="You are a coding agent running in the Codex CLI.", + input=[ # mutable-ok: the Responses API takes input as a JSON list + { + "role": "developer", + "content": [{"type": "input_text", "text": "read-only"}], + }, + {"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]}, + {"id": "rs_1", "type": "reasoning", "summary": [{"type": "summary_text", "text": "A trivial question."}]}, + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Paris is the capital of France.", "annotations": []}], + }, + {"role": "user", "content": [{"type": "input_text", "text": "And of Spain?"}]}, + ], + store=False, + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert "instructions" not in body + assert tuple(body["input"]) == ( + { + "role": "system", + "content": ( + "You are a coding agent running in the Codex CLI.\n\n" + "read-only" + ), + "type": "message", + }, + {"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]}, + {"id": "rs_1", "type": "reasoning", "summary": [{"type": "summary_text", "text": "A trivial question."}]}, + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Paris is the capital of France.", "annotations": []}], + }, + {"role": "user", "content": [{"type": "input_text", "text": "And of Spain?"}]}, + ) + + +def test_responses_call_turns_string_input_with_instructions_into_system_then_user_messages() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/kimi-k3", + instructions="Answer with exactly one word.", + input="What is the capital of France?", + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert "instructions" not in body + assert tuple(body["input"]) == ( + {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, + {"role": "user", "content": "What is the capital of France?"}, + ) + + def test_responses_call_maps_pydantic_developer_items_and_replays_pydantic_output_items() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) pydantic_input: Final = cast( From 41c0897c7aa21742968715d152b2948a4d0fb83f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:14:41 -0700 Subject: [PATCH 031/105] refactor(cost_map): drop docstrings from the provenance helpers and their tests --- litellm/litellm_core_utils/get_model_cost_map.py | 6 ------ .../litellm_core_utils/test_get_model_cost_map.py | 12 ------------ .../proxy/proxy_server/test_routes_model_cost_map.py | 3 --- 3 files changed, 21 deletions(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 2bdfbc66088..cdc4810ff04 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -45,7 +45,6 @@ def _count_model_entries(model_cost: dict) -> int: def git_blob_id(body: bytes) -> str: - """The sha1 git gives these bytes as a blob, so ``git rev-parse :`` reproduces it for the file""" return hashlib.sha1(b"blob %d\0" % len(body) + body, usedforsecurity=False).hexdigest() @@ -70,7 +69,6 @@ class GetModelCostMap: @staticmethod def load_local_model_cost_map_with_revision() -> "ModelCostMapReloaded": - """The bundled backup map together with the git blob id of the file it was parsed from""" body: Final = GetModelCostMap.read_local_model_cost_map_bytes() content: Final = json.loads(body) return ModelCostMapReloaded(model_cost_map=content, revision=git_blob_id(body)) @@ -413,9 +411,6 @@ class CostMapSourceInfo(CostMapProvenance): def get_model_cost_map_provenance() -> CostMapProvenance: - """Which revision of the cost map this process serves: the git blob id of the bytes it loaded, the - same id ``git rev-parse :model_prices_and_context_window.json`` prints for a checkout, plus - the ETag the remote fetch returned (None for the bundled backup)""" return { "source_revision": _cost_map_source_info.source_revision, "etag": _cost_map_source_info.etag, @@ -520,7 +515,6 @@ def _finalize_model_cost_map(model_cost: dict) -> dict: def _finalize_loaded_model_cost_map(loaded: ModelCostMapReloaded) -> ModelCostMapReloaded: - """Record which bytes this process now serves, then finalize the map they parsed into""" _cost_map_source_info.source_revision = loaded.revision _cost_map_source_info.etag = loaded.etag return replace(loaded, model_cost_map=_finalize_model_cost_map(loaded.model_cost_map)) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 7c3ad283639..18794ea7eec 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -40,8 +40,6 @@ def _bundled_blob_id() -> str: def test_git_blob_id_is_what_git_hash_object_prints(): - """An operator checks a reported revision with ``git hash-object`` or ``git rev-parse :``, - so the id must be git's blob sha1 of the exact bytes, not a plain sha1 or a hash of the parsed JSON.""" assert git_blob_id(b'{"gpt-5.4-mini": {"mode": "chat"}}\n') == "18b9a8381e13a3b38a2128f184f631f95829e987" @@ -516,9 +514,6 @@ async def test_refetch_respects_local_env_override(monkeypatch): @pytest.mark.asyncio async def test_refetch_records_the_blob_id_of_the_bytes_served_and_the_fetch_etag(): - """A reload reports which revision of the map it swapped in: the git blob id of the exact bytes the - fetch returned, so ``git rev-parse :model_prices_and_context_window.json`` can confirm it, - plus the ETag the fetch returned.""" body = _real_map_bytes() client, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"abc123"'}, content=body)]) @@ -532,7 +527,6 @@ async def test_refetch_records_the_blob_id_of_the_bytes_served_and_the_fetch_eta @pytest.mark.asyncio async def test_refetch_revision_follows_the_bytes_not_the_url(): - """Two fetches of the same URL that return different bytes report different revisions.""" edited = json.loads(_real_map_bytes()) edited["gpt-5.4-mini"]["input_cost_per_token"] = 0.5 client, _ = _mock_client( @@ -549,8 +543,6 @@ async def test_refetch_revision_follows_the_bytes_not_the_url(): @pytest.mark.asyncio async def test_refetch_local_override_reports_the_bundled_blob_id_without_an_etag(monkeypatch): - """Forcing the bundled backup after a remote reload must report the backup's own blob id and drop the - remote ETag, since the map served is no longer the one that ETag identifies.""" remote, _ = _mock_client([httpx.Response(200, headers={"ETag": 'W/"remote"'}, content=_real_map_bytes())]) await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=remote) monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") @@ -670,8 +662,6 @@ def test_boot_load_records_the_blob_id_of_the_bytes_served_and_the_fetch_etag(): def test_boot_load_fallback_to_the_backup_reports_its_blob_id_and_drops_the_remote_etag(): - """A boot that lands on the bundled backup reports the backup's own blob id and no ETag, even - when an earlier load in the same process had fetched the remote map.""" remote, _ = _mock_client( [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_real_map_bytes())], client_cls=httpx.Client ) @@ -687,8 +677,6 @@ def test_boot_load_fallback_to_the_backup_reports_its_blob_id_and_drops_the_remo def test_boot_load_that_fails_the_integrity_check_reports_the_backup_not_the_rejected_fetch(): - """A fetch that succeeds but fails integrity validation is thrown away, so the provenance must - describe the backup that got loaded, never the ETag or bytes of the map that was rejected.""" remote, _ = _mock_client( [httpx.Response(200, headers={"ETag": 'W/"boot"'}, content=_real_map_bytes())], client_cls=httpx.Client ) diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py index 0490993a314..36c364fb82b 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py @@ -50,7 +50,6 @@ def _attach_litellm_config(mock_prisma): def _pin_provenance(monkeypatch): - """Fix what this process reports as its cost map revision, independent of the map loaded at import.""" monkeypatch.setattr( "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_provenance", lambda: dict(_PROVENANCE), @@ -110,8 +109,6 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): def test_reload_model_cost_map_surfaces_the_blob_id_of_the_bytes_served_on_every_status_surface( client, auth_as, monkeypatch, mock_prisma ): - """A real refetch through the reload route reports the git blob id of the exact bytes it fetched and - the fetch ETag on the reload response, the source route, and the schedule status alike.""" import httpx import litellm From 4807630c2ab1ab04f3b51c9ef78a5361d78af11d Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:48:01 -0700 Subject: [PATCH 032/105] fix(fireworks_ai): keep non-text system and developer parts on the folded leading system message --- .../fireworks_ai/responses/transformation.py | 36 ++++++++++++++----- ...t_fireworks_ai_responses_transformation.py | 34 ++++++++++++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/litellm/llms/fireworks_ai/responses/transformation.py b/litellm/llms/fireworks_ai/responses/transformation.py index 61ef0731900..660a07181ff 100644 --- a/litellm/llms/fireworks_ai/responses/transformation.py +++ b/litellm/llms/fireworks_ai/responses/transformation.py @@ -4,7 +4,12 @@ from typing import TYPE_CHECKING, Final from urllib.parse import unquote import httpx -from openai.types.responses import EasyInputMessageParam, ResponseInputItemParam +from openai.types.responses import ( + EasyInputMessageParam, + ResponseInputContentParam, + ResponseInputItemParam, + ResponseInputTextParam, +) from pydantic import TypeAdapter from litellm.llms.fireworks_ai.common_utils import ( @@ -35,27 +40,42 @@ def _session_params(litellm_params: GenericLiteLLMParams) -> Mapping[str, object _instructions_adapter: Final = TypeAdapter[str | None](str | None) -def _instruction_text(item: ResponseInputItemParam) -> str | None: +def _instruction_parts(item: ResponseInputItemParam) -> tuple[ResponseInputContentParam, ...] | None: if "role" not in item or (item["role"] != "system" and item["role"] != "developer"): return None content: Final = item["content"] if isinstance(content, str): - return content - return "\n\n".join(part["text"] for part in content if part["type"] == "input_text") + return (ResponseInputTextParam(type="input_text", text=content),) + return tuple(content) + + +def _leading_system_content( + instructions: str | None, parts: tuple[ResponseInputContentParam, ...] +) -> str | list[ResponseInputContentParam]: + text: Final = "\n\n".join( + chunk for chunk in (instructions, *(part["text"] for part in parts if part["type"] == "input_text")) if chunk + ) + non_text: Final = tuple(part for part in parts if part["type"] != "input_text") + if not non_text: + return text + return [ResponseInputTextParam(type="input_text", text=text), *non_text] if text else list(non_text) def _with_single_leading_system_item( input: str | ResponseInputParam, instructions: str | None ) -> str | ResponseInputParam: items: Final = () if isinstance(input, str) else tuple(input) - instruction_texts: Final = tuple(text for text in (instructions, *map(_instruction_text, items)) if text) - if not instruction_texts: + instruction_parts: Final = tuple( + part for item_parts in map(_instruction_parts, items) if item_parts is not None for part in item_parts + ) + content: Final = _leading_system_content(instructions, instruction_parts) + if not content: return input - leading: Final = EasyInputMessageParam(role="system", content="\n\n".join(instruction_texts), type="message") + leading: Final = EasyInputMessageParam(role="system", content=content, type="message") rest: Final = ( (EasyInputMessageParam(role="user", content=input),) if isinstance(input, str) - else tuple(item for item in items if _instruction_text(item) is None) + else tuple(item for item in items if _instruction_parts(item) is None) ) return [leading, *rest] diff --git a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py index 3462e041a19..9d947b25d69 100644 --- a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py @@ -231,6 +231,40 @@ def test_responses_call_folds_instructions_and_developer_item_into_one_leading_s ) +def test_responses_call_keeps_non_text_developer_parts_on_the_leading_system_message() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b", + instructions="Answer with one word.", + input=[ # mutable-ok: the Responses API takes input as a JSON list + { + "role": "developer", + "content": [ + {"type": "input_text", "text": "Match the style of this reference image."}, + {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"}, + ], + }, + {"role": "user", "content": "What is the capital of France?"}, + ], + store=False, + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert "instructions" not in body + assert tuple(body["input"]) == ( + { + "role": "system", + "content": [ + {"type": "input_text", "text": "Answer with one word.\n\nMatch the style of this reference image."}, + {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"}, + ], + "type": "message", + }, + {"role": "user", "content": "What is the capital of France?"}, + ) + + def test_responses_call_turns_string_input_with_instructions_into_system_then_user_messages() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): From 2285640eeab7206f609d54286df0bac4130987f7 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:02:39 -0700 Subject: [PATCH 033/105] fix(spend-tracking): recover key alias for session tokens from spend logs CLI session tokens are in-memory only and never get a LiteLLM_VerificationToken row, so the usage APIs could not resolve key_alias, team_id, or user_email for their spend rows: the exact join and the reverse-hash recovery both miss. The owner is written to LiteLLM_SpendLogs.metadata at request time under the same hashed api_key, so read it back from there for keys still unresolved after the token-table passes. The lookup is sha256-gated like the existing reverse-hash recovery and bounded to the records' startTime window (min date minus one day, max date plus two) so it stays on the startTime index. No migration. Also guard the window parser against the date=None rollup rows GROUPING SETS aggregation emits, which raised TypeError from strptime and turned the aggregated usage endpoints into HTTP 500s. --- .../common_daily_activity.py | 51 ++++++++-- .../spend_tracking/key_metadata_recovery.py | 48 ++++++++++ .../test_common_daily_activity.py | 53 +++++++++++ .../test_key_metadata_recovery.py | 95 ++++++++++++++++++- 4 files changed, 238 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/management_endpoints/common_daily_activity.py b/litellm/proxy/management_endpoints/common_daily_activity.py index ce6a97708ab..c6c5aad5926 100644 --- a/litellm/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/proxy/management_endpoints/common_daily_activity.py @@ -14,6 +14,7 @@ from litellm.proxy._types import CommonProxyErrors from litellm.proxy.spend_tracking.key_metadata_recovery import ( attach_user_emails, recover_double_hashed_key_metadata, + recover_key_metadata_from_spend_logs, ) from litellm.proxy.spend_tracking.ptu_feature_flag import is_ptu_cost_attribution_enabled from litellm.proxy.utils import PrismaClient @@ -433,15 +434,37 @@ def update_breakdown_metrics( return breakdown +def _spend_logs_window(dates: AbstractSet[str | None]) -> tuple[datetime, datetime] | None: + parsed: Final = sorted(day for day in (_parse_spend_date(raw) for raw in dates) if day is not None) + if not parsed: + return None + return (parsed[0] - timedelta(days=1), parsed[-1] + timedelta(days=2)) + + +def _parse_spend_date(raw: str | None) -> datetime | None: + if not isinstance(raw, str): + return None + try: + return datetime.fromisoformat(raw) + except ValueError: + return None + + +_EMPTY_KEY_METADATA: Final[Mapping[str, _KeyMetadataDict]] = MappingProxyType({}) + + async def get_api_key_metadata( prisma_client: PrismaClient, api_keys: AbstractSet[str], + spend_logs_window: tuple[datetime, datetime] | None = None, ) -> Mapping[str, _KeyMetadataDict]: """Get api key metadata, falling back to deleted keys table for keys not found in active table. This ensures that key_alias and team_id are preserved in historical activity logs even after a key is deleted or regenerated. Also recovers aliases for api_key - values that were double-hashed by the v1.99 spend-log provenance gate. + values that were double-hashed by the v1.99 spend-log provenance gate, and, when + spend_logs_window is given, for keys never written to either token table (CLI + session tokens) from the spend-log rows those requests wrote in that window. """ key_records: Sequence[PrismaVerificationToken] = await VerificationTokenRepository(prisma_client).table.find_many( where={"token": {"in": list(api_keys)}} @@ -481,11 +504,17 @@ async def get_api_key_metadata( ) still_missing: Final = api_keys - frozenset(result) - combined: Final = ( - result - if not still_missing - else MappingProxyType({**result, **(await recover_double_hashed_key_metadata(prisma_client, still_missing))}) + from_reverse_hash: Final = ( + await recover_double_hashed_key_metadata(prisma_client, still_missing) if still_missing else _EMPTY_KEY_METADATA ) + after_token_recovery: Final = MappingProxyType({**result, **from_reverse_hash}) + unresolved: Final = api_keys - frozenset(after_token_recovery) + from_spend_logs: Final = ( + await recover_key_metadata_from_spend_logs(prisma_client, unresolved, spend_logs_window) + if unresolved and spend_logs_window is not None + else _EMPTY_KEY_METADATA + ) + combined: Final = MappingProxyType({**after_token_recovery, **from_spend_logs}) return await attach_user_emails(prisma_client, combined) @@ -898,7 +927,9 @@ async def _aggregate_spend_records( api_key_metadata: dict[str, _KeyMetadataDict] = {} if api_keys: - api_key_metadata = await get_api_key_metadata(prisma_client, api_keys) + api_key_metadata = await get_api_key_metadata( + prisma_client, api_keys, _spend_logs_window(frozenset(record.date for record in records)) + ) return await asyncio.to_thread( _aggregate_spend_records_sync, @@ -1094,7 +1125,9 @@ async def _aggregate_grouping_sets_records( api_key_metadata: dict[str, _KeyMetadataDict] = {} if api_keys: - api_key_metadata = await get_api_key_metadata(prisma_client, api_keys) + api_key_metadata = await get_api_key_metadata( + prisma_client, api_keys, _spend_logs_window(frozenset(r.date for r in records)) + ) return await asyncio.to_thread( _aggregate_grouping_sets_records_sync, @@ -1357,7 +1390,9 @@ async def get_daily_activity_aggregated( r.api_key for r in entity_records if r.api_key and r.api_key != PTU_SENTINEL_API_KEY ) entity_key_metadata: Final = ( - await get_api_key_metadata(prisma_client, entity_api_keys) + await get_api_key_metadata( + prisma_client, entity_api_keys, _spend_logs_window(frozenset(r.date for r in entity_records)) + ) if entity_api_keys else {} # mutable-ok: matches the helper's dict return ) diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index 7de18521edd..81c1f4cf119 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -1,5 +1,6 @@ from collections.abc import Awaitable, Callable, Mapping, Sequence from collections.abc import Set as AbstractSet +from datetime import datetime from types import MappingProxyType from typing import Final, TypeVar @@ -27,6 +28,19 @@ WHERE encode(sha256(convert_to(token, 'UTF8')), 'hex') = ANY($1::text[]) ORDER BY token, deleted_at DESC """ +_SPEND_LOG_ALIAS_SQL: Final = """ +SELECT DISTINCT ON (api_key) + api_key AS digest, + metadata->>'user_api_key_alias' AS key_alias, + COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, + COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id +FROM "LiteLLM_SpendLogs" +WHERE api_key = ANY($1::text[]) + AND "startTime" >= $2::timestamp + AND "startTime" < $3::timestamp +ORDER BY api_key, (metadata->>'user_api_key_alias') IS NULL, "startTime" DESC +""" + class KeyMetadataDict(TypedDict, total=False): key_alias: ReadOnly[str | None] @@ -168,6 +182,40 @@ async def recover_double_hashed_key_metadata( return MappingProxyType({**from_active, **from_deleted}) +async def recover_key_metadata_from_spend_logs( + prisma_client: PrismaClient, + missing_keys: AbstractSet[str], + window: tuple[datetime, datetime], +) -> Mapping[str, KeyMetadataDict]: + """ + Recover key_alias/team_id/user_id for hashed api_key values absent from both + verification-token tables, e.g. in-memory CLI session tokens that never get a + token row. Their owner is written to LiteLLM_SpendLogs metadata at request + time under the same hashed api_key, so it is the only surviving source. Only + sha256 digests are looked up, matching the reverse-hash recovery gate, since + every current api_key value in spend logs is a token hash. The [start, end) + bound keeps the lookup on the startTime index instead of scanning the table. + """ + sha_missing: Final = frozenset(key for key in missing_keys if is_valid_sha256_hash(key)) + if not sha_missing: + return _EMPTY_KEY_METADATA + start, end = window + rows: Final = await _db_or_empty( + lambda: prisma_client.db.query_raw(_SPEND_LOG_ALIAS_SQL, sorted(sha_missing), start, end), + "Failed spend-log alias recovery for %d missing keys: %s", + len(sha_missing), + ) + if rows is None: + return _EMPTY_KEY_METADATA + return MappingProxyType( + { + row.digest: KeyMetadataDict(key_alias=row.key_alias, team_id=row.team_id, user_id=row.user_id) + for row in _TOKEN_DIGEST_ROWS.validate_python(rows) + if row.digest in sha_missing and (row.key_alias or row.user_id or row.team_id) + } + ) + + def _row_with_recovered_fields( row: Mapping[str, object], recovered: Mapping[str, KeyMetadataDict], diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index 37a54c4901a..4d3dc320d8f 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -2105,3 +2105,56 @@ async def test_get_daily_activity_aggregated_with_entity_breakdown(): # Rollups with the entity bit set must still land in their usual buckets assert daily.breakdown.models["gpt-4o"].metrics.spend == 18.0 assert daily.breakdown.api_keys["key-1"].metrics.spend == 12.0 + + +@pytest.mark.asyncio +async def test_get_api_key_metadata_resolves_session_key_via_spend_log_window(): + """ + A CLI session token has no verification-token row, so the active/deleted lookups + and reverse-hash all miss. Given a spend-log window, its alias and owner are + recovered from the spend-log metadata and its email is filled from the user table. + """ + from litellm.proxy.utils import hash_token + + session_digest = hash_token("cli-session-user-42") + mock_prisma = MagicMock() + mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_deletedverificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_usertable.find_many = AsyncMock( + return_value=[SimpleNamespace(user_id="user-42", user_email="user42@example.com")] + ) + + async def query_raw(sql, *params): + if "LiteLLM_SpendLogs" in sql: + return [{"digest": session_digest, "key_alias": "cli-session-user-42", "team_id": None, "user_id": "user-42"}] + return [] + + mock_prisma.db.query_raw = AsyncMock(side_effect=query_raw) + + result = await get_api_key_metadata( + prisma_client=mock_prisma, + api_keys={session_digest}, + spend_logs_window=(datetime(2026, 9, 7), datetime(2026, 9, 10)), + ) + + assert result[session_digest]["key_alias"] == "cli-session-user-42" + assert result[session_digest]["user_id"] == "user-42" + assert result[session_digest]["user_email"] == "user42@example.com" + spend_log_calls = [call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0]] + ((_, digests, start, end),) = spend_log_calls + assert digests == [session_digest] + assert (start, end) == (datetime(2026, 9, 7), datetime(2026, 9, 10)) + + +def test_spend_logs_window_pads_min_minus_one_day_and_max_plus_two_days(): + from litellm.proxy.management_endpoints.common_daily_activity import _spend_logs_window + + window = _spend_logs_window({"2026-09-08", "2026-09-05", "not-a-date"}) + + assert window == (datetime(2026, 9, 4), datetime(2026, 9, 10)) + + +def test_spend_logs_window_is_none_when_no_date_parses(): + from litellm.proxy.management_endpoints.common_daily_activity import _spend_logs_window + + assert _spend_logs_window({"garbage", ""}) is None diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 7a80319239d..9e42f017106 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -1,4 +1,5 @@ from collections.abc import Sequence +from datetime import datetime from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock @@ -8,14 +9,24 @@ from prisma.errors import PrismaError from litellm.proxy.spend_tracking.key_metadata_recovery import ( fill_missing_api_key_aliases, recover_double_hashed_key_metadata, + recover_key_metadata_from_spend_logs, ) from litellm.proxy.utils import hash_token -def _digest_row(digest: str, key_alias: str, team_id: str | None, user_id: str | None) -> dict[str, str | None]: +def _digest_row(digest: str, key_alias: str | None, team_id: str | None, user_id: str | None) -> dict[str, str | None]: return {"digest": digest, "key_alias": key_alias, "team_id": team_id, "user_id": user_id} +def _query_raw_spend_logs(rows: Sequence[dict[str, str | None]]) -> AsyncMock: + async def query_raw(sql: str, *params: object) -> list[dict[str, str | None]]: + if '"LiteLLM_SpendLogs"' in sql: + return list(rows) + raise AssertionError(f"unexpected query: {sql}") + + return AsyncMock(side_effect=query_raw) + + def _query_raw_by_table( active_rows: Sequence[dict[str, str | None]], deleted_rows: Sequence[dict[str, str | None]], @@ -218,3 +229,85 @@ async def test_fill_missing_api_key_aliases_skips_named_keys_that_have_no_email( assert filled == rows mock_prisma.db.query_raw.assert_not_called() + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_resolves_session_token_from_metadata(): + """ + CLI session tokens never get a verification-token row, so both the exact join and + the reverse-hash lookup miss them. Their owner survives only in the spend-log + metadata written at request time, keyed by the same hashed api_key. + """ + session_digest = hash_token("cli-session-repro-user-6852") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs( + [_digest_row(session_digest, "cli-session-repro-user-6852", None, "repro-user-6852")] + ) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {session_digest}, window) + + assert result[session_digest]["key_alias"] == "cli-session-repro-user-6852" + assert result[session_digest]["user_id"] == "repro-user-6852" + ((_, digests, start, end),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + assert digests == [session_digest] + assert (start, end) == window + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_skips_query_when_no_missing_keys(): + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, set(), window) + + assert result == {} + mock_prisma.db.query_raw.assert_not_called() + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_returns_empty_on_prisma_error(): + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("db down")) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {hash_token("cli-session-x")}, window) + + assert result == {} + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_ignores_foreign_and_all_null_rows(): + wanted = hash_token("cli-session-wanted") + all_null = hash_token("cli-session-null") + foreign = hash_token("cli-session-foreign") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs( + [ + _digest_row(wanted, "kept-alias", None, "owner-1"), + _digest_row(all_null, None, None, None), + _digest_row(foreign, "foreign-alias", None, "owner-2"), + ] + ) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {wanted, all_null}, window) + + assert set(result) == {wanted} + assert result[wanted]["key_alias"] == "kept-alias" + assert result[wanted]["user_id"] == "owner-1" + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_skips_non_sha256_keys(): + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + + result = await recover_key_metadata_from_spend_logs( + mock_prisma, {"cli-session-raw-1798", "key-hash-short"}, window + ) + + assert result == {} + mock_prisma.db.query_raw.assert_not_called() From 629b464cd68114e595b584c9fdde86484f8c219d Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 12:06:52 -0700 Subject: [PATCH 034/105] feat(auto_router): opt-in NON_REASONING tier below SIMPLE Agent harnesses send a lot of operational turns that relay or reformat tool output rather than reason about it, and the cheapest built-in tier was SIMPLE. NON_REASONING adds a rung below it, behind enable_non_reasoning_tier so an already-deployed router cannot move. The toggle is what keeps it safe. The tier set feeds the classifier rubric, the response-format enum, the escalation ladder and the savings baseline, so a default-on fifth tier would have changed what every existing router sends and where its traffic lands. Off, the ladder, rubric, wire labels and baseline are byte-identical to before. On, the rung is added at index 0, escalation walks up out of it, and it can never win the savings baseline. It requires an llm or custom classifier and a model of its own: the v1 score ladder has no rung below simple_medium and the v2 artifact is trained on four classes, so the heuristic scorers cannot produce the tier and a router that enabled it there would pay for a bullet nothing reaches. The dashboard follows the same flag, and the edit modal now reads the tier back from the stored config rather than assuming four keys, since it rewrites tiers wholesale on save and would otherwise delete a hand-written tier on any edit. --- .../public_endpoints/public_endpoints.py | 3 + .../classification_rubrics.py | 5 + .../complexity_router/complexity_router.py | 46 +++- .../complexity_router/config.py | 91 +++++++- .../public_endpoints/public_endpoints.py | 9 +- .../router_strategy/test_complexity_router.py | 197 +++++++++++++++++- .../add_model/ComplexityRouterConfig.test.tsx | 4 +- .../add_model/ComplexityRouterConfig.tsx | 68 +++++- .../components/add_model/KeywordTierRules.tsx | 2 +- .../add_model/add_auto_router_tab.tsx | 1 + .../build_complexity_router_config.ts | 6 + .../add_model/complexity_router_tiers.ts | 7 +- .../components/add_model/tier_rows.test.ts | 31 +++ .../src/components/add_model/tier_rows.ts | 18 +- .../components/add_model/tier_set_actions.ts | 9 +- ...d_updated_complexity_router_config.test.ts | 49 +++++ .../edit_auto_router_modal.tsx | 14 +- ui/litellm-dashboard/src/lib/http/schema.d.ts | 20 +- 18 files changed, 541 insertions(+), 39 deletions(-) diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 94a59828451..a7a24e10bdd 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -534,6 +534,9 @@ async def get_autorouter_presets( "/public/autorouter_presets", tags=["public", "auto router"], # mutable-ok: FastAPI route tags take a list response_model=dict[str, AutoRouterPresetRecord], + # An optional tier a preset does not set must not reach the dashboard as a null pool, which the + # template picker would render as an empty tier row the operator never asked for. + response_model_exclude_none=True, ) async def get_public_autorouter_presets() -> Mapping[str, AutoRouterPresetRecord]: """ diff --git a/litellm/router_strategy/complexity_router/classification_rubrics.py b/litellm/router_strategy/complexity_router/classification_rubrics.py index 9f168eabbc4..1dae2902fad 100644 --- a/litellm/router_strategy/complexity_router/classification_rubrics.py +++ b/litellm/router_strategy/complexity_router/classification_rubrics.py @@ -108,6 +108,11 @@ _CALIBRATION_EXAMPLES: Final[Mapping[ClassificationRubric, str]] = MappingProxyT BUSINESS_TIER_CRITERIA: Final[Mapping[ComplexityTier, str]] = MappingProxyType( { + ComplexityTier.NON_REASONING: ( + "operational requests whose whole job is to pass information along or put it in a requested " + "shape: relaying or reformatting tool or system output, acknowledging a completed action, or " + "extracting a stated field. Use it only when no judgment about the content is asked for." + ), ComplexityTier.SIMPLE: ( "greetings, chitchat, or lookups of a fact, policy, price, or date with a short known answer. " "Never for analysis, strategy, or non-trivial work, even if the request is only one sentence." diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index c8644f52c57..fd0d53e0902 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -100,7 +100,12 @@ else: class TierClassification(BaseModel): - """Structured response schema for the LLM-based complexity classifier.""" + """Structured response schema for the LLM-based complexity classifier. + + The four-tier ladder, which is what a router that did not opt into NON_REASONING sends. The + enum actually put on the wire is rebuilt per router from `classifier_wire_labels`, so a + five-tier or renamed ladder widens it there rather than here. + """ tier: Literal["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] @@ -116,8 +121,20 @@ def _tier_name(tier: ComplexityTier | str) -> str: return tier.value if isinstance(tier, ComplexityTier) else tier +def _built_in_tier_or_none(tier_name: str) -> ComplexityTier | None: + """The built-in tier a `tiers` key names, or None when the key is an operator-defined name.""" + return ComplexityTier.__members__.get(tier_name) + + _CLASSIFICATION_TIER_CRITERIA: Final[Mapping[ComplexityTier, str]] = MappingProxyType( { + ComplexityTier.NON_REASONING: ( + "operational requests whose whole job is to pass information along or put it in a " + "requested shape: relaying or reformatting tool output, acknowledging a completed action, " + "or extracting a stated value. Use it only when no judgment about the content is asked for; " + "the moment the request is to summarize, compare, explain, debug, or decide, it belongs " + "in a higher tier however short it is." + ), ComplexityTier.SIMPLE: ( "greetings, chitchat, or factual lookups with a short known answer. Do not use this tier for " "unsolved problems, proofs, deep theory, multi-step analysis, or non-trivial code, even if the " @@ -1228,7 +1245,7 @@ class ComplexityRouter(CustomLogger): """ if self.config.has_custom_tiers: return tuple(dict.fromkeys(model for models in self._tier_pools().values() for model in models)) - for tier in reversed(TIER_SEVERITY_ORDER): + for tier in reversed(self.config.active_tier_severity_order()): models = self.config.tiers.get(tier.value) if models: return tuple(models) if isinstance(models, list) else (models,) @@ -1863,7 +1880,11 @@ class ComplexityRouter(CustomLogger): default_model: Final = self.config.default_model pools: Final = self._tier_pools() tier: Final = next( - (candidate for candidate in TIER_SEVERITY_ORDER if default_model in pools.get(candidate.value, ())), + ( + candidate + for candidate in self.config.active_tier_severity_order() + if default_model in pools.get(candidate.value, ()) + ), ComplexityTier.MEDIUM, ) return ClassificationOutcome( @@ -2248,7 +2269,8 @@ class ComplexityRouter(CustomLogger): return self._fitting_tier_fallback(classified_tier, fit_filter) request_type: Final = classify_prompt(user_message) - classified_idx: Final = TIER_SEVERITY_ORDER.index(classified_tier) + severity_order: Final = self.config.active_tier_severity_order() + classified_idx: Final = severity_order.index(classified_tier) pools: Final = self._tier_pools() classified_candidates: Final = _allowed(tuple(pools.get(_tier_name(classified_tier), ())), fit_filter) cold_start_candidates: Final = tuple( @@ -2313,7 +2335,7 @@ class ComplexityRouter(CustomLogger): else: model_tiers = self._model_tiers.get(model, (classified_tier,)) distance = min( - abs(TIER_SEVERITY_ORDER.index(model_tier) - classified_idx) for model_tier in model_tiers + abs(severity_order.index(model_tier) - classified_idx) for model_tier in model_tiers ) score = quality_weight * quality_sample + cost_weight * cost_score - penalty_weight * distance candidate_scores.append( @@ -2610,10 +2632,15 @@ class ComplexityRouter(CustomLogger): def _tier_for_model(self, model: str) -> ComplexityTier | None: """Return the most-severe configured tier whose pool contains this model.""" pools: Final = self._tier_pools() - matched: Final = tuple(ComplexityTier(tier_name) for tier_name, models in pools.items() if model in models) + order: Final = self.config.active_tier_severity_order() + matched: Final = tuple( + tier + for tier_name, models in pools.items() + if model in models and (tier := _built_in_tier_or_none(tier_name)) is not None and tier in order + ) if not matched: return None - return max(matched, key=TIER_SEVERITY_ORDER.index) + return max(matched, key=order.index) def _escalate_tier(self, tier: ComplexityTier | str) -> ComplexityTier | str: """Bump a tier one step up to the next-higher configured tier. @@ -2628,9 +2655,10 @@ class ComplexityRouter(CustomLogger): if self.config.has_custom_tiers: return tier configured: Final = frozenset(self.config.tiers) - current_index: Final = TIER_SEVERITY_ORDER.index(tier) + order: Final = self.config.active_tier_severity_order() + current_index: Final = order.index(tier) higher_tiers: Final = tuple( - candidate for candidate in TIER_SEVERITY_ORDER[current_index + 1 :] if candidate.value in configured + candidate for candidate in order[current_index + 1 :] if candidate.value in configured ) return higher_tiers[0] if higher_tiers else tier diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index 3ec9f9b5394..bdfa0c9c389 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -29,6 +29,7 @@ from .tier_predictor import TrainedTierArtifact class ComplexityTier(str, Enum): """Complexity tiers for routing decisions.""" + NON_REASONING = "NON_REASONING" SIMPLE = "SIMPLE" MEDIUM = "MEDIUM" COMPLEX = "COMPLEX" @@ -55,6 +56,11 @@ DEFAULT_CLASSIFICATION_RUBRIC: Final[ClassificationRubric] = ClassificationRubri LLM_CLASSIFIER_TYPES: Final[frozenset[str]] = frozenset({"llm", "heuristic_first", "hybrid"}) +# The ladder as it has always shipped. NON_REASONING is absent because it is opt-in: an existing +# router must not gain a rubric bullet, a wire label, or a rung it never configured, and the +# heuristic_v2 artifact is trained on exactly these four classes. Read the active ladder off the +# config (`tier_names`, `active_tier_severity_order`) rather than this constant wherever the +# operator's `enable_non_reasoning_tier` can reach. TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( ComplexityTier.SIMPLE, ComplexityTier.MEDIUM, @@ -62,6 +68,16 @@ TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( ComplexityTier.REASONING, ) +NON_REASONING_TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( + ComplexityTier.NON_REASONING, + *TIER_SEVERITY_ORDER, +) + + +def tier_severity_order(non_reasoning_enabled: bool) -> tuple[ComplexityTier, ...]: + """The built-in ladder for one router, tier 0 included only when it opted in.""" + return NON_REASONING_TIER_SEVERITY_ORDER if non_reasoning_enabled else TIER_SEVERITY_ORDER + DEFAULT_TIER_DISTANCE_PENALTY: Final[float] = 0.5 DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE: Final[int] = 3 @@ -142,6 +158,9 @@ def normalize_classification_examples(value: str | None) -> str | None: return _normalize_operator_section(value, "classification_examples", MAX_CLASSIFICATION_EXAMPLES_CHARS) +_BUILT_IN_TIER_NAMES: Final[str] = ", ".join(ComplexityTier.__members__) + + class TierDefinition(BaseModel): """An operator-defined tier: the name the LLM classifier must return and its rubric description.""" @@ -152,7 +171,7 @@ class TierDefinition(BaseModel): default=None, description=( "What belongs in this tier; rendered as this tier's bullet in the classifier rubric. " - "Required unless the name is a built-in tier (SIMPLE/MEDIUM/COMPLEX/REASONING), which " + f"Required unless the name is a built-in tier ({_BUILT_IN_TIER_NAMES}), which " "inherits the built-in criteria when omitted" ), ) @@ -174,7 +193,7 @@ class TierDefinition(BaseModel): if description is None and name.upper() not in ComplexityTier.__members__: raise ValueError( f"tier_definitions entry {name!r} must have a description: only the built-in tiers " - "(SIMPLE, MEDIUM, COMPLEX, REASONING) carry one the rubric can inherit" + f"({_BUILT_IN_TIER_NAMES}) carry one the rubric can inherit" ) rendered_on_one_line: Final = (name, description or "") if any("\n" in part or "\r" in part for part in rendered_on_one_line): @@ -703,6 +722,20 @@ class ComplexityRouterConfig(BaseModel): default_factory=dict, ) + enable_non_reasoning_tier: bool = Field( + default=False, + description=( + "Add NON_REASONING as a fifth built-in tier below SIMPLE, for operational agent traffic " + "that relays or reformats information rather than reasoning about it. Off by default: " + "turning it on adds a rung to this router's ladder, a bullet to the LLM classifier's " + "rubric, and a value the classifier may return, all of which move tier decisions and " + "spend on an already-deployed router. Requires an LLM classifier or a custom classifier " + "plugin, since the heuristic scorers cannot produce the tier, and a model in `tiers` " + "under the NON_REASONING key. Escalation still walks up from it, and it is never the " + "savings baseline or a `heuristic_v2` prediction." + ), + ) + tier_definitions: tuple[TierDefinition, ...] | None = Field( default=None, description=( @@ -1491,11 +1524,16 @@ class ComplexityRouterConfig(BaseModel): which still makes it a dependency on every one of those requests.""" return self.classifier_type in LLM_CLASSIFIER_TYPES + def active_tier_severity_order(self) -> tuple[ComplexityTier, ...]: + """This router's built-in ladder, ascending. Meaningless for a custom tier set, whose + severity order is tier_definitions list order over names that are not enum members.""" + return tier_severity_order(self.enable_non_reasoning_tier) + def tier_names(self) -> tuple[str, ...]: """The active tier names: the defined names, or the built-in set in severity order.""" if self.tier_definitions is not None: return tuple(definition.name for definition in self.tier_definitions) - return tuple(tier.value for tier in TIER_SEVERITY_ORDER) + return tuple(tier.value for tier in self.active_tier_severity_order()) def classifier_wire_labels(self) -> tuple[str, ...]: """The tier names the classifier is told to emit: defined names, or the display labels.""" @@ -1587,6 +1625,41 @@ class ComplexityRouterConfig(BaseModel): if present ) + @model_validator(mode="after") + def _validate_non_reasoning_tier(self) -> "ComplexityRouterConfig": + """Gate the opt-in fifth tier on the two things that make it reachable and routable. + + The heuristic scorers cannot emit it (the v1 score ladder has no rung below simple_medium + and the v2 artifact is trained on four classes), so a router whose classifier can never + return the tier would pay for a rubric bullet and a configured pool that no request reaches. + """ + non_reasoning_key: Final = ComplexityTier.NON_REASONING.value + if not self.enable_non_reasoning_tier: + if not self.has_custom_tiers and non_reasoning_key in self.tiers: + raise ValueError( + f"tiers names {non_reasoning_key} but enable_non_reasoning_tier is False, so no request " + "can route there; set enable_non_reasoning_tier: true or drop the tier" + ) + return self + if self.has_custom_tiers: + raise ValueError( + "enable_non_reasoning_tier cannot be combined with tier_definitions: a custom tier set " + f"replaces the built-in ladder, so name a tier {non_reasoning_key} in tier_definitions instead" + ) + if self.classifier_type not in ("llm", "custom"): + raise ValueError( + f"enable_non_reasoning_tier requires classifier_type 'llm' or 'custom', got " + f"{self.classifier_type!r}: the heuristic scorers only produce the four tiers from SIMPLE up, " + f"so nothing would ever classify as {non_reasoning_key}" + ) + if not self.tiers.get(non_reasoning_key): + raise ValueError( + f"enable_non_reasoning_tier requires tiers to map {non_reasoning_key} to at least one model: " + "the tier exists to send operational traffic somewhere cheaper, and an unconfigured tier " + "would fall through to the default model" + ) + return self + @model_validator(mode="after") def _validate_tier_definitions(self) -> "ComplexityRouterConfig": if self.tier_definitions is None: @@ -1609,7 +1682,7 @@ class ComplexityRouterConfig(BaseModel): if self.classifier_type in ("heuristic", "heuristic_v2", "heuristic_first", "hybrid"): raise ValueError( "tier_definitions requires classifier_type 'llm' or 'custom': the heuristic scorer only " - "produces the four built-in tiers, as does heuristic_v2" + "produces the built-in tiers from SIMPLE up, as does heuristic_v2" ) conflicts: Final = self._tier_definition_conflicts() if conflicts: @@ -1762,16 +1835,18 @@ class ComplexityRouterConfig(BaseModel): return self.tier_labels.get(tier, "").strip() or tier.value def labeled_tiers(self) -> tuple[tuple[ComplexityTier, str], ...]: - """Every tier paired with its display name, in ascending severity order.""" - return tuple((tier, self.tier_label(tier)) for tier in TIER_SEVERITY_ORDER) + """Every active tier paired with its display name, in ascending severity order.""" + return tuple((tier, self.tier_label(tier)) for tier in self.active_tier_severity_order()) def tier_for_label(self, label: str) -> ComplexityTier | None: - """Resolve a display name back to its tier, case-insensitively, then canonical names.""" + """Resolve a display name back to its active tier, case-insensitively, then canonical + names. A tier this router did not opt into resolves to None, so a classifier naming + NON_REASONING on a four-tier router is an unparseable reply rather than a fifth rung.""" folded: Final = label.strip().casefold() labeled: Final = self.labeled_tiers() return next( (tier for tier, tier_label in labeled if tier_label.casefold() == folded), - next((tier for tier in TIER_SEVERITY_ORDER if tier.value.casefold() == folded), None), + next((tier for tier, _ in labeled if tier.value.casefold() == folded), None), ) diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index fa73926305b..fb5a37c45fc 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -74,10 +74,14 @@ class SupportedEndpointsResponse(BaseModel): class AutoRouterPresetTiers(BaseModel): - """Exactly the four built-in tiers the dashboard's preset prefill can apply. + """The built-in tiers the dashboard's preset prefill can apply. extra="forbid" on purpose: a tier name this dashboard cannot apply would grey out or crash the - picker, so such a catalog is rejected wholesale and the bundled one serves instead. + picker, so such a catalog is rejected wholesale and the bundled one serves instead. NON_REASONING + is optional rather than required so this proxy accepts both a catalog that ships the opt-in fifth + tier and the four-tier catalogs that predate it. It stays None when unset rather than defaulting + to an empty pool, so a four-tier preset serves the tier set it was published with instead of + growing a key the dashboard would render as an empty fifth tier row. """ model_config = ConfigDict(extra="forbid") @@ -86,6 +90,7 @@ class AutoRouterPresetTiers(BaseModel): MEDIUM: Sequence[str] COMPLEX: Sequence[str] REASONING: Sequence[str] + NON_REASONING: Sequence[str] | None = None class AutoRouterPresetConfig(BaseModel): diff --git a/tests/test_litellm/router_strategy/test_complexity_router.py b/tests/test_litellm/router_strategy/test_complexity_router.py index 5b1d8562abd..ce9cd5d3b7d 100644 --- a/tests/test_litellm/router_strategy/test_complexity_router.py +++ b/tests/test_litellm/router_strategy/test_complexity_router.py @@ -44,6 +44,7 @@ from litellm.router_strategy.complexity_router.config import ( DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE, DEFAULT_COMPLEXITY_CONFIG, DEFAULT_TECHNICAL_KEYWORDS, + TIER_SEVERITY_ORDER, ClassificationRubric, ClassifierLLMConfig, ComplexityRouterConfig, @@ -11044,7 +11045,7 @@ async def test_tier_model_params_reach_the_hook_response_and_override_client_val async def test_tier_params_mask_credentials_in_routing_decision(route, mock_router_instance): params = {"reasoning_effort": "xhigh", "api_key": "secret-tier-key"} config = { - "tiers": {tier.value: {"model_name": "opus", "litellm_params": params} for tier in ComplexityTier}, + "tiers": {tier.value: {"model_name": "opus", "litellm_params": params} for tier in TIER_SEVERITY_ORDER}, "keyword_tier_rules": [{"keywords": ["reason carefully"], "tier": "REASONING"}] if route == "keyword" else None, "session_affinity": route == "session", } @@ -13212,3 +13213,197 @@ class TestClassifierVision: def test_max_images_must_be_positive(self): with pytest.raises(ValidationError): ClassifierLLMConfig(model="clf", vision={"enabled": True, "max_images": 0}) + + +NON_REASONING_TIERS: Final = { + "NON_REASONING": "gpt-4o-mini", + "SIMPLE": "gpt-4o-mini", + "MEDIUM": "gpt-4o", + "COMPLEX": "claude-sonnet-4-20250514", + "REASONING": "o1-preview", +} + + +class TestNonReasoningTier: + """The opt-in fifth built-in tier below SIMPLE. + + Two properties carry the feature. A router that did not opt in must be byte-identical to one + built before the tier existed, because the tier set feeds the classifier rubric, the wire enum, + and the savings baseline, all of which move live routing decisions and spend. A router that did + opt in must be able to actually reach the tier and escalate off it. + """ + + @staticmethod + def _router(mock_router_instance, **overrides) -> ComplexityRouter: + config: Final = { + "tiers": dict(NON_REASONING_TIERS), + "enable_non_reasoning_tier": True, + "classifier_type": "llm", + "classifier_llm_config": {"model": "haiku-classifier"}, + **overrides, + } + return ComplexityRouter( + model_name="test-non-reasoning-router", + litellm_router_instance=mock_router_instance, + complexity_router_config=config, + ) + + def test_ladder_gains_a_rung_below_simple_only_when_enabled(self): + """Tier 0 sits at the bottom. Anywhere else and escalation, the savings baseline, and + heuristic_first's 'highest tier' check would all read a different ladder.""" + enabled: Final = ComplexityRouterConfig( + tiers=dict(NON_REASONING_TIERS), + enable_non_reasoning_tier=True, + classifier_type="llm", + classifier_llm_config={"model": "clf"}, + ) + assert enabled.tier_names() == ("NON_REASONING", "SIMPLE", "MEDIUM", "COMPLEX", "REASONING") + assert ComplexityRouterConfig().tier_names() == ("SIMPLE", "MEDIUM", "COMPLEX", "REASONING") + + def test_default_router_is_unchanged_by_the_tier_existing(self): + """The regression that matters for every already-deployed router: the enum grew a member, + and nothing a four-tier router sends or resolves may change because of it.""" + default: Final = ComplexityRouterConfig() + assert default.enable_non_reasoning_tier is False + assert "NON_REASONING" not in DEFAULT_COMPLEXITY_CONFIG.tiers + assert default.classifier_wire_labels() == ("SIMPLE", "MEDIUM", "COMPLEX", "REASONING") + assert default.labeled_tiers() == TIER_SEVERITY_ORDER_LABELED + assert default.resolve_classified_tier("NON_REASONING") is None + + @pytest.mark.parametrize("preset", tuple(ClassificationRubric)) + def test_rubric_gains_the_bullet_only_when_enabled(self, preset): + """Every preset renders one bullet per active tier, so an unset toggle must leave all four + shipped rubrics byte-identical while an enabled one must actually describe the new tier.""" + enabled: Final = ComplexityRouterConfig( + tiers=dict(NON_REASONING_TIERS), + enable_non_reasoning_tier=True, + classifier_type="llm", + classifier_llm_config={"model": "clf"}, + ) + on: Final = classification_system_prompt(3, None, enabled.labeled_tiers(), preset) + off: Final = classification_system_prompt(3, None, ComplexityRouterConfig().labeled_tiers(), preset) + assert "- NON_REASONING:" in on + assert "- NON_REASONING" not in off + + def test_enabled_router_puts_the_tier_on_the_classifier_wire(self, mock_router_instance): + """The response schema's enum is what the classifier may return; without the new label the + tier would be unreachable no matter what the rubric says.""" + router: Final = self._router(mock_router_instance) + enum: Final = router._classifier_response_format["json_schema"]["schema"]["properties"]["tier"]["enum"] + assert enum == ["NON_REASONING", "SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] + + @pytest.mark.asyncio + async def test_classifier_verdict_routes_to_the_tier_model(self, mock_router_instance): + """End to end on the LLM path: the classifier names the tier and the request lands on that + tier's model with the decision recording it.""" + mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) + router: Final = self._router(mock_router_instance, tiers={**NON_REASONING_TIERS, "NON_REASONING": "cheap-relay"}) + response = await router.async_pre_routing_hook( + model="test-non-reasoning-router", + request_kwargs={}, + messages=[{"role": "user", "content": "here is the file, pass it along"}], + ) + assert response.model == "cheap-relay" + assert response.routing_decision["tier"] == "NON_REASONING" + assert response.routing_decision["cause"] == "llm_classifier" + + @pytest.mark.asyncio + async def test_a_four_tier_router_ignores_a_non_reasoning_verdict(self, llm_complexity_router, mock_router_instance): + """A classifier that names the tier at a router which never opted in must be an unparseable + reply that falls back, not a silent route to a tier the operator did not configure.""" + mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) + outcome = await llm_complexity_router.aclassify("relay this") + assert outcome.tier != ComplexityTier.NON_REASONING + assert outcome.cause != "llm_classifier" + + def test_escalation_walks_up_off_the_tier(self, mock_router_instance): + """Escalation is a built-in-ladder feature and the issue asks for it from the new tier.""" + router: Final = self._router(mock_router_instance) + assert router._escalate_tier(ComplexityTier.NON_REASONING) == ComplexityTier.SIMPLE + assert router._escalate_tier(ComplexityTier.REASONING) == ComplexityTier.REASONING + + def test_escalation_skips_the_tier_when_unconfigured(self, mock_router_instance): + """SIMPLE must still escalate to MEDIUM rather than to the cheaper new rung, or escalation + would route below the model the caller would otherwise have received.""" + router: Final = self._router( + mock_router_instance, + tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}, + ) + assert router._escalate_tier(ComplexityTier.SIMPLE) == ComplexityTier.MEDIUM + + def test_tier_zero_is_never_the_savings_baseline(self, mock_router_instance): + """Savings are measured against the hardest configured tier. If tier 0 could win that pick, + every enabled router's reported savings would invert.""" + assert self._router(mock_router_instance)._hardest_tier_models() == ("o1-preview",) + cheap_only: Final = self._router( + mock_router_instance, tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini"} + ) + assert cheap_only._hardest_tier_models() == ("gpt-4o-mini",) + + def test_the_tier_gets_its_own_display_label(self, mock_router_instance): + """tier_labels covers the built-in tiers, so the new rung must be renameable like the rest.""" + router: Final = self._router(mock_router_instance, tier_labels={"NON_REASONING": "Relay"}) + assert router.config.classifier_wire_labels()[0] == "Relay" + assert router.config.resolve_classified_tier("relay") == ComplexityTier.NON_REASONING + + @pytest.mark.parametrize( + "overrides, expected", + ( + ({"classifier_type": "heuristic", "classifier_llm_config": None}, "requires classifier_type"), + ({"classifier_type": "heuristic_v2", "classifier_llm_config": None}, "requires classifier_type"), + ({"tiers": {"SIMPLE": "a", "MEDIUM": "b"}}, "at least one model"), + ), + ids=["heuristic", "heuristic_v2", "no_model"], + ) + def test_unreachable_or_unroutable_configs_are_rejected(self, overrides, expected): + """The toggle is refused wherever it could not do anything: the heuristic scorers cannot + emit the tier, and an unconfigured tier would fall through to the default model.""" + config: Final = { + "tiers": dict(NON_REASONING_TIERS), + "enable_non_reasoning_tier": True, + "classifier_type": "llm", + "classifier_llm_config": {"model": "clf"}, + **overrides, + } + with pytest.raises(ValidationError, match=expected): + ComplexityRouterConfig.model_validate(config) + + def test_the_tier_cannot_be_configured_without_the_toggle(self): + """Silently ignoring the key would leave an operator paying for a pool nothing routes to.""" + with pytest.raises(ValidationError, match="no request can route there"): + ComplexityRouterConfig(tiers={"NON_REASONING": "cheap", "SIMPLE": "a"}) + + def test_the_toggle_is_refused_alongside_a_custom_tier_set(self): + """A custom tier set replaces the built-in ladder, so both at once has no meaning.""" + with pytest.raises(ValidationError, match="cannot be combined with tier_definitions"): + ComplexityRouterConfig( + enable_non_reasoning_tier=True, + classifier_type="llm", + classifier_llm_config={"model": "clf"}, + tier_definitions=({"name": "lo", "description": "d"}, {"name": "hi", "description": "d"}), + tiers={"lo": "a", "hi": "b"}, + fallback_tier="lo", + ) + + def test_heuristic_v2_predictions_never_reach_the_new_tier(self, mock_router_instance): + """The bundled artifact is trained on four classes, so its 1-based tier index must keep + mapping onto SIMPLE..REASONING. Reading the enabled ladder here would shift every + prediction down a rung and make REASONING unreachable.""" + router: Final = ComplexityRouter( + model_name="v2-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={ + "tiers": {k: v for k, v in NON_REASONING_TIERS.items() if k != "NON_REASONING"}, + "classifier_type": "heuristic_v2", + }, + ) + outcome = router._classify_with_heuristic_v2("implement a distributed rate limiter under concurrency") + assert outcome.tier in TIER_SEVERITY_ORDER + # One probability signal per trained class, named for the tier that class means. A ladder + # shifted by the new rung would relabel all four and lose REASONING off the end. + assert tuple(signal.split(":")[1].split("=")[0] for signal in outcome.signals[1:]) == ( + "simple", + "medium", + "complex", + "reasoning", + ) 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 2970e14b335..7632c8f9a70 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx @@ -102,7 +102,7 @@ describe("ComplexityRouterConfig", () => { renderWithProviders(); await user.click(screen.getByText("Advanced: Response Format")); - await user.click(screen.getByRole("switch")); + await user.click(screen.getByRole("switch", { name: "Return raw model name" })); expect(onChange).toHaveBeenCalledWith({ ...defaultValue, @@ -495,7 +495,7 @@ describe("ComplexityRouterConfig", () => { />, ); fireEvent.click(screen.getByText("Advanced: Keyword/Semantic Matching")); - await user.click(screen.getByRole("switch")); + await user.click(screen.getByRole("switch", { name: "Semantic keyword matching" })); expect(onSemanticMatchingEnabledChange).toHaveBeenCalledWith(true, expect.anything()); }); diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index d7df6ce33bb..01e65914c67 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -75,11 +75,16 @@ export const DEFAULT_CLASSIFICATION_MODE: ClassificationMode = "every_request"; */ export type ClassificationFrequency = ClassificationMode | "session"; +/** + * NON_REASONING is optional because it is the opt-in fifth tier: a router that never enabled it + * stores no such key, and hydrating one in would send an empty pool the backend rejects. + */ export type ComplexityTiers = { SIMPLE: string[]; MEDIUM: string[]; COMPLEX: string[]; REASONING: string[]; + NON_REASONING?: string[]; }; export type ClassificationRubric = "legacy" | "agentic" | "chat" | "business"; @@ -378,6 +383,11 @@ export type ComplexityTierLabels = Partial export interface ComplexityRouterConfigValue { tiers: ComplexityTiers; + /** + * Opt into the NON_REASONING tier below SIMPLE. Off means the router keeps the four-tier ladder + * it has always had, so an existing router's rubric and tier decisions cannot move under it. + */ + enable_non_reasoning_tier?: boolean; custom_tier_set?: CustomTierSet; tier_labels?: ComplexityTierLabels; /** An explicit pin. Unset means the default tracks the tiers - see resolveComplexityDefaultModel. */ @@ -494,6 +504,11 @@ export const TIER_DESCRIPTIONS: Record< keyof ComplexityTiers, { label: string; description: string; examples: string } > = { + NON_REASONING: { + label: "Non-reasoning", + description: "Operational relay work: passing information along with no judgment about it", + examples: '"Reformat this tool output", "Acknowledge the write succeeded"', + }, SIMPLE: { label: "Simple", description: "Basic questions, greetings, simple factual queries", @@ -516,8 +531,16 @@ export const TIER_DESCRIPTIONS: Record< }, }; +/** Every built-in tier name, including the opt-in one, for label and membership checks. */ export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array; +/** + * The four-tier ladder in ascending severity, which is what a router sends unless it opted into + * NON_REASONING. Mirrors TIER_SEVERITY_ORDER in the backend config; use tierOrderFor to get the + * ladder one router actually renders. + */ +export const BUILT_IN_TIER_ORDER: Array = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]; + export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string => tierLabels?.[tier]?.trim() || TIER_DESCRIPTIONS[tier].label; @@ -528,9 +551,46 @@ export const DEFAULT_HYBRID_BOUNDARY_MARGIN = 0.03; /** * Tiers the heuristic_first threshold may name. The top tier is excluded because it would short - * circuit every request and leave the classifier unreachable, which the backend rejects. + * circuit every request and leave the classifier unreachable, which the backend rejects. So is + * NON_REASONING, which the backend refuses alongside heuristic_first because the local scorer + * cannot produce it. */ -export const HEURISTIC_FIRST_MAX_TIER_KEYS = TIER_KEYS.slice(0, -1); +export const HEURISTIC_FIRST_MAX_TIER_KEYS = BUILT_IN_TIER_ORDER.slice(0, -1); + +/** + * The opt-in fifth tier. Only offered on the LLM classification method, matching the backend: the + * heuristic scorers cannot produce the tier, so enabling it there would buy a rubric bullet and a + * model pool that no request ever reaches. + */ +const NonReasoningTierToggle: React.FC<{ + value: ComplexityRouterConfigValue; + onChange: (value: ComplexityRouterConfigValue) => void; + available: boolean; +}> = ({ value, onChange, available }) => ( + <> +
+ { + const { NON_REASONING: _dropped, ...keptTiers } = value.tiers; + onChange({ + ...value, + enable_non_reasoning_tier: enabled ? true : undefined, + tiers: enabled ? { ...keptTiers, NON_REASONING: value.tiers.NON_REASONING ?? [] } : keptTiers, + }); + }} + aria-label="Add a non-reasoning tier" + /> + Add a non-reasoning tier +
+ + Adds NON_REASONING below Simple, for operational agent traffic that relays or reformats information rather than + reasoning about it. Escalation still moves up out of it when a request needs more. + {!available && " Requires the LLM classification method."} + + +); const PlanModeOverrideControls: React.FC<{ value: ComplexityRouterConfigValue; @@ -742,6 +802,10 @@ const ComplexityRouterConfig: React.FC = ({ ); })} + {!customTierSet && ( + + )} + = ({ const complexityRouterConfigParams: BuildComplexityRouterConfigParams = { tiers: complexityRouterConfig.tiers, + enableNonReasoningTier: complexityRouterConfig.enable_non_reasoning_tier, customTierSet: complexityRouterConfig.custom_tier_set, defaultModel: complexityRouterConfig.default_model, planModeMinTier: complexityRouterConfig.plan_mode_min_tier, diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index 7769fb832fe..2f36ec2f43d 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts @@ -119,6 +119,7 @@ const scorerKnobPayload = ({ export interface BuildComplexityRouterConfigParams { tiers: ComplexityTiers; + enableNonReasoningTier?: boolean; customTierSet?: CustomTierSet; defaultModel: string | undefined; planModeMinTier: string | undefined; @@ -180,6 +181,7 @@ export interface TierDefinitionPayload { export interface ComplexityRouterConfigPayload { tiers: ComplexityTiers | Record; + enable_non_reasoning_tier?: boolean; tier_definitions?: TierDefinitionPayload[]; fallback_tier?: string; default_model?: string; @@ -460,6 +462,7 @@ const classifierWireFields = ( export const buildComplexityRouterConfig = ({ tiers, + enableNonReasoningTier, customTierSet, defaultModel, planModeMinTier, @@ -535,6 +538,9 @@ export const buildComplexityRouterConfig = ({ const payload: ComplexityRouterConfigPayload = { tiers, + // Only written when on, and never beside a custom tier set: the backend rejects the two + // together, and an explicit false on a four-tier router would be a key it never carried. + ...(!customTierSet && enableNonReasoningTier && { enable_non_reasoning_tier: true }), ...(serializedTierModelConfigs && { tier_model_configs: serializedTierModelConfigs }), ...(defaultModel?.trim() && { default_model: defaultModel }), ...(planModeMinTier?.trim() && { plan_mode_min_tier: planModeMinTier }), diff --git a/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts b/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts index eb58c94b789..3fec63518e5 100644 --- a/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts +++ b/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts @@ -1,6 +1,6 @@ import type { ComplexityTier } from "./KeywordTierRules"; import type { ModelGroup } from "@/components/llm_calls/fetch_models"; -import { TIER_ORDER } from "./tier_rows"; +import { ALL_BUILT_IN_TIERS, TIER_ORDER } from "./tier_rows"; export type TierModelParams = Record; @@ -145,13 +145,14 @@ export const pruneTierModelParams = ( }; export const DEFAULT_TIER_LABELS: Record = { + NON_REASONING: "Non-reasoning", SIMPLE: "Simple", MEDIUM: "Medium", COMPLEX: "Complex", REASONING: "Reasoning", }; -const isBuiltInTier = (tier: string): tier is ComplexityTier => (TIER_ORDER as string[]).includes(tier); +const isBuiltInTier = (tier: string): tier is ComplexityTier => (ALL_BUILT_IN_TIERS as string[]).includes(tier); const builtInTierLabel = ( tierLabels: Partial> | undefined, @@ -164,7 +165,7 @@ export const tierRowLabel = ( row: { id: string; name: string }, tierLabels?: Partial>, ): string => { - const builtIn = TIER_ORDER.find((tier) => tier === row.id); + const builtIn = ALL_BUILT_IN_TIERS.find((tier) => tier === row.id); const named = row.name.trim(); if (!builtIn || named !== builtIn) return named || "New"; return builtInTierLabel(tierLabels, builtIn); diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts index df50f116f60..07b9a4702aa 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts @@ -168,3 +168,34 @@ describe("tierParamsByRowId", () => { expect(tierParamsByRowId(undefined, rows)).toBeUndefined(); }); }); + +describe("the opt-in non-reasoning tier", () => { + const withTierZero = { SIMPLE: ["a"], MEDIUM: ["b"], COMPLEX: ["c"], REASONING: ["d"], NON_REASONING: ["cheap"] }; + + it("renders no fifth row while the toggle is off", () => { + // The regression for every existing router: the tier exists in the type, and the form must + // still show the four rows it always showed. + expect(activeTierRows({ tiers: withTierZero }).map((row) => row.id)).toEqual([ + "SIMPLE", + "MEDIUM", + "COMPLEX", + "REASONING", + ]); + }); + + it("renders it first, as tier 0, when enabled", () => { + const rows = activeTierRows({ tiers: withTierZero, enable_non_reasoning_tier: true }); + expect(rows.map((row) => row.id)).toEqual(["NON_REASONING", "SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]); + expect(rows[0].models).toEqual(["cheap"]); + }); + + it("renders an enabled tier with no models as an empty row rather than crashing", () => { + const rows = activeTierRows({ tiers, enable_non_reasoning_tier: true }); + expect(rows[0]).toEqual({ id: "NON_REASONING", name: "NON_REASONING", definition: "", models: [], params: {} }); + }); + + it("counts as a built-in name either way, so a custom set cannot claim the name", () => { + expect(isBuiltInTierName("NON_REASONING")).toBe(true); + expect(isBuiltInTierName("non_reasoning")).toBe(true); + }); +}); diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts index 5e2a32addee..289a5645b51 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts @@ -4,6 +4,16 @@ import type { TierModelParams, TierModelParamsByTier } from "./complexity_router export const TIER_ORDER: ComplexityTier[] = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]; +/** Every built-in tier name, so a stored NON_REASONING row is recognized as built-in either way. */ +export const ALL_BUILT_IN_TIERS: ComplexityTier[] = ["NON_REASONING", ...TIER_ORDER]; + +/** + * The ladder one router renders, ascending. NON_REASONING is tier 0 and appears only when enabled, + * which is what keeps an existing four-tier router's form, payload, and rubric unchanged. + */ +export const tierOrderFor = (enableNonReasoningTier: boolean | undefined): ComplexityTier[] => + enableNonReasoningTier ? ALL_BUILT_IN_TIERS : TIER_ORDER; + export interface TierRow { id: string; name: string; @@ -27,6 +37,7 @@ export const MAX_TIER_DEFINITION_CHARS = 500; export interface ActiveTierSet { tiers: ComplexityTiers; + enable_non_reasoning_tier?: boolean; custom_tier_set?: CustomTierSet; tier_model_params?: TierModelParamsByTier; } @@ -39,7 +50,8 @@ export const activeTierName = (row: TierRow): string => row.name.trim(); export const sameTierIdentity = (left: string, right: string): boolean => left.trim().toLowerCase() === right.trim().toLowerCase(); -export const isBuiltInTierName = (name: string): boolean => TIER_ORDER.some((tier) => sameTierIdentity(tier, name)); +export const isBuiltInTierName = (name: string): boolean => + ALL_BUILT_IN_TIERS.some((tier) => sameTierIdentity(tier, name)); const builtInRow = (tier: keyof ComplexityTiers, tiers: ComplexityTiers): TierRow => ({ id: tier, @@ -51,7 +63,9 @@ const builtInRow = (tier: keyof ComplexityTiers, tiers: ComplexityTiers): TierRo // The only reader of the tier set. Built-in rows carry the canonical tier key as their id, so every // pointer into the set is a row id in both modes and nothing downstream branches on the mode. export const activeTierRows = (value: ActiveTierSet): ActiveTierRow[] => { - const rows = value.custom_tier_set?.tiers ?? TIER_ORDER.map((tier) => builtInRow(tier, value.tiers)); + const rows = + value.custom_tier_set?.tiers ?? + tierOrderFor(value.enable_non_reasoning_tier).map((tier) => builtInRow(tier, value.tiers)); return rows.map((row) => ({ ...row, params: value.tier_model_params?.[row.id] ?? {} })); }; diff --git a/ui/litellm-dashboard/src/components/add_model/tier_set_actions.ts b/ui/litellm-dashboard/src/components/add_model/tier_set_actions.ts index c8254b6bff9..f737e0fdff6 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_set_actions.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_set_actions.ts @@ -4,11 +4,12 @@ import { pruneTierModelParams } from "./complexity_router_tiers"; import { type ActiveTierRow, type TierRow, - TIER_ORDER, + ALL_BUILT_IN_TIERS, activeTierName, activeTierRows, rowParamsByTier, sameTierIdentity, + tierOrderFor, tierRowById, tierRowByName, } from "./tier_rows"; @@ -74,13 +75,13 @@ const rulesFollowingRows = ( // Models and params both come from these rows, so the two cannot be keyed differently. const exitToBuiltInTiers = (value: ComplexityRouterConfigValue, rows: readonly ActiveTierRow[]) => { const { custom_tier_set: _dropped, ...rest } = value; - const builtInRows: ActiveTierRow[] = TIER_ORDER.map( + const builtInRows: ActiveTierRow[] = tierOrderFor(value.enable_non_reasoning_tier).map( (tier) => tierRowById(rows, tier) ?? { id: tier, name: tier, definition: "", - models: value.tiers[tier], + models: value.tiers[tier] ?? [], params: value.tier_model_params?.[tier] ?? {}, }, ); @@ -121,7 +122,7 @@ const nextTierSetValue = ( case "remove": { const removed = tierRowById(rows, action.id); const snapshot = - removed && (TIER_ORDER as string[]).includes(action.id) + removed && (ALL_BUILT_IN_TIERS as string[]).includes(action.id) ? { ...value, tiers: { ...value.tiers, [action.id]: removed.models } } : value; return commitTierRows( diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts index 0144dff3498..5b09dabd214 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts +++ b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts @@ -598,6 +598,10 @@ describe("managed keys survive an untouched open-and-save", () => { "stall_escalation_repeat_threshold", ]); + // The opt-in fifth tier requires the LLM classifier, which this heuristic_first fixture is not, + // so it gets its own round trip below. + const KEYS_ANOTHER_TIER_LADDER_OWNS = new Set(["enable_non_reasoning_tier"]); + it("carries every managed key a built-in router can hold through hydrate then save", () => { const hydrated = hydrateComplexityRouterConfig(STORED_ALL_MANAGED, undefined); const saved = buildUpdatedComplexityRouterConfig(STORED_ALL_MANAGED, hydrated); @@ -605,10 +609,55 @@ describe("managed keys survive an untouched open-and-save", () => { const dropped = [...MANAGED_COMPLEXITY_ROUTER_KEYS] .filter((key) => !KEYS_ANOTHER_CLASSIFIER_TYPE_OWNS.has(key)) .filter((key) => !KEYS_ANOTHER_CLASSIFICATION_FREQUENCY_OWNS.has(key)) + .filter((key) => !KEYS_ANOTHER_TIER_LADDER_OWNS.has(key)) .filter((key) => saved[key] === undefined); expect(dropped).toEqual([]); }); + it("carries an enabled non-reasoning tier and its models through their own round trip", () => { + // `tiers` is rewritten wholesale on save, so this is the regression that matters: opening an + // enabled router and saving an unrelated edit must not delete the tier or its pool. + const stored: Record = { + ...STORED_ALL_MANAGED, + classifier_type: "llm", + classifier_llm_config: { model: "haiku-classifier" }, + heuristic_first_max_tier: undefined, + enable_non_reasoning_tier: true, + tiers: { ...(STORED_ALL_MANAGED.tiers as object), NON_REASONING: ["gpt-4o-mini"] }, + }; + const hydrated = hydrateComplexityRouterConfig(stored, undefined); + const saved = buildUpdatedComplexityRouterConfig(stored, hydrated); + + expect(saved.enable_non_reasoning_tier).toBe(true); + expect((saved.tiers as Record).NON_REASONING).toEqual(["gpt-4o-mini"]); + }); + + it("keeps a stored non-reasoning tier when the stored config never wrote the flag", () => { + // A hand-written config that names the tier: the flag is inferred from the stored pool, so an + // edit made for an unrelated reason cannot silently turn the tier off. + const stored: Record = { + ...STORED_ALL_MANAGED, + classifier_type: "llm", + classifier_llm_config: { model: "haiku-classifier" }, + heuristic_first_max_tier: undefined, + tiers: { ...(STORED_ALL_MANAGED.tiers as object), NON_REASONING: ["gpt-4o-mini"] }, + }; + const saved = buildUpdatedComplexityRouterConfig(stored, hydrateComplexityRouterConfig(stored, undefined)); + + expect(saved.enable_non_reasoning_tier).toBe(true); + expect((saved.tiers as Record).NON_REASONING).toEqual(["gpt-4o-mini"]); + }); + + it("leaves the tier and its flag out of a saved config that never had it on", () => { + const saved = buildUpdatedComplexityRouterConfig( + STORED_ALL_MANAGED, + hydrateComplexityRouterConfig(STORED_ALL_MANAGED, undefined), + ); + + expect(saved).not.toHaveProperty("enable_non_reasoning_tier"); + expect(saved.tiers).not.toHaveProperty("NON_REASONING"); + }); + it("carries the stall-escalation keys through their own round trip", () => { const stored: Record = { ...STORED_ALL_MANAGED, diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx index 3c0013e267e..283fe978790 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx @@ -91,6 +91,7 @@ interface EditAutoRouterModalProps { * hydrators validate themselves stay `unknown`; the ones assigned straight through carry their type. */ export interface StoredComplexityRouterConfig { tiers?: Partial>; + enable_non_reasoning_tier?: boolean; tier_model_configs?: unknown; default_model?: string | null; plan_mode_min_tier?: unknown; @@ -135,18 +136,27 @@ export const hydrateComplexityRouterConfig = ( parsedConfig: StoredComplexityRouterConfig, complexityRouterDefaultModel: string | null | undefined, ): ComplexityRouterConfigValue => { + // `tiers` is rewritten wholesale on save, so a stored tier this misses is deleted from the + // router by any edit at all, including one made for an unrelated reason. NON_REASONING is + // therefore read back from the stored config rather than assumed absent, and the toggle follows + // what is actually stored so the round-trip cannot silently turn the tier off. + const storedNonReasoning: string[] = normalizeTierModels(parsedConfig.tiers?.NON_REASONING); + const enable_non_reasoning_tier: boolean = + parsedConfig.enable_non_reasoning_tier === true || storedNonReasoning.length > 0; const hydratedTiers: ComplexityTiers = { SIMPLE: normalizeTierModels(parsedConfig.tiers?.SIMPLE), MEDIUM: normalizeTierModels(parsedConfig.tiers?.MEDIUM), COMPLEX: normalizeTierModels(parsedConfig.tiers?.COMPLEX), REASONING: normalizeTierModels(parsedConfig.tiers?.REASONING), + ...(enable_non_reasoning_tier && { NON_REASONING: storedNonReasoning }), }; const custom_tier_set = hydrateCustomTierSet(parsedConfig); - const activeTiers = { tiers: hydratedTiers, custom_tier_set }; + const activeTiers = { tiers: hydratedTiers, enable_non_reasoning_tier, custom_tier_set }; return { tiers: hydratedTiers, + enable_non_reasoning_tier, custom_tier_set, tier_model_params: tierParamsByRowId( hydrateTierModelParams(parsedConfig.tiers, parsedConfig.tier_model_configs), @@ -234,6 +244,7 @@ export const hydrateComplexityRouterConfig = ( export const MANAGED_COMPLEXITY_ROUTER_KEYS = new Set([ "tiers", + "enable_non_reasoning_tier", "tier_definitions", "fallback_tier", "tier_model_configs", @@ -339,6 +350,7 @@ export const buildUpdatedComplexityRouterConfig = ( const builderParams: BuildComplexityRouterConfigParams = { tiers: value.tiers, + enableNonReasoningTier: value.enable_non_reasoning_tier, customTierSet: value.custom_tier_set, defaultModel: value.default_model, planModeMinTier: value.plan_mode_min_tier, diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 232b366d8ce..6a60b0db58a 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -23573,16 +23573,22 @@ export interface components { }; /** * AutoRouterPresetTiers - * @description Exactly the four built-in tiers the dashboard's preset prefill can apply. + * @description The built-in tiers the dashboard's preset prefill can apply. * * extra="forbid" on purpose: a tier name this dashboard cannot apply would grey out or crash the - * picker, so such a catalog is rejected wholesale and the bundled one serves instead. + * picker, so such a catalog is rejected wholesale and the bundled one serves instead. NON_REASONING + * is optional rather than required so this proxy accepts both a catalog that ships the opt-in fifth + * tier and the four-tier catalogs that predate it. It stays None when unset rather than defaulting + * to an empty pool, so a four-tier preset serves the tier set it was published with instead of + * growing a key the dashboard would render as an empty fifth tier row. */ AutoRouterPresetTiers: { /** Complex */ COMPLEX: string[]; /** Medium */ MEDIUM: string[]; + /** Non Reasoning */ + NON_REASONING?: string[] | null; /** Reasoning */ REASONING: string[]; /** Simple */ @@ -25579,7 +25585,7 @@ export interface components { * @description Complexity tiers for routing decisions. * @enum {string} */ - ComplexityTier: "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING"; + ComplexityTier: "NON_REASONING" | "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING"; /** ComplexityTierModel */ ComplexityTierModel: { /** Litellm Params */ @@ -34947,6 +34953,12 @@ export interface components { * @default true */ enable_context_window_escalation: boolean; + /** + * Enable Non Reasoning Tier + * @description Add NON_REASONING as a fifth built-in tier below SIMPLE, for operational agent traffic that relays or reformats information rather than reasoning about it. Off by default: turning it on adds a rung to this router's ladder, a bullet to the LLM classifier's rubric, and a value the classifier may return, all of which move tier decisions and spend on an already-deployed router. Requires an LLM classifier or a custom classifier plugin, since the heuristic scorers cannot produce the tier, and a model in `tiers` under the NON_REASONING key. Escalation still walks up from it, and it is never the savings baseline or a `heuristic_v2` prediction. + * @default false + */ + enable_non_reasoning_tier: boolean; /** * Escalation Keywords * @description Case-sensitive phrases a user can include to force a bump to the next-higher complexity tier when they aren't satisfied with results (they can force a stronger model, but not choose which one). Defaults to ['LITELLM ESCALATE'] when unset; set to an empty list to disable. @@ -37212,7 +37224,7 @@ export interface components { TierDefinition: { /** * Description - * @description What belongs in this tier; rendered as this tier's bullet in the classifier rubric. Required unless the name is a built-in tier (SIMPLE/MEDIUM/COMPLEX/REASONING), which inherits the built-in criteria when omitted + * @description What belongs in this tier; rendered as this tier's bullet in the classifier rubric. Required unless the name is a built-in tier (NON_REASONING, SIMPLE, MEDIUM, COMPLEX, REASONING), which inherits the built-in criteria when omitted */ description?: string | null; /** From da2e1ff6190d68873a5c3af173ef71fe7dce51c9 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:12:34 -0700 Subject: [PATCH 035/105] fix(fireworks): fold system and developer items into instructions on the responses path Fireworks renders a Responses request through a chat template that only accepts a system message at the very beginning, so a request carrying `instructions`, a developer item, and a replayed reasoning item (the shape Codex CLI sends from its second prompt on) came back 400 with "System message must be at the beginning". The leading system or developer items, and any developer item later in the conversation, now fold their text into top-level `instructions`, joined with blank lines, and leave `input`. A developer item that closes the conversation right after an assistant turn stays where it is as a system item, as does any system or developer item with an image or file part, so those parts still reach Fireworks. Mid-conversation system items stay untouched. Non-string `instructions` pass through unchanged. Folding into `instructions` rather than a leading system item keeps `previous_response_id` chaining working, since Fireworks prepends the stored history to `input` and a leading system item would land after it. This supersedes the leading system item approach from deaadc21d3 and 4807630c2a on this branch. The leading and closing block rules match the chat path change in #39852. --- .../fireworks_ai/responses/transformation.py | 136 +++++++++++------ ...t_fireworks_ai_responses_transformation.py | 140 +++++++++++++----- 2 files changed, 196 insertions(+), 80 deletions(-) diff --git a/litellm/llms/fireworks_ai/responses/transformation.py b/litellm/llms/fireworks_ai/responses/transformation.py index 660a07181ff..f7dd774ea18 100644 --- a/litellm/llms/fireworks_ai/responses/transformation.py +++ b/litellm/llms/fireworks_ai/responses/transformation.py @@ -1,16 +1,10 @@ -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from types import MappingProxyType from typing import TYPE_CHECKING, Final from urllib.parse import unquote import httpx -from openai.types.responses import ( - EasyInputMessageParam, - ResponseInputContentParam, - ResponseInputItemParam, - ResponseInputTextParam, -) -from pydantic import TypeAdapter +from openai.types.responses import EasyInputMessageParam, ResponseInputContentParam, ResponseInputItemParam from litellm.llms.fireworks_ai.common_utils import ( resolve_fireworks_api_key, @@ -37,47 +31,90 @@ def _session_params(litellm_params: GenericLiteLLMParams) -> Mapping[str, object ) -_instructions_adapter: Final = TypeAdapter[str | None](str | None) +_INSTRUCTION_ROLES: Final = frozenset({"system", "developer"}) -def _instruction_parts(item: ResponseInputItemParam) -> tuple[ResponseInputContentParam, ...] | None: - if "role" not in item or (item["role"] != "system" and item["role"] != "developer"): - return None - content: Final = item["content"] - if isinstance(content, str): - return (ResponseInputTextParam(type="input_text", text=content),) - return tuple(content) +def _role(item: ResponseInputItemParam) -> str | None: + match item: + case {"role": str(role)}: + return role + case _: + return None -def _leading_system_content( - instructions: str | None, parts: tuple[ResponseInputContentParam, ...] -) -> str | list[ResponseInputContentParam]: - text: Final = "\n\n".join( - chunk for chunk in (instructions, *(part["text"] for part in parts if part["type"] == "input_text")) if chunk - ) - non_text: Final = tuple(part for part in parts if part["type"] != "input_text") - if not non_text: - return text - return [ResponseInputTextParam(type="input_text", text=text), *non_text] if text else list(non_text) +def _developer_item_as_system(item: ResponseInputItemParam) -> ResponseInputItemParam: + if "role" not in item or item["role"] != "developer": + return item + return EasyInputMessageParam(role="system", content=item["content"], type="message") -def _with_single_leading_system_item( - input: str | ResponseInputParam, instructions: str | None -) -> str | ResponseInputParam: - items: Final = () if isinstance(input, str) else tuple(input) - instruction_parts: Final = tuple( - part for item_parts in map(_instruction_parts, items) if item_parts is not None for part in item_parts - ) - content: Final = _leading_system_content(instructions, instruction_parts) - if not content: +def _developer_items_as_system(input: str | ResponseInputParam) -> str | ResponseInputParam: + if isinstance(input, str): return input - leading: Final = EasyInputMessageParam(role="system", content=content, type="message") - rest: Final = ( - (EasyInputMessageParam(role="user", content=input),) - if isinstance(input, str) - else tuple(item for item in items if _instruction_parts(item) is None) + return [_developer_item_as_system(item) for item in input] + + +def _text_part(part: ResponseInputContentParam) -> str | None: + match part: + case {"type": "input_text", "text": str(text)}: + return text + case _: + return None + + +def _text_only_content(item: ResponseInputItemParam) -> str | None: + match item: + case {"role": "system" | "developer", "content": str(text)}: + return text + case {"role": "system" | "developer", "content": [*parts]}: + texts: Final = tuple(map(_text_part, parts)) + return None if any(text is None for text in texts) else "\n\n".join(text for text in texts if text) + case _: + return None + + +def _leading_instruction_block_length(roles: Sequence[str | None]) -> int: + return next((index for index, role in enumerate(roles) if role not in _INSTRUCTION_ROLES), len(roles)) + + +def _closing_instruction_block_start(roles: Sequence[str | None], leading_length: int) -> int: + last_conversation_index: Final = next( + (index for index in range(len(roles) - 1, leading_length - 1, -1) if roles[index] not in _INSTRUCTION_ROLES), + None, + ) + if last_conversation_index is None or roles[last_conversation_index] != "assistant": + return len(roles) + return last_conversation_index + 1 + + +def _hoisted_indices(roles: Sequence[str | None]) -> tuple[int, ...]: + leading_length: Final = _leading_instruction_block_length(roles) + closing_start: Final = _closing_instruction_block_start(roles, leading_length) + return tuple( + index for index, role in enumerate(roles[:closing_start]) if index < leading_length or role == "developer" + ) + + +def _with_instruction_items_folded( + input: str | ResponseInputParam, instructions: str | None +) -> tuple[str | None, str | ResponseInputParam]: + if isinstance(input, str): + return instructions, input + items: Final = tuple(input) + folded: Final = MappingProxyType( + { + index: text + for index in _hoisted_indices(tuple(map(_role, items))) + if (text := _text_only_content(items[index])) is not None + } + ) + joined: Final = "\n\n".join(chunk for chunk in (instructions, *folded.values()) if chunk) + return ( + instructions if not folded else joined or None, + [ # mutable-ok: the base class takes the input items as a list + _developer_item_as_system(item) for index, item in enumerate(items) if index not in folded + ], ) - return [leading, *rest] class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): @@ -113,15 +150,24 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): litellm_params: GenericLiteLLMParams, headers: dict, # mutable-ok: overrides the base class signature ) -> dict: # mutable-ok: overrides the base class signature - instructions: Final = _instructions_adapter.validate_python( - response_api_optional_request_params.get("instructions") + instructions_param: Final[object] = response_api_optional_request_params.get("instructions") + validated_input: Final = self._validate_input_param(input) + instructions, folded_input = ( + _with_instruction_items_folded(validated_input, instructions_param) + if isinstance(instructions_param, str | None) + else (instructions_param, _developer_items_as_system(validated_input)) ) + instruction_entries: Final = () if instructions is None else (("instructions", instructions),) folded_params: Final = { # mutable-ok: the base class takes the optional params as a dict - key: value for key, value in response_api_optional_request_params.items() if key != "instructions" + key: value + for key, value in ( + *((key, value) for key, value in response_api_optional_request_params.items() if key != "instructions"), + *instruction_entries, + ) } return super().transform_responses_api_request( model=resolve_fireworks_resource_name(model), - input=_with_single_leading_system_item(self._validate_input_param(input), instructions), + input=folded_input, response_api_optional_request_params=folded_params, litellm_params=litellm_params, headers=headers, diff --git a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py index 9d947b25d69..d0697ca9b0e 100644 --- a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py @@ -162,7 +162,7 @@ def test_responses_call_forwards_previous_response_id_and_store() -> None: assert body["input"][0]["call_id"] == "call_abc123" -def test_responses_call_hoists_developer_items_into_one_leading_system_message() -> None: +def test_responses_call_folds_developer_items_into_instructions() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): litellm.responses( @@ -175,14 +175,14 @@ def test_responses_call_hoists_developer_items_into_one_leading_system_message() api_key="fw-test-key", ) _, _, body = _sent_request(client) + assert body["instructions"] == "Answer with exactly one word." assert tuple(body["input"]) == ( - {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, {"role": "user", "content": "Hi there"}, {"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]}, ) -def test_responses_call_folds_instructions_and_developer_item_into_one_leading_system_message() -> None: +def test_responses_call_folds_instructions_and_developer_item_into_instructions_with_reasoning_replayed() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): litellm.responses( @@ -208,16 +208,10 @@ def test_responses_call_folds_instructions_and_developer_item_into_one_leading_s api_key="fw-test-key", ) _, _, body = _sent_request(client) - assert "instructions" not in body + assert body["instructions"] == ( + "You are a coding agent running in the Codex CLI.\n\nread-only" + ) assert tuple(body["input"]) == ( - { - "role": "system", - "content": ( - "You are a coding agent running in the Codex CLI.\n\n" - "read-only" - ), - "type": "message", - }, {"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]}, {"id": "rs_1", "type": "reasoning", "summary": [{"type": "summary_text", "text": "A trivial question."}]}, { @@ -231,41 +225,103 @@ def test_responses_call_folds_instructions_and_developer_item_into_one_leading_s ) -def test_responses_call_keeps_non_text_developer_parts_on_the_leading_system_message() -> None: +def test_responses_call_folds_instructions_and_developer_item_with_previous_response_id() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): litellm.responses( model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b", - instructions="Answer with one word.", + instructions="You are a terse assistant.", input=[ # mutable-ok: the Responses API takes input as a JSON list - { - "role": "developer", - "content": [ - {"type": "input_text", "text": "Match the style of this reference image."}, - {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"}, - ], - }, + {"role": "developer", "content": "Answer with exactly one word."}, + {"role": "user", "content": "And of Spain?"}, + ], + previous_response_id="resp_0e946f2d46bf4b49bf8b29ff78083583", + store=True, + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert body["instructions"] == "You are a terse assistant.\n\nAnswer with exactly one word." + assert body["previous_response_id"] == "resp_0e946f2d46bf4b49bf8b29ff78083583" + assert tuple(body["input"]) == ({"role": "user", "content": "And of Spain?"},) + + +def test_responses_call_keeps_a_closing_developer_item_after_an_assistant_turn_in_place() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) + assistant_turn: Final = { + "id": "msg_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Paris.", "annotations": []}], + } + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b", + instructions="Be terse.", + input=[ # mutable-ok: the Responses API takes input as a JSON list + {"role": "developer", "content": "Answer with exactly one word."}, + {"role": "user", "content": "What is the capital of France?"}, + assistant_turn, + {"role": "developer", "content": "Now restate it in French."}, + ], + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert body["instructions"] == "Be terse.\n\nAnswer with exactly one word." + assert tuple(body["input"]) == ( + {"role": "user", "content": "What is the capital of France?"}, + assistant_turn, + {"role": "system", "content": "Now restate it in French.", "type": "message"}, + ) + + +def test_responses_call_keeps_a_mid_conversation_system_item_in_place() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/kimi-k3", + input=[ # mutable-ok: the Responses API takes input as a JSON list + {"role": "user", "content": "Hi there"}, + {"role": "system", "content": "Switch to French."}, {"role": "user", "content": "What is the capital of France?"}, ], - store=False, api_key="fw-test-key", ) _, _, body = _sent_request(client) assert "instructions" not in body assert tuple(body["input"]) == ( - { - "role": "system", - "content": [ - {"type": "input_text", "text": "Answer with one word.\n\nMatch the style of this reference image."}, - {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"}, - ], - "type": "message", - }, + {"role": "user", "content": "Hi there"}, + {"role": "system", "content": "Switch to French."}, {"role": "user", "content": "What is the capital of France?"}, ) -def test_responses_call_turns_string_input_with_instructions_into_system_then_user_messages() -> None: +def test_responses_call_keeps_a_developer_item_with_non_text_parts_in_place_as_a_system_item() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b")) + developer_item: Final = { + "role": "developer", + "content": [ + {"type": "input_text", "text": "Match the style of this reference image."}, + {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"}, + ], + } + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b", + instructions="Answer with one word.", + input=[developer_item, {"role": "user", "content": "What is the capital of France?"}], # mutable-ok: JSON list + store=False, + api_key="fw-test-key", + ) + _, _, body = _sent_request(client) + assert body["instructions"] == "Answer with one word." + assert tuple(body["input"]) == ( + {"role": "system", "content": developer_item["content"], "type": "message"}, + {"role": "user", "content": "What is the capital of France?"}, + ) + + +def test_responses_call_forwards_string_input_and_instructions_unchanged() -> None: client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) with patch(HTTPX_CLIENT_FACTORY, return_value=client): litellm.responses( @@ -275,10 +331,24 @@ def test_responses_call_turns_string_input_with_instructions_into_system_then_us api_key="fw-test-key", ) _, _, body = _sent_request(client) - assert "instructions" not in body - assert tuple(body["input"]) == ( + assert body["instructions"] == "Answer with exactly one word." + assert body["input"] == "What is the capital of France?" + + +def test_transform_request_forwards_non_string_instructions_and_input_untouched() -> None: + developer_item: Final = {"role": "developer", "content": "Answer with exactly one word."} + user_item: Final = {"role": "user", "content": "What is the capital of France?"} + request: Final = FireworksAIResponsesAPIConfig().transform_responses_api_request( + model="accounts/fireworks/models/kimi-k3", + input=cast(ResponseInputParam, [developer_item, user_item]), # mutable-ok: JSON list + response_api_optional_request_params={"instructions": ["not", "a", "string"]}, # mutable-ok: base takes a dict + litellm_params=GenericLiteLLMParams(), + headers={}, # mutable-ok: base takes a dict + ) + assert request["instructions"] == ["not", "a", "string"] + assert tuple(request["input"]) == ( {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, - {"role": "user", "content": "What is the capital of France?"}, + user_item, ) @@ -305,8 +375,8 @@ def test_responses_call_maps_pydantic_developer_items_and_replays_pydantic_outpu model="fireworks_ai/accounts/fireworks/models/kimi-k3", input=pydantic_input, api_key="fw-test-key" ) _, _, body = _sent_request(client) + assert body["instructions"] == "Answer with exactly one word." assert tuple(body["input"]) == ( - {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, {"id": "rs_1", "summary": [], "type": "reasoning"}, { "id": "fc_1", From 946d6f665ed84c59c4545d4519d9e029258046a2 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:18:26 -0700 Subject: [PATCH 036/105] test(cost_map): assert runtime reloads refresh loaded_at --- .../test_get_model_cost_map.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 18794ea7eec..f72d175d579 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -335,6 +335,7 @@ def test_get_model_cost_map_stamps_loaded_at(monkeypatch): import functools import random +from datetime import datetime, timezone import httpx @@ -554,6 +555,27 @@ async def test_refetch_local_override_reports_the_bundled_blob_id_without_an_eta assert get_model_cost_map_provenance() == {"source_revision": _bundled_blob_id(), "etag": None} +@pytest.mark.asyncio +async def test_refetch_stamps_loaded_at_on_remote_and_local_reloads(monkeypatch): + from litellm.litellm_core_utils import get_model_cost_map as module + + client, _ = _mock_client([httpx.Response(200, content=_real_map_bytes())]) + monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) + before_remote = datetime.now(timezone.utc) + await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) + remote_loaded_at = module.get_model_cost_map_loaded_at() + assert remote_loaded_at is not None + assert before_remote <= remote_loaded_at <= datetime.now(timezone.utc) + + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) + before_local = datetime.now(timezone.utc) + await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0)) + local_loaded_at = module.get_model_cost_map_loaded_at() + assert local_loaded_at is not None + assert before_local <= local_loaded_at <= datetime.now(timezone.utc) + + # --------------------------------------------------------------------------- # get_model_cost_map: the boot-time load retries transient failures like a reload does # --------------------------------------------------------------------------- From 91f1d98fb08428339802e0f16206f1836709d9c7 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 12:30:42 -0700 Subject: [PATCH 037/105] fix(auto_router): clear the non-reasoning tier when the classifier changes Three review findings, all in the dashboard. Switching off the LLM classifier left the toggle checked but disabled, so the flag could not be cleared and every save was refused by the backend. The classifier-change handler now drops the flag and the tier's pool the same way it already drops the other classifier-specific keys. builtInTierInfo resolved rows against the four-tier order, so the new row rendered with no description, no examples and no rename field. It now resolves against every built-in tier, and the duplicate BUILT_IN_TIER_ORDER constant is gone in favour of the one in tier_rows. The preset schema widening is reverted. It was speculative, no published catalog carries the tier, and prefill would have discarded it while the route-wide null exclusion changed the endpoint's passthrough contract for every other field. Also splits NonReasoningTierToggle and TierConfigIntro into their own files to get ComplexityRouterConfig.tsx back under the max-lines limit, and applies ruff format to the two backend files CI flagged. --- .../public_endpoints/public_endpoints.py | 3 - .../complexity_router/complexity_router.py | 4 +- .../complexity_router/config.py | 1 + .../public_endpoints/public_endpoints.py | 9 +-- .../add_model/ClassificationMethodConfig.tsx | 20 +++++ .../add_model/ComplexityRouterConfig.tsx | 73 ++----------------- .../add_model/NonReasoningTierToggle.tsx | 49 +++++++++++++ .../components/add_model/TierConfigIntro.tsx | 30 ++++++++ .../add_model/nonReasoningTierFields.test.ts | 52 +++++++++++++ .../components/add_model/tier_rows.test.ts | 9 ++- ui/litellm-dashboard/src/lib/http/schema.d.ts | 10 +-- ui/litellm-dashboard/tsconfig.tsbuildinfo | 2 +- 12 files changed, 171 insertions(+), 91 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx create mode 100644 ui/litellm-dashboard/src/components/add_model/TierConfigIntro.tsx create mode 100644 ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index a7a24e10bdd..94a59828451 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -534,9 +534,6 @@ async def get_autorouter_presets( "/public/autorouter_presets", tags=["public", "auto router"], # mutable-ok: FastAPI route tags take a list response_model=dict[str, AutoRouterPresetRecord], - # An optional tier a preset does not set must not reach the dashboard as a null pool, which the - # template picker would render as an empty tier row the operator never asked for. - response_model_exclude_none=True, ) async def get_public_autorouter_presets() -> Mapping[str, AutoRouterPresetRecord]: """ diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index fd0d53e0902..c40405ecf0f 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -2334,9 +2334,7 @@ class ComplexityRouter(CustomLogger): distance = 0 else: model_tiers = self._model_tiers.get(model, (classified_tier,)) - distance = min( - abs(severity_order.index(model_tier) - classified_idx) for model_tier in model_tiers - ) + distance = min(abs(severity_order.index(model_tier) - classified_idx) for model_tier in model_tiers) score = quality_weight * quality_sample + cost_weight * cost_score - penalty_weight * distance candidate_scores.append( { diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index bdfa0c9c389..d917c1b8041 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -78,6 +78,7 @@ def tier_severity_order(non_reasoning_enabled: bool) -> tuple[ComplexityTier, .. """The built-in ladder for one router, tier 0 included only when it opted in.""" return NON_REASONING_TIER_SEVERITY_ORDER if non_reasoning_enabled else TIER_SEVERITY_ORDER + DEFAULT_TIER_DISTANCE_PENALTY: Final[float] = 0.5 DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE: Final[int] = 3 diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index fb5a37c45fc..fa73926305b 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -74,14 +74,10 @@ class SupportedEndpointsResponse(BaseModel): class AutoRouterPresetTiers(BaseModel): - """The built-in tiers the dashboard's preset prefill can apply. + """Exactly the four built-in tiers the dashboard's preset prefill can apply. extra="forbid" on purpose: a tier name this dashboard cannot apply would grey out or crash the - picker, so such a catalog is rejected wholesale and the bundled one serves instead. NON_REASONING - is optional rather than required so this proxy accepts both a catalog that ships the opt-in fifth - tier and the four-tier catalogs that predate it. It stays None when unset rather than defaulting - to an empty pool, so a four-tier preset serves the tier set it was published with instead of - growing a key the dashboard would render as an empty fifth tier row. + picker, so such a catalog is rejected wholesale and the bundled one serves instead. """ model_config = ConfigDict(extra="forbid") @@ -90,7 +86,6 @@ class AutoRouterPresetTiers(BaseModel): MEDIUM: Sequence[str] COMPLEX: Sequence[str] REASONING: Sequence[str] - NON_REASONING: Sequence[str] | None = None class AutoRouterPresetConfig(BaseModel): diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 15cfff01766..468dcf5b411 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -236,6 +236,22 @@ const ClassifierTypeRadios: React.FC<{ ); }; +/** + * The NON_REASONING keys a classifier switch should carry forward, or clear. Leaving the flag set + * under a classifier that cannot emit the tier produces a config the backend refuses on save, and + * the switch is disabled there, so the operator would have no way to undo it. + */ +export const nonReasoningTierFields = ( + classifierType: ClassifierType, + value: ComplexityRouterConfigValue, +): Pick => { + if (classifierType === "llm") { + return { enable_non_reasoning_tier: value.enable_non_reasoning_tier, tiers: value.tiers }; + } + const { NON_REASONING: _cleared, ...keptTiers } = value.tiers; + return { enable_non_reasoning_tier: undefined, tiers: keptTiers }; +}; + const ClassificationMethodConfig: React.FC = ({ value, onChange, @@ -287,6 +303,10 @@ const ClassificationMethodConfig: React.FC = ({ : undefined, hybrid_boundary_margin: classifierType === "hybrid" ? value.hybrid_boundary_margin ?? DEFAULT_HYBRID_BOUNDARY_MARGIN : undefined, + // Only the LLM classifier can produce NON_REASONING, and the backend rejects the flag + // beside any other type. Clearing it here (with the tier's own pool) is what keeps a + // switch away from LLM from stranding a config that can never be saved. + ...nonReasoningTierFields(classifierType, value), }; onChange(nextValue); }; diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index 01e65914c67..540b68cf9f7 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -5,6 +5,8 @@ import { ChevronRight, Info, Plus, Trash2, X } from "lucide-react"; import { Switch } from "@/components/ui/switch"; import { AffinityControls } from "./AffinityControls"; +import NonReasoningTierToggle from "./NonReasoningTierToggle"; +import TierConfigIntro from "./TierConfigIntro"; import TierRowSelect from "./TierRowSelect"; import { ModalityRoutingControls } from "./ModalityRoutingControls"; import { Card, CardContent } from "@/components/ui/card"; @@ -20,6 +22,7 @@ import { MAX_TIER_COUNT, MAX_TIER_DEFINITION_CHARS, MAX_TIER_NAME_CHARS, + ALL_BUILT_IN_TIERS, MIN_TIER_COUNT, TIER_ORDER, activeTierName, @@ -200,34 +203,10 @@ const defaultModelPlaceholderFor = (derivedDefaultModel: string | undefined, isC }; const builtInTierInfo = (rowId: string): { label: string; description: string; examples: string } | undefined => { - const builtIn = TIER_ORDER.find((tier) => tier === rowId); + const builtIn = ALL_BUILT_IN_TIERS.find((tier) => tier === rowId); return builtIn ? TIER_DESCRIPTIONS[builtIn] : undefined; }; -const tierConfigIntroText = (value: ComplexityRouterConfigValue): string => { - if (value.classifier_type === "heuristic_v2") { - return "The complexity router classifies each request with a calibrated local four-tier model (no API calls). Configure which model(s) handle each tier."; - } - if (heuristicScoringRole(value) === "never") { - return "The complexity router classifies each request with your classifier model and routes it to that tier. Configure which model(s) handle each tier."; - } - return "The complexity router automatically classifies requests by complexity using rule-based scoring (no API calls, <1ms latency). Configure which model(s) handle each tier."; -}; - -const TierConfigIntro: React.FC<{ value: ComplexityRouterConfigValue }> = ({ value }) => ( - <> - {tierConfigIntroText(value)} - - - {restrictedBy(value, "displayNames")?.reason ?? - "Rename a tier to use your own vocabulary in the dashboard and your spend logs. Renaming doesn't change how requests are classified, and callers never see these names."} - {!value.custom_tier_set && - usesLlmClassifier(value.classifier_type) && - " Your classifier model reads these names, so clearer ones can sharpen its choices."} - - -); - const TierSetToolbar: React.FC<{ editing: boolean; isCustomSet: boolean; @@ -534,13 +513,6 @@ export const TIER_DESCRIPTIONS: Record< /** Every built-in tier name, including the opt-in one, for label and membership checks. */ export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array; -/** - * The four-tier ladder in ascending severity, which is what a router sends unless it opted into - * NON_REASONING. Mirrors TIER_SEVERITY_ORDER in the backend config; use tierOrderFor to get the - * ladder one router actually renders. - */ -export const BUILT_IN_TIER_ORDER: Array = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]; - export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string => tierLabels?.[tier]?.trim() || TIER_DESCRIPTIONS[tier].label; @@ -555,42 +527,7 @@ export const DEFAULT_HYBRID_BOUNDARY_MARGIN = 0.03; * NON_REASONING, which the backend refuses alongside heuristic_first because the local scorer * cannot produce it. */ -export const HEURISTIC_FIRST_MAX_TIER_KEYS = BUILT_IN_TIER_ORDER.slice(0, -1); - -/** - * The opt-in fifth tier. Only offered on the LLM classification method, matching the backend: the - * heuristic scorers cannot produce the tier, so enabling it there would buy a rubric bullet and a - * model pool that no request ever reaches. - */ -const NonReasoningTierToggle: React.FC<{ - value: ComplexityRouterConfigValue; - onChange: (value: ComplexityRouterConfigValue) => void; - available: boolean; -}> = ({ value, onChange, available }) => ( - <> -
- { - const { NON_REASONING: _dropped, ...keptTiers } = value.tiers; - onChange({ - ...value, - enable_non_reasoning_tier: enabled ? true : undefined, - tiers: enabled ? { ...keptTiers, NON_REASONING: value.tiers.NON_REASONING ?? [] } : keptTiers, - }); - }} - aria-label="Add a non-reasoning tier" - /> - Add a non-reasoning tier -
- - Adds NON_REASONING below Simple, for operational agent traffic that relays or reformats information rather than - reasoning about it. Escalation still moves up out of it when a request needs more. - {!available && " Requires the LLM classification method."} - - -); +export const HEURISTIC_FIRST_MAX_TIER_KEYS = TIER_ORDER.slice(0, -1); const PlanModeOverrideControls: React.FC<{ value: ComplexityRouterConfigValue; diff --git a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx new file mode 100644 index 00000000000..794c2e5ec70 --- /dev/null +++ b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +import { Switch } from "@/components/ui/switch"; + +import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; + +/** + * The opt-in fifth tier. Only offered on the LLM classification method, matching the backend: the + * heuristic scorers cannot produce the tier, so enabling it there would buy a rubric bullet and a + * model pool that no request ever reaches. + */ +const NonReasoningTierToggle: React.FC<{ + value: ComplexityRouterConfigValue; + onChange: (value: ComplexityRouterConfigValue) => void; + available: boolean; +}> = ({ value, onChange, available }) => { + // Turning it off drops the tier's key rather than leaving an empty pool, which the backend + // rejects; turning it back on restores whatever pool the form still held. + const handleToggle = (enabled: boolean): void => { + const { NON_REASONING: existingPool, ...keptTiers } = value.tiers; + const next: ComplexityRouterConfigValue = { + ...value, + enable_non_reasoning_tier: enabled ? true : undefined, + tiers: enabled ? { ...keptTiers, NON_REASONING: existingPool ?? [] } : keptTiers, + }; + onChange(next); + }; + + return ( + <> +
+ + Add a non-reasoning tier +
+ + Adds NON_REASONING below Simple, for operational agent traffic that relays or reformats information rather than + reasoning about it. Escalation still moves up out of it when a request needs more. + {!available && " Requires the LLM classification method."} + + + ); +}; + +export default NonReasoningTierToggle; diff --git a/ui/litellm-dashboard/src/components/add_model/TierConfigIntro.tsx b/ui/litellm-dashboard/src/components/add_model/TierConfigIntro.tsx new file mode 100644 index 00000000000..4b14307dda5 --- /dev/null +++ b/ui/litellm-dashboard/src/components/add_model/TierConfigIntro.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +import { type ComplexityRouterConfigValue, heuristicScoringRole, usesLlmClassifier } from "./ComplexityRouterConfig"; +import { restrictedBy } from "./TierRestrictions"; + +const tierConfigIntroText = (value: ComplexityRouterConfigValue): string => { + if (value.classifier_type === "heuristic_v2") { + return "The complexity router classifies each request with a calibrated local four-tier model (no API calls). Configure which model(s) handle each tier."; + } + if (heuristicScoringRole(value) === "never") { + return "The complexity router classifies each request with your classifier model and routes it to that tier. Configure which model(s) handle each tier."; + } + return "The complexity router automatically classifies requests by complexity using rule-based scoring (no API calls, <1ms latency). Configure which model(s) handle each tier."; +}; + +const TierConfigIntro: React.FC<{ value: ComplexityRouterConfigValue }> = ({ value }) => ( + <> + {tierConfigIntroText(value)} + + + {restrictedBy(value, "displayNames")?.reason ?? + "Rename a tier to use your own vocabulary in the dashboard and your spend logs. Renaming doesn't change how requests are classified, and callers never see these names."} + {!value.custom_tier_set && + usesLlmClassifier(value.classifier_type) && + " Your classifier model reads these names, so clearer ones can sharpen its choices."} + + +); + +export default TierConfigIntro; diff --git a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts new file mode 100644 index 00000000000..86e50130e43 --- /dev/null +++ b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; + +import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; +import { nonReasoningTierFields } from "./ClassificationMethodConfig"; + +const enabledValue: ComplexityRouterConfigValue = { + classifier_type: "llm", + enable_non_reasoning_tier: true, + tiers: { + NON_REASONING: ["relay-cheap"], + SIMPLE: ["gpt-4o-mini"], + MEDIUM: ["gpt-4o"], + COMPLEX: ["sonnet"], + REASONING: ["opus"], + }, +}; + +describe("nonReasoningTierFields", () => { + it("keeps the tier and its pool while the classifier stays LLM", () => { + expect(nonReasoningTierFields("llm", enabledValue)).toEqual({ + enable_non_reasoning_tier: true, + tiers: enabledValue.tiers, + }); + }); + + it.each(["heuristic", "heuristic_v2", "heuristic_first", "hybrid"] as const)( + "clears the flag and the tier when the classifier becomes %s", + (classifierType) => { + // Leaving the flag set under a classifier that cannot emit the tier is a config the backend + // refuses, and the switch is disabled there, so the operator could never undo it. + const cleared = nonReasoningTierFields(classifierType, enabledValue); + expect(cleared.enable_non_reasoning_tier).toBeUndefined(); + expect(cleared.tiers).not.toHaveProperty("NON_REASONING"); + }, + ); + + it("leaves the other tiers untouched when it clears", () => { + const { NON_REASONING: _dropped, ...expectedTiers } = enabledValue.tiers; + expect(nonReasoningTierFields("heuristic", enabledValue).tiers).toEqual(expectedTiers); + }); + + it("is a no-op for a router that never enabled the tier", () => { + const fourTier: ComplexityRouterConfigValue = { + classifier_type: "heuristic", + tiers: { SIMPLE: ["gpt-4o-mini"], MEDIUM: ["gpt-4o"], COMPLEX: ["sonnet"], REASONING: ["opus"] }, + }; + expect(nonReasoningTierFields("heuristic", fourTier)).toEqual({ + enable_non_reasoning_tier: undefined, + tiers: fourTier.tiers, + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts index 07b9a4702aa..872e8850d91 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.test.ts @@ -190,8 +190,15 @@ describe("the opt-in non-reasoning tier", () => { }); it("renders an enabled tier with no models as an empty row rather than crashing", () => { + const emptyTierZeroRow: ActiveTierRow = { + id: "NON_REASONING", + name: "NON_REASONING", + definition: "", + models: [], + params: {}, + }; const rows = activeTierRows({ tiers, enable_non_reasoning_tier: true }); - expect(rows[0]).toEqual({ id: "NON_REASONING", name: "NON_REASONING", definition: "", models: [], params: {} }); + expect(rows[0]).toEqual(emptyTierZeroRow); }); it("counts as a built-in name either way, so a custom set cannot claim the name", () => { diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6a60b0db58a..81b5ed79abf 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -23573,22 +23573,16 @@ export interface components { }; /** * AutoRouterPresetTiers - * @description The built-in tiers the dashboard's preset prefill can apply. + * @description Exactly the four built-in tiers the dashboard's preset prefill can apply. * * extra="forbid" on purpose: a tier name this dashboard cannot apply would grey out or crash the - * picker, so such a catalog is rejected wholesale and the bundled one serves instead. NON_REASONING - * is optional rather than required so this proxy accepts both a catalog that ships the opt-in fifth - * tier and the four-tier catalogs that predate it. It stays None when unset rather than defaulting - * to an empty pool, so a four-tier preset serves the tier set it was published with instead of - * growing a key the dashboard would render as an empty fifth tier row. + * picker, so such a catalog is rejected wholesale and the bundled one serves instead. */ AutoRouterPresetTiers: { /** Complex */ COMPLEX: string[]; /** Medium */ MEDIUM: string[]; - /** Non Reasoning */ - NON_REASONING?: string[] | null; /** Reasoning */ REASONING: string[]; /** Simple */ diff --git a/ui/litellm-dashboard/tsconfig.tsbuildinfo b/ui/litellm-dashboard/tsconfig.tsbuildinfo index 38e28e4f476..1036720c31e 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/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/@img/colour/index.d.ts","./node_modules/sharp/dist/index.d.mts","./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/@types/react/compiler-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/static.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/types/routes.d.ts","./next-env.d.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/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.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.bkdhh7zx.d.ts","./node_modules/vitest/dist/chunks/worker.d.cugipz9v.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.buron0i0.d.ts","./node_modules/vitest/dist/chunks/vite.d.bnoppc46.d.ts","./node_modules/vitest/dist/config.d.ts","./node_modules/vitest/config.d.ts","./vitest.config.ts","./src/types.ts","./node_modules/sonner/dist/index.d.mts","./src/lib/http/client.ts","./src/lib/toast.ts","./src/utils/securestorage.ts","./src/utils/mcptokenstore.ts","./src/utils/cookieutils.ts","./node_modules/jwt-decode/build/esm/index.d.ts","./src/utils/jwtutils.ts","./src/components/tag_management/types.tsx","./src/lib/http/schema.d.ts","./src/components/object_permission_types.ts","./node_modules/@base-ui/react/internals/reason-parts.d.mts","./node_modules/@base-ui/react/internals/reasons.d.mts","./node_modules/@base-ui/react/internals/createbaseuieventdetails.d.mts","./node_modules/@base-ui/react/types/index.d.mts","./node_modules/@base-ui/react/internals/types.d.mts","./node_modules/@base-ui/react/accordion/root/accordionroot.d.mts","./node_modules/@base-ui/react/internals/usetransitionstatus.d.mts","./node_modules/@base-ui/react/collapsible/root/collapsibleroot.d.mts","./node_modules/@base-ui/react/collapsible/root/usecollapsibleroot.d.mts","./node_modules/@base-ui/react/accordion/item/accordionitem.d.mts","./node_modules/@base-ui/react/accordion/header/accordionheader.d.mts","./node_modules/@base-ui/react/accordion/trigger/accordiontrigger.d.mts","./node_modules/@base-ui/react/accordion/panel/accordionpanel.d.mts","./node_modules/@base-ui/react/accordion/index.parts.d.mts","./node_modules/@base-ui/react/accordion/index.d.mts","./node_modules/reselect/dist/reselect.d.ts","./node_modules/@base-ui/utils/store/createselector.d.mts","./node_modules/@base-ui/utils/store/createselectormemoized.d.mts","./node_modules/@base-ui/utils/fasthooks.d.mts","./node_modules/@base-ui/utils/store/store.d.mts","./node_modules/@base-ui/utils/store/usestore.d.mts","./node_modules/@base-ui/utils/store/reactstore.d.mts","./node_modules/@base-ui/utils/store/storeinspector.d.mts","./node_modules/@base-ui/utils/store/index.d.mts","./node_modules/@base-ui/utils/useenhancedclickhandler.d.mts","./node_modules/@floating-ui/utils/dist/floating-ui.utils.d.mts","./node_modules/@floating-ui/core/dist/floating-ui.core.d.mts","./node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.d.mts","./node_modules/@floating-ui/dom/dist/floating-ui.dom.d.mts","./node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.d.mts","./node_modules/@base-ui/react/utils/popups/inlinerect.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingtreestore.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingrootstore.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingfocusmanager.d.mts","./node_modules/@base-ui/react/internals/getstateattributesprops.d.mts","./node_modules/@base-ui/react/internals/userenderelement.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingportal.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/useclientpoint.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usedismiss.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefocus.d.mts","./node_modules/@base-ui/react/internals/shadowdom.d.mts","./node_modules/@base-ui/react/floating-ui-react/utils/element.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehovershared.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehover.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehoverfloatinginteraction.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehoverreferenceinteraction.d.mts","./node_modules/@base-ui/react/floating-ui-react/utils/composite.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/gridnavigation.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/uselistnavigation.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usetypeahead.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefloatingrootcontext.d.mts","./node_modules/@base-ui/react/floating-ui-react/safepolygon.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingtree.d.mts","./node_modules/@base-ui/react/floating-ui-react/types.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingdelaygroup.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/useclick.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefloating.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usesyncedfloatingrootcontext.d.mts","./node_modules/@base-ui/react/floating-ui-react/index.d.mts","./node_modules/@base-ui/react/utils/popups/popuptriggermap.d.mts","./node_modules/@base-ui/react/utils/popups/store.d.mts","./node_modules/@base-ui/react/utils/popups/popupstoreutils.d.mts","./node_modules/@base-ui/react/utils/popups/index.d.mts","./node_modules/@base-ui/react/dialog/store/dialogstore.d.mts","./node_modules/@base-ui/react/dialog/store/dialoghandle.d.mts","./node_modules/@base-ui/react/dialog/root/dialogroot.d.mts","./node_modules/@base-ui/react/alert-dialog/handle.d.mts","./node_modules/@base-ui/react/alert-dialog/root/alertdialogroot.d.mts","./node_modules/@base-ui/react/dialog/backdrop/dialogbackdrop.d.mts","./node_modules/@base-ui/react/dialog/close/dialogclose.d.mts","./node_modules/@base-ui/react/dialog/description/dialogdescription.d.mts","./node_modules/@base-ui/react/dialog/popup/dialogpopup.d.mts","./node_modules/@base-ui/react/dialog/portal/dialogportal.d.mts","./node_modules/@base-ui/react/dialog/title/dialogtitle.d.mts","./node_modules/@base-ui/react/dialog/trigger/dialogtrigger.d.mts","./node_modules/@base-ui/react/alert-dialog/trigger/alertdialogtrigger.d.mts","./node_modules/@base-ui/react/dialog/viewport/dialogviewport.d.mts","./node_modules/@base-ui/react/alert-dialog/index.parts.d.mts","./node_modules/@base-ui/react/alert-dialog/index.d.mts","./node_modules/@base-ui/react/internals/resolvevaluelabel.d.mts","./node_modules/@base-ui/react/combobox/root/ariacombobox.d.mts","./node_modules/@base-ui/react/autocomplete/root/autocompleteroot.d.mts","./node_modules/@base-ui/react/autocomplete/value/autocompletevalue.d.mts","./node_modules/@base-ui/react/internals/form-context/formcontext.d.mts","./node_modules/@base-ui/react/form/form.d.mts","./node_modules/@base-ui/react/form/index.d.mts","./node_modules/@base-ui/react/field/root/fieldroot.d.mts","./node_modules/@base-ui/react/utils/useanchorpositioning.d.mts","./node_modules/@base-ui/react/autocomplete/trigger/autocompletetrigger.d.mts","./node_modules/@base-ui/react/combobox/input/comboboxinput.d.mts","./node_modules/@base-ui/react/autocomplete/input-group/autocompleteinputgroup.d.mts","./node_modules/@base-ui/react/combobox/icon/comboboxicon.d.mts","./node_modules/@base-ui/react/combobox/clear/comboboxclear.d.mts","./node_modules/@base-ui/react/combobox/list/comboboxlist.d.mts","./node_modules/@base-ui/react/combobox/status/comboboxstatus.d.mts","./node_modules/@base-ui/react/combobox/portal/comboboxportal.d.mts","./node_modules/@base-ui/react/combobox/backdrop/comboboxbackdrop.d.mts","./node_modules/@base-ui/react/combobox/positioner/comboboxpositioner.d.mts","./node_modules/@base-ui/react/combobox/popup/comboboxpopup.d.mts","./node_modules/@base-ui/react/combobox/arrow/comboboxarrow.d.mts","./node_modules/@base-ui/react/combobox/group/comboboxgroup.d.mts","./node_modules/@base-ui/react/combobox/group-label/comboboxgrouplabel.d.mts","./node_modules/@base-ui/react/autocomplete/item/autocompleteitem.d.mts","./node_modules/@base-ui/react/combobox/row/comboboxrow.d.mts","./node_modules/@base-ui/react/combobox/collection/comboboxcollection.d.mts","./node_modules/@base-ui/react/combobox/empty/comboboxempty.d.mts","./node_modules/@base-ui/react/separator/separator.d.mts","./node_modules/@base-ui/react/internals/filter.d.mts","./node_modules/@base-ui/react/combobox/root/utils/usefilter.d.mts","./node_modules/@base-ui/react/combobox/root/utils/usefiltereditems.d.mts","./node_modules/@base-ui/react/autocomplete/index.parts.d.mts","./node_modules/@base-ui/react/autocomplete/index.d.mts","./node_modules/@base-ui/react/avatar/root/avatarroot.d.mts","./node_modules/@base-ui/react/avatar/image/avatarimage.d.mts","./node_modules/@base-ui/react/avatar/fallback/avatarfallback.d.mts","./node_modules/@base-ui/react/avatar/index.parts.d.mts","./node_modules/@base-ui/react/avatar/index.d.mts","./node_modules/@base-ui/react/button/button.d.mts","./node_modules/@base-ui/react/button/index.d.mts","./node_modules/@base-ui/react/checkbox/root/checkboxroot.d.mts","./node_modules/@base-ui/react/checkbox/indicator/checkboxindicator.d.mts","./node_modules/@base-ui/react/checkbox/index.parts.d.mts","./node_modules/@base-ui/react/checkbox/index.d.mts","./node_modules/@base-ui/react/checkbox-group/checkboxgroup.d.mts","./node_modules/@base-ui/react/checkbox-group/index.d.mts","./node_modules/@base-ui/react/collapsible/trigger/collapsibletrigger.d.mts","./node_modules/@base-ui/react/collapsible/panel/collapsiblepanel.d.mts","./node_modules/@base-ui/react/collapsible/index.parts.d.mts","./node_modules/@base-ui/react/collapsible/index.d.mts","./node_modules/@base-ui/react/combobox/root/comboboxroot.d.mts","./node_modules/@base-ui/react/combobox/label/comboboxlabel.d.mts","./node_modules/@base-ui/react/combobox/value/comboboxvalue.d.mts","./node_modules/@base-ui/react/combobox/input-group/comboboxinputgroup.d.mts","./node_modules/@base-ui/react/combobox/trigger/comboboxtrigger.d.mts","./node_modules/@base-ui/react/combobox/item/comboboxitem.d.mts","./node_modules/@base-ui/react/combobox/item-indicator/comboboxitemindicator.d.mts","./node_modules/@base-ui/react/combobox/chips/comboboxchips.d.mts","./node_modules/@base-ui/react/combobox/chip/comboboxchip.d.mts","./node_modules/@base-ui/react/combobox/chip-remove/comboboxchipremove.d.mts","./node_modules/@base-ui/react/separator/index.d.mts","./node_modules/@base-ui/react/combobox/index.parts.d.mts","./node_modules/@base-ui/react/combobox/index.d.mts","./node_modules/@base-ui/react/menu/arrow/menuarrow.d.mts","./node_modules/@base-ui/react/menu/backdrop/menubackdrop.d.mts","./node_modules/@base-ui/react/menu/store/menustore.d.mts","./node_modules/@base-ui/react/menu/root/menurootcontext.d.mts","./node_modules/@base-ui/react/menubar/menubarcontext.d.mts","./node_modules/@base-ui/react/context-menu/root/contextmenurootcontext.d.mts","./node_modules/@base-ui/react/menu/store/menuhandle.d.mts","./node_modules/@base-ui/react/menu/root/menuroot.d.mts","./node_modules/@base-ui/react/menu/checkbox-item/menucheckboxitem.d.mts","./node_modules/@base-ui/react/menu/checkbox-item-indicator/menucheckboxitemindicator.d.mts","./node_modules/@base-ui/react/menu/group/menugroup.d.mts","./node_modules/@base-ui/react/menu/group-label/menugrouplabel.d.mts","./node_modules/@base-ui/react/menu/item/menuitem.d.mts","./node_modules/@base-ui/react/menu/link-item/menulinkitem.d.mts","./node_modules/@base-ui/react/menu/popup/menupopup.d.mts","./node_modules/@base-ui/react/menu/portal/menuportal.d.mts","./node_modules/@base-ui/react/menu/positioner/menupositioner.d.mts","./node_modules/@base-ui/react/menu/radio-group/menuradiogroup.d.mts","./node_modules/@base-ui/react/menu/radio-item/menuradioitem.d.mts","./node_modules/@base-ui/react/menu/radio-item-indicator/menuradioitemindicator.d.mts","./node_modules/@base-ui/react/menu/submenu-root/menusubmenurootcontext.d.mts","./node_modules/@base-ui/react/menu/submenu-root/menusubmenuroot.d.mts","./node_modules/@base-ui/react/menu/trigger/menutrigger.d.mts","./node_modules/@base-ui/react/menu/viewport/menuviewport.d.mts","./node_modules/@base-ui/react/menu/submenu-trigger/menusubmenutrigger.d.mts","./node_modules/@base-ui/react/menu/index.parts.d.mts","./node_modules/@base-ui/react/menu/index.d.mts","./node_modules/@base-ui/react/context-menu/root/contextmenuroot.d.mts","./node_modules/@base-ui/react/context-menu/trigger/contextmenutrigger.d.mts","./node_modules/@base-ui/react/context-menu/index.parts.d.mts","./node_modules/@base-ui/react/context-menu/index.d.mts","./node_modules/@base-ui/react/csp-provider/cspprovider.d.mts","./node_modules/@base-ui/react/csp-provider/index.parts.d.mts","./node_modules/@base-ui/react/csp-provider/index.d.mts","./node_modules/@base-ui/react/dialog/index.parts.d.mts","./node_modules/@base-ui/react/dialog/index.d.mts","./node_modules/@base-ui/react/internals/direction-context/directioncontext.d.mts","./node_modules/@base-ui/react/direction-provider/directionprovider.d.mts","./node_modules/@base-ui/react/direction-provider/index.parts.d.mts","./node_modules/@base-ui/react/direction-provider/index.d.mts","./node_modules/@base-ui/react/drawer/backdrop/drawerbackdrop.d.mts","./node_modules/@base-ui/react/drawer/close/drawerclose.d.mts","./node_modules/@base-ui/react/drawer/content/drawercontent.d.mts","./node_modules/@base-ui/react/drawer/description/drawerdescription.d.mts","./node_modules/@base-ui/react/drawer/indent/drawerindent.d.mts","./node_modules/@base-ui/react/drawer/indent-background/drawerindentbackground.d.mts","./node_modules/@base-ui/react/utils/useswipedismiss.d.mts","./node_modules/@base-ui/react/drawer/root/drawerroot.d.mts","./node_modules/@base-ui/react/drawer/root/drawerrootcontext.d.mts","./node_modules/@base-ui/react/drawer/popup/drawerpopup.d.mts","./node_modules/@base-ui/react/drawer/portal/drawerportal.d.mts","./node_modules/@base-ui/react/drawer/provider/drawerprovider.d.mts","./node_modules/@base-ui/react/drawer/swipe-area/drawerswipearea.d.mts","./node_modules/@base-ui/react/drawer/title/drawertitle.d.mts","./node_modules/@base-ui/react/drawer/trigger/drawertrigger.d.mts","./node_modules/@base-ui/react/drawer/viewport/drawerviewport.d.mts","./node_modules/@base-ui/react/drawer/virtual-keyboard-provider/drawervirtualkeyboardprovider.d.mts","./node_modules/@base-ui/react/drawer/index.parts.d.mts","./node_modules/@base-ui/react/drawer/index.d.mts","./node_modules/@base-ui/react/field/label/fieldlabel.d.mts","./node_modules/@base-ui/react/field/error/fielderror.d.mts","./node_modules/@base-ui/react/field/description/fielddescription.d.mts","./node_modules/@base-ui/react/field/control/fieldcontrol.d.mts","./node_modules/@base-ui/react/field/validity/fieldvalidity.d.mts","./node_modules/@base-ui/react/field/item/fielditem.d.mts","./node_modules/@base-ui/react/field/index.parts.d.mts","./node_modules/@base-ui/react/field/index.d.mts","./node_modules/@base-ui/react/fieldset/root/fieldsetroot.d.mts","./node_modules/@base-ui/react/fieldset/legend/fieldsetlegend.d.mts","./node_modules/@base-ui/react/fieldset/index.parts.d.mts","./node_modules/@base-ui/react/fieldset/index.d.mts","./node_modules/@base-ui/react/input/input.d.mts","./node_modules/@base-ui/react/input/index.d.mts","./node_modules/@base-ui/react/menubar/menubar.d.mts","./node_modules/@base-ui/react/menubar/index.d.mts","./node_modules/@base-ui/react/merge-props/mergeprops.d.mts","./node_modules/@base-ui/react/merge-props/index.d.mts","./node_modules/@base-ui/react/meter/root/meterroot.d.mts","./node_modules/@base-ui/react/meter/track/metertrack.d.mts","./node_modules/@base-ui/react/meter/indicator/meterindicator.d.mts","./node_modules/@base-ui/react/meter/value/metervalue.d.mts","./node_modules/@base-ui/react/meter/label/meterlabel.d.mts","./node_modules/@base-ui/react/meter/index.parts.d.mts","./node_modules/@base-ui/react/meter/index.d.mts","./node_modules/@base-ui/react/navigation-menu/root/navigationmenuroot.d.mts","./node_modules/@base-ui/react/navigation-menu/list/navigationmenulist.d.mts","./node_modules/@base-ui/react/navigation-menu/item/navigationmenuitem.d.mts","./node_modules/@base-ui/react/navigation-menu/content/navigationmenucontent.d.mts","./node_modules/@base-ui/react/navigation-menu/trigger/navigationmenutrigger.d.mts","./node_modules/@base-ui/react/navigation-menu/portal/navigationmenuportal.d.mts","./node_modules/@base-ui/react/navigation-menu/positioner/navigationmenupositioner.d.mts","./node_modules/@base-ui/react/navigation-menu/viewport/navigationmenuviewport.d.mts","./node_modules/@base-ui/react/navigation-menu/backdrop/navigationmenubackdrop.d.mts","./node_modules/@base-ui/react/navigation-menu/popup/navigationmenupopup.d.mts","./node_modules/@base-ui/react/navigation-menu/arrow/navigationmenuarrow.d.mts","./node_modules/@base-ui/react/navigation-menu/link/navigationmenulink.d.mts","./node_modules/@base-ui/react/navigation-menu/icon/navigationmenuicon.d.mts","./node_modules/@base-ui/react/navigation-menu/index.parts.d.mts","./node_modules/@base-ui/react/navigation-menu/index.d.mts","./node_modules/@base-ui/react/number-field/utils/types.d.mts","./node_modules/@base-ui/react/number-field/root/numberfieldroot.d.mts","./node_modules/@base-ui/react/number-field/group/numberfieldgroup.d.mts","./node_modules/@base-ui/react/number-field/increment/numberfieldincrement.d.mts","./node_modules/@base-ui/react/number-field/decrement/numberfielddecrement.d.mts","./node_modules/@base-ui/react/number-field/input/numberfieldinput.d.mts","./node_modules/@base-ui/react/number-field/scrub-area/numberfieldscrubarea.d.mts","./node_modules/@base-ui/react/number-field/scrub-area-cursor/numberfieldscrubareacursor.d.mts","./node_modules/@base-ui/react/number-field/index.parts.d.mts","./node_modules/@base-ui/react/number-field/index.d.mts","./node_modules/@base-ui/react/otp-field/utils/otp.d.mts","./node_modules/@base-ui/react/otp-field/root/otpfieldroot.d.mts","./node_modules/@base-ui/react/otp-field/input/otpfieldinput.d.mts","./node_modules/@base-ui/react/otp-field/index.parts.d.mts","./node_modules/@base-ui/react/otp-field/index.d.mts","./node_modules/@base-ui/utils/usetimeout.d.mts","./node_modules/@base-ui/react/popover/store/popoverstore.d.mts","./node_modules/@base-ui/react/popover/store/popoverhandle.d.mts","./node_modules/@base-ui/react/popover/root/popoverroot.d.mts","./node_modules/@base-ui/react/popover/trigger/popovertrigger.d.mts","./node_modules/@base-ui/react/popover/portal/popoverportal.d.mts","./node_modules/@base-ui/react/popover/positioner/popoverpositioner.d.mts","./node_modules/@base-ui/react/popover/popup/popoverpopup.d.mts","./node_modules/@base-ui/react/popover/arrow/popoverarrow.d.mts","./node_modules/@base-ui/react/popover/backdrop/popoverbackdrop.d.mts","./node_modules/@base-ui/react/popover/title/popovertitle.d.mts","./node_modules/@base-ui/react/popover/description/popoverdescription.d.mts","./node_modules/@base-ui/react/popover/close/popoverclose.d.mts","./node_modules/@base-ui/react/popover/viewport/popoverviewport.d.mts","./node_modules/@base-ui/react/popover/index.parts.d.mts","./node_modules/@base-ui/react/popover/index.d.mts","./node_modules/@base-ui/react/preview-card/store/previewcardstore.d.mts","./node_modules/@base-ui/react/preview-card/store/previewcardhandle.d.mts","./node_modules/@base-ui/react/preview-card/root/previewcardroot.d.mts","./node_modules/@base-ui/react/utils/floatingportallite.d.mts","./node_modules/@base-ui/react/preview-card/portal/previewcardportal.d.mts","./node_modules/@base-ui/react/preview-card/trigger/previewcardtrigger.d.mts","./node_modules/@base-ui/react/preview-card/positioner/previewcardpositioner.d.mts","./node_modules/@base-ui/react/preview-card/popup/previewcardpopup.d.mts","./node_modules/@base-ui/react/preview-card/arrow/previewcardarrow.d.mts","./node_modules/@base-ui/react/preview-card/backdrop/previewcardbackdrop.d.mts","./node_modules/@base-ui/react/preview-card/viewport/previewcardviewport.d.mts","./node_modules/@base-ui/react/preview-card/index.parts.d.mts","./node_modules/@base-ui/react/preview-card/index.d.mts","./node_modules/@base-ui/react/progress/root/progressroot.d.mts","./node_modules/@base-ui/react/progress/track/progresstrack.d.mts","./node_modules/@base-ui/react/progress/indicator/progressindicator.d.mts","./node_modules/@base-ui/react/progress/value/progressvalue.d.mts","./node_modules/@base-ui/react/progress/label/progresslabel.d.mts","./node_modules/@base-ui/react/progress/index.parts.d.mts","./node_modules/@base-ui/react/progress/index.d.mts","./node_modules/@base-ui/react/radio/root/radioroot.d.mts","./node_modules/@base-ui/react/radio/indicator/radioindicator.d.mts","./node_modules/@base-ui/react/radio/index.parts.d.mts","./node_modules/@base-ui/react/radio/index.d.mts","./node_modules/@base-ui/react/radio-group/radiogroup.d.mts","./node_modules/@base-ui/react/radio-group/index.d.mts","./node_modules/@base-ui/react/scroll-area/root/scrollarearoot.d.mts","./node_modules/@base-ui/react/scroll-area/viewport/scrollareaviewport.d.mts","./node_modules/@base-ui/react/scroll-area/scrollbar/scrollareascrollbar.d.mts","./node_modules/@base-ui/react/scroll-area/content/scrollareacontent.d.mts","./node_modules/@base-ui/react/scroll-area/thumb/scrollareathumb.d.mts","./node_modules/@base-ui/react/scroll-area/corner/scrollareacorner.d.mts","./node_modules/@base-ui/react/scroll-area/index.parts.d.mts","./node_modules/@base-ui/react/scroll-area/index.d.mts","./node_modules/@base-ui/react/select/root/selectroot.d.mts","./node_modules/@base-ui/react/select/label/selectlabel.d.mts","./node_modules/@base-ui/react/select/trigger/selecttrigger.d.mts","./node_modules/@base-ui/react/select/value/selectvalue.d.mts","./node_modules/@base-ui/react/select/icon/selecticon.d.mts","./node_modules/@base-ui/react/select/portal/selectportal.d.mts","./node_modules/@base-ui/react/select/backdrop/selectbackdrop.d.mts","./node_modules/@base-ui/react/select/positioner/selectpositioner.d.mts","./node_modules/@base-ui/react/select/popup/selectpopup.d.mts","./node_modules/@base-ui/react/select/list/selectlist.d.mts","./node_modules/@base-ui/react/select/item/selectitem.d.mts","./node_modules/@base-ui/react/select/item-indicator/selectitemindicator.d.mts","./node_modules/@base-ui/react/select/item-text/selectitemtext.d.mts","./node_modules/@base-ui/react/select/arrow/selectarrow.d.mts","./node_modules/@base-ui/react/select/scroll-down-arrow/selectscrolldownarrow.d.mts","./node_modules/@base-ui/react/select/scroll-up-arrow/selectscrolluparrow.d.mts","./node_modules/@base-ui/react/select/group/selectgroup.d.mts","./node_modules/@base-ui/react/select/group-label/selectgrouplabel.d.mts","./node_modules/@base-ui/react/select/index.parts.d.mts","./node_modules/@base-ui/react/select/index.d.mts","./node_modules/@base-ui/react/slider/root/sliderroot.d.mts","./node_modules/@base-ui/react/slider/label/sliderlabel.d.mts","./node_modules/@base-ui/react/slider/value/slidervalue.d.mts","./node_modules/@base-ui/react/slider/control/slidercontrol.d.mts","./node_modules/@base-ui/react/slider/track/slidertrack.d.mts","./node_modules/@base-ui/react/internals/labelable-provider/labelablecontext.d.mts","./node_modules/@base-ui/react/slider/thumb/sliderthumb.d.mts","./node_modules/@base-ui/react/slider/indicator/sliderindicator.d.mts","./node_modules/@base-ui/react/slider/index.parts.d.mts","./node_modules/@base-ui/react/slider/index.d.mts","./node_modules/@base-ui/react/switch/root/switchroot.d.mts","./node_modules/@base-ui/react/switch/thumb/switchthumb.d.mts","./node_modules/@base-ui/react/switch/index.parts.d.mts","./node_modules/@base-ui/react/switch/index.d.mts","./node_modules/@base-ui/react/tabs/tab/tabstab.d.mts","./node_modules/@base-ui/react/tabs/root/tabsroot.d.mts","./node_modules/@base-ui/react/tabs/indicator/tabsindicator.d.mts","./node_modules/@base-ui/react/tabs/panel/tabspanel.d.mts","./node_modules/@base-ui/react/tabs/list/tabslist.d.mts","./node_modules/@base-ui/react/tabs/index.parts.d.mts","./node_modules/@base-ui/react/tabs/index.d.mts","./node_modules/@base-ui/react/toast/positioner/toastpositioner.d.mts","./node_modules/@base-ui/react/toast/usetoastmanager.d.mts","./node_modules/@base-ui/react/toast/createtoastmanager.d.mts","./node_modules/@base-ui/react/toast/provider/toastprovider.d.mts","./node_modules/@base-ui/react/toast/viewport/toastviewport.d.mts","./node_modules/@base-ui/react/toast/root/toastroot.d.mts","./node_modules/@base-ui/react/toast/content/toastcontent.d.mts","./node_modules/@base-ui/react/toast/description/toastdescription.d.mts","./node_modules/@base-ui/react/toast/title/toasttitle.d.mts","./node_modules/@base-ui/react/toast/close/toastclose.d.mts","./node_modules/@base-ui/react/toast/action/toastaction.d.mts","./node_modules/@base-ui/react/toast/portal/toastportal.d.mts","./node_modules/@base-ui/react/toast/arrow/toastarrow.d.mts","./node_modules/@base-ui/react/toast/index.parts.d.mts","./node_modules/@base-ui/react/toast/index.d.mts","./node_modules/@base-ui/react/toggle/toggle.d.mts","./node_modules/@base-ui/react/toggle/index.d.mts","./node_modules/@base-ui/react/toggle-group/togglegroup.d.mts","./node_modules/@base-ui/react/toggle-group/index.d.mts","./node_modules/@base-ui/react/toolbar/separator/toolbarseparator.d.mts","./node_modules/@base-ui/react/toolbar/root/toolbarroot.d.mts","./node_modules/@base-ui/react/toolbar/group/toolbargroup.d.mts","./node_modules/@base-ui/react/toolbar/button/toolbarbutton.d.mts","./node_modules/@base-ui/react/toolbar/link/toolbarlink.d.mts","./node_modules/@base-ui/react/toolbar/input/toolbarinput.d.mts","./node_modules/@base-ui/react/toolbar/index.parts.d.mts","./node_modules/@base-ui/react/toolbar/index.d.mts","./node_modules/@base-ui/react/tooltip/store/tooltipstore.d.mts","./node_modules/@base-ui/react/tooltip/store/tooltiphandle.d.mts","./node_modules/@base-ui/react/tooltip/root/tooltiproot.d.mts","./node_modules/@base-ui/react/tooltip/trigger/tooltiptrigger.d.mts","./node_modules/@base-ui/react/tooltip/portal/tooltipportal.d.mts","./node_modules/@base-ui/react/tooltip/positioner/tooltippositioner.d.mts","./node_modules/@base-ui/react/tooltip/popup/tooltippopup.d.mts","./node_modules/@base-ui/react/tooltip/arrow/tooltiparrow.d.mts","./node_modules/@base-ui/react/tooltip/provider/tooltipprovider.d.mts","./node_modules/@base-ui/react/tooltip/viewport/tooltipviewport.d.mts","./node_modules/@base-ui/react/tooltip/index.parts.d.mts","./node_modules/@base-ui/react/tooltip/index.d.mts","./node_modules/@base-ui/react/use-render/userender.d.mts","./node_modules/@base-ui/react/use-render/index.d.mts","./node_modules/@base-ui/react/index.d.mts","./node_modules/clsx/clsx.d.mts","./node_modules/tailwind-merge/dist/types.d.ts","./node_modules/class-variance-authority/dist/types.d.ts","./node_modules/class-variance-authority/dist/index.d.ts","./src/lib/cva.config.ts","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/textarea.tsx","./src/components/ui/input-group.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/ui/combobox.tsx","./src/components/shared/searchselect.tsx","./src/components/ui/label.tsx","./src/components/ui/separator.tsx","./src/components/ui/field.tsx","./src/components/ui/select.tsx","./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","./src/components/key_team_helpers/key_list.tsx","./src/components/email_events/types.ts","./src/components/claude_code_plugins/types.ts","./src/components/ui/tooltip.tsx","./node_modules/react-hook-form/dist/constants.d.ts","./node_modules/react-hook-form/dist/utils/createsubject.d.ts","./node_modules/react-hook-form/dist/types/events.d.ts","./node_modules/react-hook-form/dist/types/path/common.d.ts","./node_modules/react-hook-form/dist/types/path/eager.d.ts","./node_modules/react-hook-form/dist/types/path/index.d.ts","./node_modules/react-hook-form/dist/types/fieldarray.d.ts","./node_modules/react-hook-form/dist/types/resolvers.d.ts","./node_modules/react-hook-form/dist/types/form.d.ts","./node_modules/react-hook-form/dist/types/utils.d.ts","./node_modules/react-hook-form/dist/types/fields.d.ts","./node_modules/react-hook-form/dist/types/errors.d.ts","./node_modules/react-hook-form/dist/types/validator.d.ts","./node_modules/react-hook-form/dist/types/controller.d.ts","./node_modules/react-hook-form/dist/types/watch.d.ts","./node_modules/react-hook-form/dist/types/index.d.ts","./node_modules/react-hook-form/dist/controller.d.ts","./node_modules/react-hook-form/dist/fieldarray.d.ts","./node_modules/react-hook-form/dist/form.d.ts","./node_modules/react-hook-form/dist/formstatesubscribe.d.ts","./node_modules/react-hook-form/dist/logic/appenderrors.d.ts","./node_modules/react-hook-form/dist/logic/createformcontrol.d.ts","./node_modules/react-hook-form/dist/logic/index.d.ts","./node_modules/react-hook-form/dist/usecontroller.d.ts","./node_modules/react-hook-form/dist/usefieldarray.d.ts","./node_modules/react-hook-form/dist/useform.d.ts","./node_modules/react-hook-form/dist/useformcontext.d.ts","./node_modules/react-hook-form/dist/useformstate.d.ts","./node_modules/react-hook-form/dist/usewatch.d.ts","./node_modules/react-hook-form/dist/utils/get.d.ts","./node_modules/react-hook-form/dist/utils/set.d.ts","./node_modules/react-hook-form/dist/utils/index.d.ts","./node_modules/react-hook-form/dist/watch.d.ts","./node_modules/react-hook-form/dist/index.d.ts","./src/utils/textutils.ts","./src/components/common_components/mountedformfield.tsx","./src/components/common_components/check_openapi_schema.tsx","./src/components/mcp_tools/types.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/types.ts","./src/components/mcp_tools/constants.ts","./src/components/shared/multiselect.tsx","./src/components/ui/card.tsx","./src/components/add_model/complexity_router_keywords.ts","./src/components/ui/switch.tsx","./src/components/ui/collapsible.tsx","./src/components/key_team_helpers/fetch_available_models_team_key.tsx","./src/components/llm_calls/fetch_models.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/slider.tsx","./src/components/add_model/adaptiveroutingconfig.tsx","./src/utils/returnurlutils.ts","./src/utils/roles.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","./src/app/(dashboard)/hooks/common/querykeysfactory.ts","./src/app/(dashboard)/hooks/uiconfig/useuiconfig.ts","./src/app/(dashboard)/hooks/useauthorized.ts","./src/components/ui/dialog.tsx","./src/components/add_model/classifierprompteditorstate.ts","./src/components/add_model/classifierprompteditor.tsx","./src/app/(dashboard)/hooks/autorouter/usecomplexityscorerdefaults.ts","./src/components/ui/badge.tsx","./src/components/add_model/heuristic_scoring_knobs.ts","./src/components/add_model/heuristicscoringconfig.tsx","./src/components/add_model/classificationmethodconfig.tsx","./src/components/add_model/tiermodeleffortrows.tsx","./src/components/add_model/escalationkeywords.tsx","./src/components/add_model/semantickeywordmatching.tsx","./src/components/add_model/complexityrouterconfig.tsx","./src/components/add_model/tier_rows.ts","./src/components/add_model/complexity_router_tiers.ts","./src/components/add_model/keywordtierrules.tsx","./src/components/add_model/build_complexity_router_config.ts","./src/components/vector_store_management/types.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/shared/datatable/types.ts","./src/components/shared/datatable/columnmeta.ts","./src/components/ui/skeleton.tsx","./src/components/ui/table.tsx","./src/components/shared/datatable/datatablepagination.tsx","./src/components/shared/datatable/datatable.tsx","./src/components/ui/sheet.tsx","./src/components/shared/datatable/datatablefilterdrawer.tsx","./src/components/ui/checkbox.tsx","./src/components/shared/datatable/datatableselectioncolumn.tsx","./src/components/shared/datatable/datatableviewoptions.tsx","./src/components/shared/datatable/datatabletoolbar.tsx","./src/components/shared/datatable/datatablesortheader.tsx","./src/components/shared/datatable/index.ts","./src/app/(dashboard)/hooks/models/usemodels.ts","./src/components/shared/table_cells/autoroutertag.tsx","./src/components/shared/table_cells/cell_tooltip.tsx","./src/components/shared/table_cells/date_cell.tsx","./src/utils/datautils.ts","./src/components/shared/table_cells/id_cell.tsx","./src/components/shared/entitylink.tsx","./src/components/shared/table_cells/identity_cell.tsx","./src/components/key_scope.ts","./src/components/shared/table_cells/models_cell.tsx","./src/components/shared/table_cells/money_cell.tsx","./src/components/shared/inheritedbudgethint.tsx","./src/components/shared/meter.tsx","./src/components/shared/table_cells/spend_budget_cell.tsx","./src/components/shared/table_cells/status_badge.tsx","./src/components/shared/table_cells/index.ts","./src/utils/migratedpages.ts","./src/utils/entitylinks.ts","./src/app/(dashboard)/vector-stores/_components/indexestablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/indexestable.tsx","./src/app/(dashboard)/vector-stores/_components/indexestab.tsx","./src/components/view_logs/logdetailsdrawer/routingdecisioncard.tsx","./src/lib/http/resolveapibase.ts","./src/lib/http/runtime.ts","./src/lib/serverrootpath.ts","./src/components/networking.tsx","./src/app/(dashboard)/networking.ts","./src/app/(dashboard)/access-groups/_components/types.ts","./node_modules/vitest/dist/chunks/worker.d.uzwscv9x.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/zod/v4/core/standard-schema.d.cts","./node_modules/zod/v4/core/util.d.cts","./node_modules/zod/v4/core/versions.d.cts","./node_modules/zod/v4/core/schemas.d.cts","./node_modules/zod/v4/core/checks.d.cts","./node_modules/zod/v4/core/errors.d.cts","./node_modules/zod/v4/core/core.d.cts","./node_modules/zod/v4/core/parse.d.cts","./node_modules/zod/v4/core/regexes.d.cts","./node_modules/zod/v4/locales/ar.d.cts","./node_modules/zod/v4/locales/az.d.cts","./node_modules/zod/v4/locales/be.d.cts","./node_modules/zod/v4/locales/ca.d.cts","./node_modules/zod/v4/locales/cs.d.cts","./node_modules/zod/v4/locales/de.d.cts","./node_modules/zod/v4/locales/en.d.cts","./node_modules/zod/v4/locales/eo.d.cts","./node_modules/zod/v4/locales/es.d.cts","./node_modules/zod/v4/locales/fa.d.cts","./node_modules/zod/v4/locales/fi.d.cts","./node_modules/zod/v4/locales/fr.d.cts","./node_modules/zod/v4/locales/fr-ca.d.cts","./node_modules/zod/v4/locales/he.d.cts","./node_modules/zod/v4/locales/hu.d.cts","./node_modules/zod/v4/locales/id.d.cts","./node_modules/zod/v4/locales/it.d.cts","./node_modules/zod/v4/locales/ja.d.cts","./node_modules/zod/v4/locales/kh.d.cts","./node_modules/zod/v4/locales/ko.d.cts","./node_modules/zod/v4/locales/mk.d.cts","./node_modules/zod/v4/locales/ms.d.cts","./node_modules/zod/v4/locales/nl.d.cts","./node_modules/zod/v4/locales/no.d.cts","./node_modules/zod/v4/locales/ota.d.cts","./node_modules/zod/v4/locales/ps.d.cts","./node_modules/zod/v4/locales/pl.d.cts","./node_modules/zod/v4/locales/pt.d.cts","./node_modules/zod/v4/locales/ru.d.cts","./node_modules/zod/v4/locales/sl.d.cts","./node_modules/zod/v4/locales/sv.d.cts","./node_modules/zod/v4/locales/ta.d.cts","./node_modules/zod/v4/locales/th.d.cts","./node_modules/zod/v4/locales/tr.d.cts","./node_modules/zod/v4/locales/ua.d.cts","./node_modules/zod/v4/locales/ur.d.cts","./node_modules/zod/v4/locales/vi.d.cts","./node_modules/zod/v4/locales/zh-cn.d.cts","./node_modules/zod/v4/locales/zh-tw.d.cts","./node_modules/zod/v4/locales/index.d.cts","./node_modules/zod/v4/core/registries.d.cts","./node_modules/zod/v4/core/doc.d.cts","./node_modules/zod/v4/core/function.d.cts","./node_modules/zod/v4/core/api.d.cts","./node_modules/zod/v4/core/json-schema.d.cts","./node_modules/zod/v4/core/to-json-schema.d.cts","./node_modules/zod/v4/core/index.d.cts","./node_modules/zod/v4/classic/errors.d.cts","./node_modules/zod/v4/classic/parse.d.cts","./node_modules/zod/v4/classic/schemas.d.cts","./node_modules/zod/v4/classic/checks.d.cts","./node_modules/zod/v4/classic/compat.d.cts","./node_modules/zod/v4/classic/iso.d.cts","./node_modules/zod/v4/classic/coerce.d.cts","./node_modules/zod/v4/classic/external.d.cts","./node_modules/zod/v4/classic/index.d.cts","./node_modules/zod/v4/index.d.cts","./src/app/(dashboard)/access-groups/_components/access-group-create/schema.ts","./src/app/(dashboard)/access-groups/_components/access-group-create/mapper.ts","./src/app/(dashboard)/access-groups/_components/access-group-create/mapper.test.ts","./src/app/(dashboard)/agents/_components/agent_config.ts","./src/app/(dashboard)/agents/_components/agent_discovery_utils.ts","./src/app/(dashboard)/agents/_components/agent_discovery_utils.test.ts","./src/components/agents/types.ts","./src/app/(dashboard)/agents/_components/agent_type_utils.ts","./src/app/(dashboard)/budgets/_components/budgetprecision.ts","./src/app/(dashboard)/budgets/_components/budgetprecision.test.ts","./src/app/(dashboard)/budgets/_components/constants.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsfields.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsutils.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsutils.test.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisfields.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisutils.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisutils.test.ts","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarks.ts","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarks.test.ts","./src/components/usagepage/types.ts","./src/app/(dashboard)/cost-optimization/_components/costoptimizationutils.ts","./src/app/(dashboard)/cost-optimization/_components/costoptimizationutils.test.ts","./src/app/(dashboard)/cost-optimization/_components/helpers.ts","./src/app/(dashboard)/cost-optimization/_components/helpers.test.ts","./node_modules/openapi-typescript-helpers/dist/index.d.mts","./node_modules/openapi-fetch/dist/index.d.mts","./node_modules/openapi-react-query/dist/index.d.mts","./src/lib/http/api.ts","./src/app/(dashboard)/usage/_components/hooks/usepaginateddailyactivity.ts","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.ts","./src/app/(dashboard)/cost-optimization/_components/useautorouterbenchmarks.ts","./src/app/(dashboard)/cost-optimization/_components/useautorouterbenchmarks.test.ts","./src/app/(dashboard)/cost-optimization/_components/useshadoweval.ts","./src/app/(dashboard)/cost-optimization/_components/useshadoweval.test.ts","./src/components/ui/alert-dialog.tsx","./src/components/ui/tabs.tsx","./src/app/(dashboard)/cost-tracking/_components/types.ts","./src/components/common_components/simple_table.tsx","./src/lib/assetpaths.ts","./src/components/provider_info_helpers.tsx","./src/components/molecules/logo/logo.tsx","./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/hooks/use-safe-layout-effect.ts","./src/components/ui/ui-loading-spinner.tsx","./src/components/ui/dropdown-menu.tsx","./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","./src/components/helplink.tsx","./node_modules/@types/react-syntax-highlighter/index.d.ts","./node_modules/next-themes/dist/index.d.ts","./src/hooks/usesyntaxtheme.ts","./src/components/codeblock.tsx","./src/app/(dashboard)/cost-tracking/_components/how_it_works.tsx","./src/app/(dashboard)/cost-tracking/_components/provider_display_helpers.ts","./src/app/(dashboard)/cost-tracking/_components/use_discount_config.ts","./src/app/(dashboard)/cost-tracking/_components/use_margin_config.ts","./src/app/(dashboard)/cost-tracking/_components/use_block_unpriced_config.ts","./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","./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","./src/app/(dashboard)/cost-tracking/_components/use_block_unpriced_config.test.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","./src/app/(dashboard)/guardrails/_components/guardrail_garden_configs.ts","./src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts","./src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts","./src/app/(dashboard)/guardrails/_components/content_filter/action_options.ts","./src/app/(dashboard)/guardrails/_components/custom_code/customcodemodal.tsx","./src/app/(dashboard)/guardrails/_components/custom_code/index.ts","./src/app/(dashboard)/hooks/useauthorized.serverrootpath.test.ts","./src/app/(dashboard)/hooks/useauthorized.test.ts","./src/utils/capabilities.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.ts","./src/app/(dashboard)/hooks/useisorgadmin.ts","./src/app/(dashboard)/hooks/usecan.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/usehideautorouterannouncement.ts","./src/app/(dashboard)/hooks/useisorgadmin.test.ts","./src/utils/proxyutils.ts","./src/app/(dashboard)/hooks/proxysettings/useproxysettings.ts","./src/app/(dashboard)/hooks/uselogout.ts","./src/utils/tabroutes.ts","./src/app/(dashboard)/hooks/usetabrouting.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/usedeleteaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/useeditaccessgroup.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/budgetfilters.ts","./src/app/(dashboard)/hooks/budgets/budgetfilters.test.ts","./node_modules/@tanstack/react-store/dist/createstorecontext.d.ts","./node_modules/@tanstack/store/dist/alien.d.ts","./node_modules/@tanstack/store/dist/types.d.ts","./node_modules/@tanstack/store/dist/atom.d.ts","./node_modules/@tanstack/store/dist/store.d.ts","./node_modules/@tanstack/store/dist/shallow.d.ts","./node_modules/@tanstack/store/dist/index.d.ts","./node_modules/@tanstack/react-store/dist/usecreateatom.d.ts","./node_modules/@tanstack/react-store/dist/usecreatestore.d.ts","./node_modules/@tanstack/react-store/dist/useselector.d.ts","./node_modules/@tanstack/react-store/dist/useatom.d.ts","./node_modules/@tanstack/react-store/dist/_usestore.d.ts","./node_modules/@tanstack/react-store/dist/usestore.d.ts","./node_modules/@tanstack/react-store/dist/index.d.ts","./node_modules/@tanstack/pacer/dist/types.d.ts","./node_modules/@tanstack/pacer/dist/debouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedcallback.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedstate.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedvalue.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/index.d.ts","./src/utils/debounceconstants.ts","./src/app/(dashboard)/hooks/common/useresourcelist.ts","./src/app/(dashboard)/hooks/budgets/usebudgets.ts","./src/app/(dashboard)/hooks/caching/usecacheactivity.ts","./src/app/(dashboard)/hooks/caching/usecacheactivity.test.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/coordinationredis/usecoordinationredissettings.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/usekeyinfo.ts","./src/app/(dashboard)/hooks/keys/usekeys.test.ts","./src/app/(dashboard)/hooks/keys/useresetkeyspend.ts","./src/app/(dashboard)/hooks/keys/usesetkeyblockedstate.ts","./src/app/(dashboard)/hooks/keys/usesetkeyblockedstate.test.ts","./src/app/(dashboard)/hooks/license/uselicenseinfo.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.test.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.test.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/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/spendlogs/usespendlogendusers.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogendusers.test.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogusers.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogusers.test.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/useteammetadataschema.ts","./src/app/(dashboard)/hooks/teams/useteammetadataschema.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/useptucostattributionenabled.ts","./src/app/(dashboard)/hooks/uisettings/useptucostattributionenabled.test.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/userbanner/useuserbanner.ts","./src/app/(dashboard)/hooks/userbanner/useupdateuserbanner.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)/mcp-servers/_components/createoauthuistate.ts","./src/app/(dashboard)/mcp-servers/_components/createoauthuistate.test.ts","./src/app/(dashboard)/mcp-servers/_components/utils.tsx","./src/app/(dashboard)/mcp-servers/_components/createserverpayload.ts","./src/app/(dashboard)/mcp-servers/_components/createserverpayload.test.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.differential.cases.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.differential.test.ts","./src/app/(dashboard)/mcp-servers/_components/mcpfieldrules.ts","./src/app/(dashboard)/mcp-servers/_components/mcpfieldrules.test.ts","./src/app/(dashboard)/mcp-servers/_components/mcpformstore.ts","./src/app/(dashboard)/mcp-servers/_components/mountedserverfields.ts","./src/app/(dashboard)/mcp-servers/_components/mountedserverfields.test.ts","./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","./src/app/(dashboard)/mcp-servers/_components/testutils.ts","./src/app/(dashboard)/mcp-servers/_components/toolcallarguments.ts","./src/app/(dashboard)/mcp-servers/_components/toolcallarguments.test.ts","./node_modules/nuqs/dist/defs-butbdnwx.d.ts","./node_modules/nuqs/dist/context-3xask51n.d.ts","./node_modules/nuqs/dist/adapters/testing.d.ts","./node_modules/@standard-schema/spec/dist/index.d.ts","./node_modules/nuqs/dist/index.d.ts","./src/app/(dashboard)/models-and-endpoints/detailnavigation.ts","./src/app/(dashboard)/models-and-endpoints/detailnavigation.test.ts","./src/app/(dashboard)/models-and-endpoints/usemodeldashboarddata.ts","./src/components/add_model/auto_router_strategies.ts","./src/utils/modelpermissions.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterrows.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterrows.test.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/fitpills.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/fitpills.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/components/chat_ui/uploadvalidation.ts","./src/app/(dashboard)/playground/components/chat_ui/uploadvalidation.test.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/utils/promptcacheusage.ts","./src/components/chat_ui/responsemetrics.tsx","./src/components/chat_ui/types.ts","./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/components/policies/types.ts","./src/app/(dashboard)/policies/_components/build_attachment_data.ts","./src/app/(dashboard)/policies/_components/build_attachment_data.test.ts","./src/app/(dashboard)/policies/_components/scope_validation.ts","./src/app/(dashboard)/policies/_components/scope_validation.test.ts","./src/app/(dashboard)/projects/_components/projectmodals/projectformschema.ts","./src/components/shared/usepaginatedcombobox.ts","./src/components/shared/paginatedsearchselect.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/tagsinput.tsx","./src/components/agent_management/agentselector.tsx","./src/components/common_components/accessgroupselector.tsx","./src/components/common_components/budget_duration_dropdown.tsx","./src/components/common_components/keylifecyclesettings.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","./src/components/common_components/modelselector.tsx","./src/components/common_components/modelaliasmanager.tsx","./src/components/common_components/passthroughroutesselector.tsx","./src/components/callback_info_helpers.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","./src/components/common_components/team_dropdown.tsx","./src/components/common_components/organizationdropdown.tsx","./src/components/common_components/projectdropdown.tsx","./src/components/shared/form/formfield.tsx","./src/components/ui/alert.tsx","./src/components/shared/alert.tsx","./node_modules/@types/react-copy-to-clipboard/index.d.ts","./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/key_team_helpers/tagratelimiteditor.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/vectorstoreselector.tsx","./src/components/organisms/createkeypayload.ts","./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/app/(dashboard)/search-tools/_components/searchtoolpayload.ts","./src/app/(dashboard)/search-tools/_components/searchtoolpayload.test.ts","./src/app/(dashboard)/usage/_components/components/gatewayactivity.ts","./src/app/(dashboard)/usage/_components/components/gatewayactivity.test.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusageaggregations.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusagesummary.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusagesummary.test.ts","./src/app/(dashboard)/usage/_components/hooks/usepaginateddailyactivity.test.ts","./src/app/(dashboard)/users/_components/default-user-settings/schema.ts","./src/app/(dashboard)/users/_components/default-user-settings/mapper.ts","./src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts","./src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts","./src/components/key_scope.test.ts","./src/components/networking.test.ts","./src/components/page_metadata.ts","./src/contexts/themecontext.tsx","./src/components/ui/scroll-area.tsx","./src/components/shared/sidebar.tsx","./src/components/betabadge.tsx","./src/components/navbar/navdisplayname.ts","./src/components/shared/copybutton.tsx","./src/components/ui/avatar.tsx","./src/components/ui/popover.tsx","./src/components/sidebaraccountmenu/sidebaraccountmenu.tsx","./src/utils/licenseutils.ts","./src/components/sidebarusagecard.tsx","./src/components/leftnav.tsx","./src/components/page_utils.ts","./src/components/page_utils.test.ts","./src/components/cloudzerocosttracking/cloudzeropayload.ts","./src/components/cloudzerocosttracking/cloudzeropayload.test.ts","./src/utils/teamutils.ts","./src/components/shared/date_picker_types.ts","./src/components/entityusageexport/types.ts","./src/components/entityusageexport/exportformatselector.tsx","./src/components/entityusageexport/exportsummary.tsx","./src/components/entityusageexport/exporttypeselector.tsx","./node_modules/@types/papaparse/index.d.ts","./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.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/pluginsettings/schema.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/teamspage/teamscsvexport.ts","./src/components/teamspage/teamscsvexport.test.ts","./src/components/toolpolicies/toolpoliciesqueries.ts","./src/components/usagepage/utils/value_formatters.tsx","./src/components/usagepage/utils/value_formatters.test.ts","./src/components/add_model/build_auto_router_routing_test_request.ts","./src/components/add_model/build_auto_router_routing_test_request.test.ts","./src/components/add_model/build_auto_router_test_targets.ts","./src/components/add_model/build_auto_router_test_targets.test.ts","./src/components/add_model/build_complexity_router_config.test.ts","./src/components/add_model/classifierprompteditorstate.test.ts","./src/components/add_model/complexity_router_keywords.test.ts","./src/components/add_model/complexity_router_tiers.test.ts","./src/components/add_model/heuristic_scoring_knobs.test.ts","./src/components/add_model/tier_rows.test.ts","./src/components/chat/types.ts","./src/components/chat/usechathistory.ts","./src/contexts/chatshellcontext.tsx","./node_modules/dayjs/locale/types.d.ts","./node_modules/dayjs/locale/index.d.ts","./node_modules/dayjs/index.d.ts","./src/components/chat/conversationlist.tsx","./src/components/chat/chatshell.tsx","./src/components/chat/chatshell.serverrootpath.test.ts","./src/components/chat/usechathistory.test.ts","./src/components/claude_code_plugins/helpers.ts","./src/components/claude_code_plugins/helpers.test.ts","./src/components/common_components/formrules.ts","./src/components/common_components/routersettingspayload.ts","./src/components/common_components/routersettingspayload.test.ts","./node_modules/zod/v3/helpers/typealiases.d.cts","./node_modules/zod/v3/helpers/util.d.cts","./node_modules/zod/v3/zoderror.d.cts","./node_modules/zod/v3/locales/en.d.cts","./node_modules/zod/v3/errors.d.cts","./node_modules/zod/v3/helpers/parseutil.d.cts","./node_modules/zod/v3/helpers/enumutil.d.cts","./node_modules/zod/v3/helpers/errorutil.d.cts","./node_modules/zod/v3/helpers/partialutil.d.cts","./node_modules/zod/v3/standard-schema.d.cts","./node_modules/zod/v3/types.d.cts","./node_modules/zod/v3/external.d.cts","./node_modules/zod/v3/index.d.cts","./node_modules/@hookform/resolvers/zod/dist/zod.d.ts","./node_modules/@hookform/resolvers/zod/dist/index.d.ts","./src/lib/forms/usezodform.ts","./src/components/add_model/accessgrouptagscombobox.tsx","./src/components/add_model/modelchoicecombobox.tsx","./src/components/add_model/routerconfigbuilder.tsx","./src/components/edit_auto_router/edit_auto_router_modal.tsx","./src/components/edit_auto_router/build_updated_complexity_router_config.test.ts","./src/components/edit_auto_router/edit_auto_router_modal.test.ts","./src/components/email_events/email_event_settings.tsx","./src/components/email_events/index.ts","./src/components/guardrails/types.ts","./src/components/key_team_helpers/modelmaxbudgeteditor.test.ts","./src/components/key_team_helpers/filter_helpers.ts","./src/components/key_team_helpers/filter_helpers.test.ts","./src/components/key_team_helpers/modelmaxbudgetpayload.ts","./src/components/key_team_helpers/modelmaxbudgetpayload.test.ts","./src/components/key_team_helpers/transform_key_info.tsx","./src/components/key_team_helpers/transform_key_info.test.ts","./src/components/key_team_helpers/useseededstate.ts","./src/components/key_team_helpers/usemodelmaxbudgetfield.ts","./src/components/llm_calls/mcp_tool_blocks.ts","./src/components/llm_calls/mcp_tool_blocks.test.ts","./src/components/mcp_server_management/mcpentitlement.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/organisms/createkeypayload.test.ts","./src/components/organisms/regeneratekeypayload.ts","./src/components/organisms/regeneratekeypayload.test.ts","./src/components/organisms/utils.test.ts","./src/components/organization/org-settings/schema.ts","./src/components/organization/org-create/mapper.ts","./src/components/organization/org-create/mapper.test.ts","./src/components/organization/org-settings/mapper.ts","./src/components/organization/org-settings/mapper.test.ts","./src/components/routing_groups/routinggrouppayload.ts","./src/components/routing_groups/routinggrouppayload.test.ts","./src/components/routing_groups/strategy.ts","./src/components/shared/charts/colors.ts","./node_modules/@types/d3-time/index.d.ts","./node_modules/@types/d3-scale/index.d.ts","./node_modules/victory-vendor/d3-scale.d.ts","./node_modules/recharts/types/shape/dot.d.ts","./node_modules/recharts/types/component/text.d.ts","./node_modules/recharts/types/zindex/zindexlayer.d.ts","./node_modules/recharts/types/cartesian/getcartesianposition.d.ts","./node_modules/recharts/types/component/label.d.ts","./node_modules/recharts/types/cartesian/cartesianaxis.d.ts","./node_modules/recharts/types/util/scale/customscaledefinition.d.ts","./node_modules/redux/dist/redux.d.ts","./node_modules/immer/dist/immer.d.ts","./node_modules/redux-thunk/dist/redux-thunk.d.ts","./node_modules/@reduxjs/toolkit/dist/uncheckedindexed.ts","./node_modules/@reduxjs/toolkit/dist/index.d.mts","./node_modules/recharts/types/state/cartesianaxisslice.d.ts","./node_modules/recharts/types/synchronisation/types.d.ts","./node_modules/recharts/types/chart/types.d.ts","./node_modules/recharts/types/component/defaulttooltipcontent.d.ts","./node_modules/recharts/types/context/brushupdatecontext.d.ts","./node_modules/recharts/types/state/chartdataslice.d.ts","./node_modules/recharts/types/state/types/linesettings.d.ts","./node_modules/recharts/types/state/types/scattersettings.d.ts","./node_modules/@types/d3-path/index.d.ts","./node_modules/@types/d3-shape/index.d.ts","./node_modules/victory-vendor/d3-shape.d.ts","./node_modules/recharts/types/shape/curve.d.ts","./node_modules/recharts/types/component/labellist.d.ts","./node_modules/recharts/types/component/defaultlegendcontent.d.ts","./node_modules/recharts/types/util/payload/getuniqpayload.d.ts","./node_modules/recharts/types/util/useelementoffset.d.ts","./node_modules/recharts/types/component/legend.d.ts","./node_modules/recharts/types/state/legendslice.d.ts","./node_modules/recharts/types/state/types/stackedgraphicalitem.d.ts","./node_modules/recharts/types/util/stacks/stacktypes.d.ts","./node_modules/recharts/types/util/scale/rechartsscale.d.ts","./node_modules/recharts/types/util/chartutils.d.ts","./node_modules/recharts/types/state/selectors/areaselectors.d.ts","./node_modules/recharts/types/animation/easing.d.ts","./node_modules/recharts/types/animation/matchby.d.ts","./node_modules/recharts/types/animation/animateditems.d.ts","./node_modules/recharts/types/cartesian/arearevealshape.d.ts","./node_modules/recharts/types/cartesian/area.d.ts","./node_modules/recharts/types/state/types/areasettings.d.ts","./node_modules/recharts/types/shape/rectangle.d.ts","./node_modules/recharts/types/cartesian/bar.d.ts","./node_modules/recharts/types/util/barutils.d.ts","./node_modules/recharts/types/state/types/barsettings.d.ts","./node_modules/recharts/types/state/types/radialbarsettings.d.ts","./node_modules/recharts/types/util/svgpropertiesnoevents.d.ts","./node_modules/recharts/types/util/useuniqueid.d.ts","./node_modules/recharts/types/state/types/piesettings.d.ts","./node_modules/recharts/types/state/types/radarsettings.d.ts","./node_modules/recharts/types/state/graphicalitemsslice.d.ts","./node_modules/recharts/types/state/tooltipslice.d.ts","./node_modules/recharts/types/state/optionsslice.d.ts","./node_modules/recharts/types/state/layoutslice.d.ts","./node_modules/recharts/types/util/ifoverflow.d.ts","./node_modules/recharts/types/util/resolvedefaultprops.d.ts","./node_modules/recharts/types/cartesian/referenceline.d.ts","./node_modules/recharts/types/state/referenceelementsslice.d.ts","./node_modules/recharts/types/state/brushslice.d.ts","./node_modules/recharts/types/state/rootpropsslice.d.ts","./node_modules/recharts/types/state/polaraxisslice.d.ts","./node_modules/recharts/types/state/polaroptionsslice.d.ts","./node_modules/recharts/types/cartesian/linedrawshape.d.ts","./node_modules/recharts/types/cartesian/line.d.ts","./node_modules/recharts/types/shape/symbols.d.ts","./node_modules/recharts/types/util/constants.d.ts","./node_modules/recharts/types/util/scatterutils.d.ts","./node_modules/recharts/types/cartesian/scatter.d.ts","./node_modules/recharts/types/cartesian/errorbar.d.ts","./node_modules/recharts/types/state/errorbarslice.d.ts","./node_modules/recharts/types/state/zindexslice.d.ts","./node_modules/recharts/types/state/eventsettingsslice.d.ts","./node_modules/recharts/types/state/renderedticksslice.d.ts","./node_modules/recharts/types/state/store.d.ts","./node_modules/recharts/types/cartesian/getticks.d.ts","./node_modules/recharts/types/cartesian/cartesiangrid.d.ts","./node_modules/recharts/types/state/selectors/combiners/combinedisplayedstackeddata.d.ts","./node_modules/recharts/types/state/selectors/selecttooltipaxistype.d.ts","./node_modules/recharts/types/types.d.ts","./node_modules/recharts/types/hooks.d.ts","./node_modules/recharts/types/state/selectors/axisselectors.d.ts","./node_modules/recharts/types/component/dots.d.ts","./node_modules/recharts/types/util/typeddatakey.d.ts","./node_modules/recharts/types/util/types.d.ts","./node_modules/recharts/types/container/surface.d.ts","./node_modules/recharts/types/container/layer.d.ts","./node_modules/recharts/types/component/cursor.d.ts","./node_modules/recharts/types/component/tooltip.d.ts","./node_modules/recharts/types/component/responsivecontainer.d.ts","./node_modules/recharts/types/component/cell.d.ts","./node_modules/recharts/types/component/customized.d.ts","./node_modules/recharts/types/shape/sector.d.ts","./node_modules/recharts/types/shape/polygon.d.ts","./node_modules/recharts/types/shape/cross.d.ts","./node_modules/recharts/types/polar/polargrid.d.ts","./node_modules/recharts/types/polar/defaultpolarradiusaxisprops.d.ts","./node_modules/recharts/types/polar/polarradiusaxis.d.ts","./node_modules/recharts/types/polar/defaultpolarangleaxisprops.d.ts","./node_modules/recharts/types/polar/polarangleaxis.d.ts","./node_modules/recharts/types/context/tooltipcontext.d.ts","./node_modules/recharts/types/polar/pie.d.ts","./node_modules/recharts/types/polar/radar.d.ts","./node_modules/recharts/types/util/radialbarutils.d.ts","./node_modules/recharts/types/polar/radialbar.d.ts","./node_modules/recharts/types/cartesian/brush.d.ts","./node_modules/recharts/types/cartesian/referencedot.d.ts","./node_modules/recharts/types/util/excludeeventprops.d.ts","./node_modules/recharts/types/util/svgpropertiesandevents.d.ts","./node_modules/recharts/types/cartesian/referencearea.d.ts","./node_modules/recharts/types/cartesian/barstack.d.ts","./node_modules/recharts/types/cartesian/xaxis.d.ts","./node_modules/recharts/types/cartesian/yaxis.d.ts","./node_modules/recharts/types/cartesian/zaxis.d.ts","./node_modules/recharts/types/chart/linechart.d.ts","./node_modules/recharts/types/chart/barchart.d.ts","./node_modules/recharts/types/chart/piechart.d.ts","./node_modules/recharts/types/chart/treemap.d.ts","./node_modules/recharts/types/chart/sankey.d.ts","./node_modules/recharts/types/chart/radarchart.d.ts","./node_modules/recharts/types/chart/scatterchart.d.ts","./node_modules/recharts/types/chart/areachart.d.ts","./node_modules/recharts/types/chart/radialbarchart.d.ts","./node_modules/recharts/types/chart/composedchart.d.ts","./node_modules/recharts/types/chart/sunburstchart.d.ts","./node_modules/recharts/types/shape/trapezoid.d.ts","./node_modules/recharts/types/cartesian/funnel.d.ts","./node_modules/recharts/types/chart/funnelchart.d.ts","./node_modules/recharts/types/util/global.d.ts","./node_modules/recharts/types/animation/animationhandle.d.ts","./node_modules/recharts/types/animation/timeoutcontroller.d.ts","./node_modules/recharts/types/animation/animationcontroller.d.ts","./node_modules/recharts/types/animation/useanimationcontroller.d.ts","./node_modules/recharts/types/zindex/defaultzindexes.d.ts","./node_modules/decimal.js-light/decimal.d.ts","./node_modules/recharts/types/util/scale/getnicetickvalues.d.ts","./node_modules/recharts/types/context/chartlayoutcontext.d.ts","./node_modules/recharts/types/util/getrelativecoordinate.d.ts","./node_modules/recharts/types/util/createcartesiancharts.d.ts","./node_modules/recharts/types/util/createpolarcharts.d.ts","./node_modules/recharts/types/util/datautils.d.ts","./node_modules/recharts/types/index.d.ts","./src/components/ui/chart.tsx","./src/components/shared/charts/chart_tooltip.tsx","./src/components/shared/charts/area_chart.tsx","./src/components/shared/charts/bar_chart.tsx","./src/components/shared/charts/chart_legend.tsx","./src/components/shared/charts/donut_chart.tsx","./src/components/shared/charts/line_chart.tsx","./src/components/shared/charts/index.ts","./src/components/team/memberformvalues.ts","./src/components/team/memberformvalues.test.ts","./src/components/team/tabvisibilityutils.ts","./src/components/team/tabvisibilityutils.test.ts","./src/components/team/teammodelaccess.ts","./src/components/team/teammodelaccess.test.ts","./src/components/team/usemyteammember.ts","./src/components/templates/estimatedoutputtokens.ts","./src/components/templates/estimatedoutputtokens.test.ts","./src/components/templates/keyeditfieldnormalizers.ts","./src/components/key_info_utils.tsx","./src/components/templates/keyeditformvalues.ts","./src/components/templates/keyeditformvalues.test.ts","./src/components/view_logs/constants.ts","./src/components/view_logs/logdetailrouting.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/columns.tsx","./src/components/view_logs/logdetailsdrawer/classifytag.tsx","./node_modules/moment/ts3.1-typings/moment.d.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/logdetailsdrawer/utils.test.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/usevisitedtabs.ts","./src/hooks/useworker.ts","./src/hooks/policies/usedeletepolicyattachment.ts","./src/lib/assetpaths.test.ts","./src/autorouter_presets.json","./src/lib/autorouter_presets.ts","./src/lib/autorouter_presets.test.ts","./src/lib/cva.config.test.ts","./src/lib/toast.test.ts","./src/lib/forms/pickdirty.ts","./src/lib/forms/pickdirty.test.ts","./src/lib/forms/urlvalidation.ts","./src/lib/forms/urlvalidation.test.ts","./src/lib/http/api.sameorigin.test.ts","./src/lib/http/api.test.ts","./src/lib/http/client.test.ts","./src/lib/http/resolveapibase.test.ts","./src/lib/http/runtime.test.ts","./src/utils/budgetutils.ts","./src/utils/capabilities.test.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/constants.d.ts","./node_modules/date-fns/locale/types.d.ts","./node_modules/date-fns/fp/types.d.ts","./node_modules/date-fns/types.d.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.ts","./src/utils/keyexpiryutils.ts","./src/utils/keyexpiryutils.test.ts","./src/utils/keyupdateutils.ts","./src/utils/keyupdateutils.test.ts","./src/utils/licenseutils.test.ts","./src/utils/localstorageutils.test.ts","./src/utils/maskedsecretutils.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/modelpermissions.test.ts","./src/utils/pkce.ts","./src/utils/promptcacheusage.test.ts","./src/utils/proxyutils.test.ts","./node_modules/dayjs/plugin/utc.d.ts","./src/utils/ptudatetime.ts","./src/utils/ptudatetime.test.ts","./src/utils/ptuvalidation.ts","./src/utils/ptumodelinfo.ts","./src/utils/ptumodelinfo.test.ts","./src/utils/ptuvalidation.test.ts","./src/utils/returnurlutils.test.ts","./src/utils/roles.test.ts","./src/utils/tabroutes.test.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/vitest/dist/environments.d.ts","./tests/jsdomfetchenv.ts","./scripts/lint-budget-lib.mjs","./tests/lint-budget-lib.test.ts","./tests/setup.unit.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","./scripts/eslint-rules/filename-pascal-case.mjs","./tests/eslint-rules/filename-pascal-case.test.ts","./scripts/eslint-rules/no-ad-hoc-z-index.mjs","./tests/eslint-rules/no-ad-hoc-z-index.test.ts","./scripts/eslint-rules/no-complex-jsx-arrow.mjs","./tests/eslint-rules/no-complex-jsx-arrow.test.ts","./scripts/eslint-rules/no-large-inline-object-arg.mjs","./tests/eslint-rules/no-large-inline-object-arg.test.ts","./scripts/eslint-rules/no-long-condition-chain.mjs","./tests/eslint-rules/no-long-condition-chain.test.ts","./scripts/eslint-rules/no-noop-hover-variant.mjs","./tests/eslint-rules/no-noop-hover-variant.test.ts","./tests/mocks/complexityscorerdefaults.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","./node_modules/nuqs/dist/adapters/next/app.d.ts","./src/contexts/authcontext.tsx","./src/contexts/reactqueryprovider.tsx","./src/components/ui/sonner.tsx","./src/app/layout.tsx","./src/components/ui/breadcrumb.tsx","./src/components/shared/toolbarseparator.tsx","./src/components/navbar/blogdropdown/blogdropdown.tsx","./src/components/ui/button-group.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.tsx","./src/components/navbar/notificationsbell/notificationsbell.tsx","./src/contexts/pluginmodecontext.tsx","./src/components/navbar/viewswitcher.tsx","./src/components/themetoggle/themetoggle.tsx","./src/components/navbar/workerdropdown/workerdropdown.tsx","./src/components/dashboardheader.tsx","./src/components/navbar/userdropdown/userdropdown.tsx","./src/components/navbar.tsx","./src/components/common_components/loadingscreen.tsx","./src/app/(dashboard)/components/sidebarprovider.tsx","./src/components/debugwarningbanner.tsx","./src/components/norediswarningbanner.tsx","./src/components/licenseexpirybanner.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","./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/userbanner.tsx","./src/app/(dashboard)/layout.tsx","./src/app/(dashboard)/agentcontrolplaneview.test.tsx","./src/app/(dashboard)/layout.test.tsx","./src/components/common_components/fetch_teams.tsx","./src/components/shared/pageheader.tsx","./src/app/(dashboard)/hooks/useteams.tsx","./src/components/ui/hover-card.tsx","./src/components/common_components/defaultproxyadmintag.tsx","./src/components/common_components/labeledfield.tsx","./src/components/templates/keyinfoheader.tsx","./src/components/shared/advanced_date_picker.tsx","./src/components/shared/summarycard.tsx","./src/components/shared/savingstiles.tsx","./src/components/templates/keysavingstab.tsx","./src/components/common_components/autorotationview.tsx","./src/components/common_components/deleteresourcemodal.tsx","./src/components/common_components/routersettingssummary.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/shared/errorutils.tsx","./src/components/guardrails/guardrailselector.tsx","./src/components/policies/policyselector.tsx","./src/components/templates/keyeditviewcontrols.tsx","./src/components/team/editloggingsettings.tsx","./src/components/templates/key_edit_view.tsx","./src/components/templates/key_info_view.tsx","./src/components/virtualkeyspage/keytablecolumns.tsx","./src/components/virtualkeyspage/virtualkeystable.tsx","./src/components/user_dashboard.tsx","./src/app/(dashboard)/api-keys/apikeysdashboard.tsx","./src/app/(dashboard)/page.tsx","./src/app/(dashboard)/page.test.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/access-group-create/accessgroupcreatedialog.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupstablecolumns.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupstable.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupspage.tsx","./src/app/(dashboard)/access-groups/page.tsx","./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)/access-groups/_components/accessgroupsmodal/accessgroupeditmodal.integration.test.tsx","./src/app/(dashboard)/access-groups/_components/access-group-create/accessgroupcreatedialog.test.tsx","./src/components/constants.tsx","./src/components/scim.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.tsx","./src/components/shared/passwordinput.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/userbannersettings/userbannersettings.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/app/(dashboard)/admin-panel/_components/adminpanel.tsx","./src/app/(dashboard)/admin-panel/page.tsx","./src/app/(dashboard)/admin-panel/_components/adminpanel.test.tsx","./src/app/(dashboard)/agents/_components/agentformkit.tsx","./src/app/(dashboard)/agents/_components/cost_config_fields.tsx","./src/app/(dashboard)/agents/_components/agent_form_fields.tsx","./src/app/(dashboard)/agents/_components/agent_card_discovery.tsx","./src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.tsx","./src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx","./src/app/(dashboard)/agents/_components/agent_cost_view.tsx","./src/app/(dashboard)/agents/_components/agent_info.tsx","./src/app/(dashboard)/agents/_components/agentstablecolumns.tsx","./src/app/(dashboard)/agents/_components/agentstable.tsx","./src/app/(dashboard)/agents/_components/agentspanel.tsx","./src/app/(dashboard)/agents/page.tsx","./src/app/(dashboard)/agents/_components/agentspanel.test.tsx","./src/app/(dashboard)/agents/_components/agentstable.test.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.integration.test.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.test.tsx","./src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx","./src/app/(dashboard)/agents/_components/agent_cost_view.test.tsx","./src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx","./src/app/(dashboard)/agents/_components/agent_info.test.tsx","./src/app/(dashboard)/agents/_components/agent_virtual_keys.test.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/_components/apireferenceview.tsx","./src/components/deprecationbanner.tsx","./src/app/(dashboard)/api-reference/page.tsx","./src/app/(dashboard)/api-reference/_components/apireferenceview.test.tsx","./src/app/(dashboard)/budgets/_components/budget_modal.tsx","./src/app/(dashboard)/budgets/_components/budgettablecolumns.tsx","./src/app/(dashboard)/budgets/_components/budgettable.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/budgettable.test.tsx","./src/app/(dashboard)/budgets/_components/budget_modal.integration.test.tsx","./src/app/(dashboard)/budgets/_components/budget_panel.test.tsx","./src/app/(dashboard)/budgets/_components/edit_budget_modal.integration.test.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/coordination_redis_settings/coordinationredisformfield.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisfieldsection.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredistypeselector.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.tsx","./src/app/(dashboard)/caching/_components/errordrilldown.tsx","./src/app/(dashboard)/caching/_components/cache_dashboard.tsx","./src/app/(dashboard)/caching/page.tsx","./src/app/(dashboard)/caching/_components/errordrilldown.test.tsx","./src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx","./src/app/(dashboard)/caching/_components/cache_health.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/redistypeselector.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/index.integration.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredistypeselector.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.integration.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.test.tsx","./src/components/shared/paginationstatusalerts.tsx","./src/app/(dashboard)/cost-optimization/_components/usagetab.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcompressiontab.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/molecules/models/providerlogo.tsx","./src/components/settings/routersettings/fallbacks/editfallbacks.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.tsx","./src/components/routing_groups/routinggroupusagepanel.tsx","./src/components/routing_groups/routinggroupstablecolumns.tsx","./src/components/routing_groups/routinggroupstable.tsx","./src/components/routing_groups/routinggroupmodal.tsx","./src/components/routing_groups/index.tsx","./src/app/(dashboard)/router-settings/_components/general_settings.tsx","./src/app/(dashboard)/cost-optimization/_components/cacheleakagecard.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcachingtab.tsx","./src/components/shared/paginatedmultiselect.tsx","./src/app/(dashboard)/cost-optimization/_components/shadowevalsection.tsx","./src/app/(dashboard)/cost-optimization/_components/tierturnschart.tsx","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarkstab.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.tsx","./src/app/(dashboard)/cost-optimization/page.tsx","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarkstab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/cacheleakagecard.test.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.activity.test.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.test.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcachingtab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcompressiontab.integration.test.tsx","./src/app/(dashboard)/cost-optimization/_components/shadowevalsection.test.tsx","./src/app/(dashboard)/cost-optimization/_components/tierturnschart.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usagetab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.integration.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.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.integration.test.tsx","./src/app/(dashboard)/cost-tracking/_components/add_provider_form.test.tsx","./src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.integration.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/app/(dashboard)/guardrails/_components/content_filter/patternmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/custompatternmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/patterntable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordtable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentcategoryconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/thresholdinput.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/competitorintentconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/guardrailformfield.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.tsx","./src/app/(dashboard)/guardrails/_components/llm_judge/llmjudgefields.tsx","./src/app/(dashboard)/guardrails/_components/pii_components.tsx","./src/app/(dashboard)/guardrails/_components/pii_configuration.tsx","./src/app/(dashboard)/guardrails/_components/tool_permission/toolpermissionruleseditor.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtablecolumns.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_table.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/categorytable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterdisplay.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltermanager.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestresults.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestpanel.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestplayground.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_card.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_detail.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.tsx","./src/app/(dashboard)/guardrails/_components/guardrailspanel.tsx","./src/app/(dashboard)/guardrails/page.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestpanel.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestplayground.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestresults.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailspanel.test.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.integration.test.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.test.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.characterization.test.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_card.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_detail.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.characterization.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx","./src/app/(dashboard)/guardrails/_components/pii_components.test.tsx","./src/app/(dashboard)/guardrails/_components/pii_configuration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/competitorintentconfiguration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterconfiguration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterdisplay.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltermanager.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltertables.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/custompatternmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/patternmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/tagsinput.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/thresholdinput.test.tsx","./src/app/(dashboard)/guardrails/_components/custom_code/customcodemodal.test.tsx","./src/app/(dashboard)/guardrails/_components/tool_permission/toolpermissionruleseditor.test.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/components/shared/adminonlynotice.tsx","./src/app/(dashboard)/guardrails-monitor/page.tsx","./src/app/(dashboard)/guardrails-monitor/page.integration.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/evaluationsettingsmodal.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailconfig.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailconfig.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardraildetail.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailsmonitorview.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailsoverview.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/scorechart.test.tsx","./src/app/(dashboard)/hooks/usetabrouting.test.tsx","./src/app/(dashboard)/hooks/common/useresourcelist.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/cloudzeroformcontrols.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/loggingcallbackstablecolumns.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.tsx","./src/components/settings.tsx","./src/app/(dashboard)/logging-and-alerts/page.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystablecolumns.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.tsx","./src/components/deletedkeyspage/deletedkeyspage.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstablecolumns.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.tsx","./src/components/deletedteamspage/deletedteamspage.tsx","./src/components/view_logs/auditlogstablecolumns.tsx","./src/components/view_logs/auditlogstable.tsx","./src/components/view_logs/auditlogdrawer/auditlogdrawer.tsx","./src/components/view_logs/auditlogspanel.tsx","./src/components/view_logs/log_filter_logic.tsx","./src/components/view_logs/logs_utils.tsx","./src/components/view_logs/logstabletoolbar.tsx","./src/components/view_logs/requestlogsfilters.tsx","./src/components/view_logs/typebadges.tsx","./src/components/view_logs/requestlogstablecolumns.tsx","./src/components/view_logs/requestlogstable.tsx","./src/components/view_logs/requestlogspanel.tsx","./src/components/view_logs/index.tsx","./src/app/(dashboard)/logs/page.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpstandardssettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpsubmissionstab.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsettablecolumns.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsetstab.tsx","./src/app/(dashboard)/mcp-servers/_components/awssigv4fields.tsx","./src/app/(dashboard)/mcp-servers/_components/openapibyokfields.tsx","./src/app/(dashboard)/mcp-servers/_components/tokenendpointauthmethodfield.tsx","./src/app/(dashboard)/mcp-servers/_components/oauthformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/truepassthroughwarning.tsx","./src/app/(dashboard)/mcp-servers/_components/dcrbridgetoggle.tsx","./src/app/(dashboard)/mcp-servers/_components/passthroughauthorizesection.tsx","./src/app/(dashboard)/mcp-servers/_components/tokenexchangeformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/idjagformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_config.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connection_status.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tool_configuration.tsx","./src/app/(dashboard)/mcp-servers/_components/stdioconfiguration.tsx","./src/app/(dashboard)/mcp-servers/_components/mcppermissionmanagement.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiquickpicker.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiformsection.tsx","./src/app/(dashboard)/mcp-servers/_components/mcplogoselector.tsx","./src/app/(dashboard)/mcp-servers/_components/envvarssection.tsx","./src/hooks/usemcpoauthflow.tsx","./src/hooks/usetestmcpconnection.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connect.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpservercard.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_display.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpnetworksettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_discovery.tsx","./src/components/mcp_tools/byokcredentialmodal.tsx","./src/app/(dashboard)/mcp-servers/_components/userenvvarsmodal.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx","./src/app/(dashboard)/mcp-servers/_components/toolargumentsform.tsx","./src/app/(dashboard)/mcp-servers/_components/tooltestpanel.tsx","./src/hooks/usetoolsoauthflow.tsx","./src/hooks/useusermcpoauthflow.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx","./src/app/(dashboard)/mcp-servers/_components/index.tsx","./src/app/(dashboard)/mcp-servers/page.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.permissions.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/envvarssection.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcplogoselector.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpnetworksettings.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpformtestharness.tsx","./src/app/(dashboard)/mcp-servers/_components/mcppermissionmanagement.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpservercard.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpstandardssettings.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsettablecolumns.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsetstab.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/oauthformfields.test.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiquickpicker.test.tsx","./src/app/(dashboard)/mcp-servers/_components/passthroughauthorizesection.test.tsx","./src/app/(dashboard)/mcp-servers/_components/tooltestpanel.test.tsx","./src/app/(dashboard)/mcp-servers/_components/truepassthroughwarning.test.tsx","./src/app/(dashboard)/mcp-servers/_components/userenvvarsmodal.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpformstore.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connect.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connection_status.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_discovery.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_config.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_display.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_view.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_servers.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tool_configuration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tools.test.tsx","./src/app/(dashboard)/mcp-servers/_components/utils.test.tsx","./src/app/(dashboard)/memory/_components/memorydetaildrawer.tsx","./src/app/(dashboard)/memory/_components/memoryeditmodal.tsx","./src/app/(dashboard)/memory/_components/memorytablecolumns.tsx","./src/app/(dashboard)/memory/_components/memorytable.tsx","./src/app/(dashboard)/memory/_components/memoryview.tsx","./src/app/(dashboard)/memory/page.tsx","./src/app/(dashboard)/memory/page.integration.test.tsx","./src/app/(dashboard)/memory/_components/memorydetaildrawer.test.tsx","./src/app/(dashboard)/memory/_components/memoryeditmodal.integration.test.tsx","./src/app/(dashboard)/memory/_components/memorytable.test.tsx","./src/app/(dashboard)/memory/_components/memoryview.test.tsx","./src/components/aihub/agenthubtablecolumns.tsx","./src/components/aihub/forms/makeagentpublicform.tsx","./src/components/aihub/mcphubtablecolumns.tsx","./src/components/aihub/forms/makemcppublicform.tsx","./src/components/model_filters.tsx","./src/components/aihub/forms/makemodelpublicform.tsx","./src/components/aihub/modelhubtablecolumns.tsx","./src/components/common_components/iconactionbutton/baseactionbutton.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.tsx","./src/components/aihub/usefullinksmanagement.tsx","./src/components/aihub/skillhubtablecolumns.tsx","./src/components/claude_code_plugins/skill_detail.tsx","./src/components/aihub/skillhubdashboard.tsx","./src/components/claude_code_plugins/makeskillpublicform.tsx","./src/components/publicmodelhubtablecolumns.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/molecules/cost_optimization_feedback_banner.tsx","./src/components/add_model/auto_router_connection_test.tsx","./src/components/model_add/reuse_credentials.tsx","./src/components/update_model_credentials_modal.tsx","./src/components/shared/form/utcdatetimeinput.tsx","./src/components/add_model/cache_control_settings.tsx","./src/components/modelinfoeditform.tsx","./src/components/view_model/model_name_display.tsx","./src/components/model_info_view.tsx","./src/components/common_components/user_search_modal.tsx","./src/components/shared/form/labelwithhint.tsx","./src/components/team/guardrailsselect.tsx","./src/components/common_components/metadatakeyvaluefields.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/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelstablecolumns.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstable.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/allmodelspanel.tsx","./src/components/add_model/handle_add_auto_router_submit.tsx","./src/components/add_model/autorouterroutingtest.tsx","./src/components/add_model/add_auto_router_tab.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterstablecolumns.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterstable.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterspanel.tsx","./src/app/(dashboard)/models-and-endpoints/panels/autorouterstabpanel.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/handle_add_model_submit.tsx","./src/components/add_model/model_connection_test.tsx","./src/components/add_model/provider_specific_fields.tsx","./src/components/add_model/add_model_modes.tsx","./src/components/add_model/addmodelform.tsx","./src/app/(dashboard)/models-and-endpoints/panels/addmodelpanel.tsx","./src/components/model_add/credentialmodal.tsx","./src/components/model_add/credentialstablecolumns.tsx","./src/components/model_add/credentialstable.tsx","./src/components/model_add/credentialspanel.tsx","./src/app/(dashboard)/models-and-endpoints/panels/llmcredentialspanel.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/passthroughsettings/passthroughendpointstablecolumns.tsx","./src/components/passthroughsettings/passthroughendpointstable.tsx","./src/components/passthroughsettings/passthroughsettings.tsx","./src/app/(dashboard)/models-and-endpoints/panels/passthroughpanel.tsx","./src/components/model_dashboard/healthcheckstablecolumns.tsx","./src/components/model_dashboard/healthcheckstable.tsx","./src/components/model_dashboard/healthcheckcomponent.tsx","./src/app/(dashboard)/models-and-endpoints/panels/healthstatuspanel.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/modelretrysettingspanel.tsx","./src/components/model_group_alias_settings.tsx","./src/app/(dashboard)/models-and-endpoints/panels/modelgroupaliaspanel.tsx","./src/components/price_data_reload.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/pricedatapanel.tsx","./src/app/(dashboard)/models-and-endpoints/page.tsx","./src/app/(dashboard)/models-and-endpoints/page.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstable.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterspanel.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/addmodelpanel.integration.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/healthstatuspanel.test.tsx","./src/components/view_user_spend.tsx","./src/components/usagepage/components/entityusage/topkeyview.tsx","./src/app/(dashboard)/old-usage/_components/usage.tsx","./src/app/(dashboard)/old-usage/page.tsx","./src/app/(dashboard)/old-usage/_components/usage.test.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/org-settings/orgsettingsform.tsx","./src/components/organization/org-create/orgcreatedialog.tsx","./src/components/shared/badgelink.tsx","./src/components/organization/organization_view.tsx","./src/app/(dashboard)/organizations/_components/organizationstablecolumns.tsx","./src/app/(dashboard)/organizations/_components/organizationstable.tsx","./src/app/(dashboard)/organizations/_components/organizationspanel.tsx","./src/app/(dashboard)/organizations/page.tsx","./src/app/(dashboard)/organizations/_components/organizationspanel.test.tsx","./src/app/(dashboard)/organizations/_components/organizationstable.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/additionalmodelsettings.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatcomposer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.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/endpointutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.tsx","./src/app/(dashboard)/playground/components/chat_ui/a2ametrics.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.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/page.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/agentbuilderview.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatcomposer.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.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/realtimeplayground.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/anthropic_messages.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)/policies/_components/policytablecolumns.tsx","./src/app/(dashboard)/policies/_components/policytable.tsx","./src/app/(dashboard)/policies/_components/pipeline_flow_builder.tsx","./src/app/(dashboard)/policies/_components/policy_info.tsx","./src/app/(dashboard)/policies/_components/add_policy_form.tsx","./src/app/(dashboard)/policies/_components/impact_popover.tsx","./src/app/(dashboard)/policies/_components/attachmenttablecolumns.tsx","./src/app/(dashboard)/policies/_components/attachmenttable.tsx","./src/app/(dashboard)/policies/_components/impact_preview_alert.tsx","./src/app/(dashboard)/policies/_components/tokenselect.tsx","./src/app/(dashboard)/policies/_components/add_attachment_form.tsx","./src/app/(dashboard)/policies/_components/policy_test_panel.tsx","./src/app/(dashboard)/policies/_components/policy_templates.tsx","./src/app/(dashboard)/policies/_components/guardrail_selection_modal.tsx","./src/app/(dashboard)/policies/_components/template_parameter_modal.tsx","./src/app/(dashboard)/policies/_components/ai_suggestion_modal.tsx","./src/app/(dashboard)/policies/_components/index.tsx","./src/app/(dashboard)/policies/page.tsx","./src/app/(dashboard)/policies/_components/attachmenttable.test.tsx","./src/app/(dashboard)/policies/_components/policytable.test.tsx","./src/app/(dashboard)/policies/_components/add_attachment_form.test.tsx","./src/app/(dashboard)/policies/_components/add_policy_form.test.tsx","./src/app/(dashboard)/policies/_components/ai_suggestion_modal.test.tsx","./src/app/(dashboard)/policies/_components/guardrail_selection_modal.test.tsx","./src/app/(dashboard)/policies/_components/impact_popover.test.tsx","./src/app/(dashboard)/policies/_components/impact_preview_alert.test.tsx","./src/app/(dashboard)/policies/_components/index.test.tsx","./src/app/(dashboard)/policies/_components/pipeline_flow_builder.test.tsx","./src/app/(dashboard)/policies/_components/policy_info.test.tsx","./src/app/(dashboard)/policies/_components/policy_templates.test.tsx","./src/app/(dashboard)/policies/_components/policy_test_panel.test.tsx","./src/app/(dashboard)/policies/_components/template_parameter_modal.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.tsx","./src/app/(dashboard)/projects/_components/projectmodals/editprojectmodal.tsx","./src/app/(dashboard)/projects/_components/projectkeystablecolumns.tsx","./src/app/(dashboard)/projects/_components/projectkeystable.tsx","./src/app/(dashboard)/projects/_components/projectkeyssection.tsx","./src/app/(dashboard)/projects/_components/projectdetailspage.tsx","./src/app/(dashboard)/projects/_components/projectstablecolumns.tsx","./src/app/(dashboard)/projects/_components/projectstable.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/projectkeyssection.test.tsx","./src/app/(dashboard)/projects/_components/projectkeystable.test.tsx","./src/app/(dashboard)/projects/_components/projectspage.test.tsx","./src/app/(dashboard)/projects/_components/projectstable.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.integration.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/editprojectmodal.integration.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/prompttablecolumns.tsx","./src/app/(dashboard)/prompts/_components/prompttable.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/prompttable.test.tsx","./src/app/(dashboard)/prompts/_components/add_prompt_form.integration.test.tsx","./src/app/(dashboard)/prompts/_components/index.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_info.test.tsx","./src/app/(dashboard)/prompts/_components/tool_modal.test.tsx","./src/app/(dashboard)/prompts/_components/variable_textarea.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/developermessagecard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/modelconfigcard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/promptcodesnippets.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/prompteditorheader.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/promptmessagescard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/publishmodal.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/toolscard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/versionhistorysidepanel.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/emptystate.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messagebubble.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messageinput.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messagelist.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/variableinput.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.test.tsx","./src/app/(dashboard)/router-settings/page.tsx","./src/app/(dashboard)/router-settings/_components/general_settings.test.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/searchtooltablecolumns.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltable.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/createsearchtools.integration.test.tsx","./src/app/(dashboard)/search-tools/_components/createsearchtools.test.tsx","./src/app/(dashboard)/search-tools/_components/searchconnectiontest.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltable.test.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.integration.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtools.test.tsx","./src/app/(dashboard)/skills/_components/add_plugin_form.tsx","./src/app/(dashboard)/skills/_components/plugintablecolumns.tsx","./src/app/(dashboard)/skills/_components/plugintable.tsx","./src/app/(dashboard)/skills/_components/claudecodepluginspanel.tsx","./src/app/(dashboard)/skills/page.tsx","./src/app/(dashboard)/skills/_components/claudecodepluginspanel.test.tsx","./src/app/(dashboard)/skills/_components/plugintable.test.tsx","./src/app/(dashboard)/skills/_components/add_plugin_form.test.tsx","./src/app/(dashboard)/tag-management/_components/tag_info.tsx","./src/app/(dashboard)/tag-management/_components/tagtablecolumns.tsx","./src/app/(dashboard)/tag-management/_components/tagtable.tsx","./src/app/(dashboard)/tag-management/_components/components/createtagmodal.tsx","./src/app/(dashboard)/tag-management/_components/index.tsx","./src/app/(dashboard)/tag-management/page.tsx","./src/app/(dashboard)/tag-management/_components/tagtable.test.tsx","./src/app/(dashboard)/tag-management/_components/index.test.tsx","./src/app/(dashboard)/tag-management/_components/tag_info.integration.test.tsx","./src/app/(dashboard)/tag-management/_components/components/createtagmodal.test.tsx","./src/components/team/availableteamstablecolumns.tsx","./src/components/team/availableteamstable.tsx","./src/components/team/availableteamspanel.tsx","./src/components/teamssosettings.tsx","./src/components/teamspage/teamtablecolumns.tsx","./src/components/teamspage/teamstable.tsx","./src/components/teams.tsx","./src/app/(dashboard)/teams/page.tsx","./src/components/toolpolicies/policyselect.tsx","./src/components/tooldetail.tsx","./src/components/toolpolicies/toolpoliciestablecolumns.tsx","./src/components/toolpolicies/toolpoliciestable.tsx","./src/components/toolpolicies/toolpoliciespanel.tsx","./src/components/toolpoliciesview.tsx","./src/app/(dashboard)/tool-policies/page.tsx","./src/app/(dashboard)/transform-request/transformrequestpanel.tsx","./src/app/(dashboard)/transform-request/transformrequestpanel.test.tsx","./src/app/(dashboard)/transform-request/page.tsx","./src/app/(dashboard)/ui-theme/uithemesettings.tsx","./src/app/(dashboard)/ui-theme/uithemesettings.test.tsx","./src/app/(dashboard)/ui-theme/page.tsx","./src/components/usagepage/components/keymodelusageview.tsx","./src/components/activity_metrics.tsx","./src/components/cloudzero_export_modal.tsx","./src/components/common_components/userdropdown.tsx","./src/components/shared/chart_loader.tsx","./src/components/per_user_usage.tsx","./src/components/user_agent_activity.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagebarchart.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagelinechart.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagetable.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/endpointusage.tsx","./src/components/common_components/team_multi_select.tsx","./src/app/(dashboard)/usage/_components/components/modelviewtoggle.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/topmodelview.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/entityusage.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/spendbyprovider.tsx","./src/app/(dashboard)/usage/_components/components/usageaichatpanel.tsx","./src/app/(dashboard)/usage/_components/components/usageviewselect/usageviewselect.tsx","./src/app/(dashboard)/usage/_components/components/usagepageview.tsx","./src/app/(dashboard)/usage/page.tsx","./src/app/(dashboard)/usage/_components/components/usageaichatpanel.test.tsx","./src/app/(dashboard)/usage/_components/components/usagepageview.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/endpointusage.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagebarchart.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagelinechart.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagetable.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/entityusage.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/spendbyprovider.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/topmodelview.test.tsx","./src/app/(dashboard)/usage/_components/components/usageviewselect/usageviewselect.test.tsx","./src/app/(dashboard)/users/_components/user_edit_view.tsx","./src/app/(dashboard)/users/_components/bulkeditusers.tsx","./src/components/bulk_create_users_button.tsx","./src/app/(dashboard)/users/_components/default-user-settings/defaultusersettingsform.tsx","./src/app/(dashboard)/users/_components/view_users/userstablecolumns.tsx","./src/app/(dashboard)/users/_components/view_users/userstable.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.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/user_edit_view.test.tsx","./src/app/(dashboard)/users/_components/view_users.test.tsx","./src/app/(dashboard)/users/_components/default-user-settings/defaultusersettingsform.test.tsx","./src/app/(dashboard)/users/_components/view_users/userstable.test.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.integration.test.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.test.tsx","./src/components/vector_store_providers.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretable.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretester.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.tsx","./src/app/(dashboard)/vector-stores/_components/documentstablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/documentstable.tsx","./src/app/(dashboard)/vector-stores/_components/s3vectorsconfig.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.tsx","./src/app/(dashboard)/vector-stores/_components/testvectorstoretab.tsx","./src/app/(dashboard)/vector-stores/_components/index.tsx","./src/app/(dashboard)/vector-stores/page.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.characterization.test.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.test.tsx","./src/app/(dashboard)/vector-stores/_components/documentstable.test.tsx","./src/app/(dashboard)/vector-stores/_components/indexestable.test.tsx","./src/app/(dashboard)/vector-stores/_components/s3vectorsconfig.test.tsx","./src/app/(dashboard)/vector-stores/_components/testvectorstoretab.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.integration.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretable.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretester.test.tsx","./src/app/(dashboard)/vector-stores/_components/index.test.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.integration.test.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.test.tsx","./src/app/(dashboard)/workflows/workflowruns.tsx","./src/app/(dashboard)/workflows/workflowruns.test.tsx","./src/app/(dashboard)/workflows/page.tsx","./src/app/(dashboard)/workflows/page.integration.test.tsx","./src/app/chat/layout.tsx","./src/app/chat/layout.test.tsx","./src/components/chat/chatmessages.tsx","./src/components/chat/mcpconnectpicker.tsx","./src/app/chat/page.tsx","./src/app/chat/page.integration.test.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/chat/mcpappspanel.tsx","./src/components/chat/connectflowbanner.tsx","./src/app/chat/integrations/page.tsx","./src/components/chat/logspanel.tsx","./src/app/chat/logs/page.tsx","./src/components/chat/usagepanel.tsx","./src/app/chat/usage/page.tsx","./src/app/connect/layout.tsx","./src/app/connect/layout.test.tsx","./src/app/connect/page.tsx","./src/app/connect/page.test.tsx","./src/app/login/loginpage.tsx","./src/app/login/loginpage.integration.test.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.tsx","./src/app/onboarding/onboardingerrorview.test.tsx","./src/app/onboarding/onboardingloadingview.tsx","./src/app/onboarding/onboardingformbody.tsx","./src/app/onboarding/onboardingform.tsx","./src/app/onboarding/onboardingform.test.tsx","./src/app/onboarding/onboardingformbody.integration.test.tsx","./src/app/onboarding/onboardingformbody.test.tsx","./src/app/onboarding/onboardingloadingview.test.tsx","./src/app/onboarding/page.tsx","./src/components/betabadge.test.tsx","./src/components/createuserbutton.test.tsx","./src/components/dashboardheader.test.tsx","./src/components/debugwarningbanner.test.tsx","./src/components/deprecationbanner.test.tsx","./src/components/guardrailsettingsview.test.tsx","./src/components/helplink.test.tsx","./src/components/licenseexpirybanner.test.tsx","./src/components/norediswarningbanner.test.tsx","./src/components/scim.test.tsx","./src/components/ssomodals.test.tsx","./src/components/sidebarusagecard.test.tsx","./src/components/teamssosettings.test.tsx","./src/components/teams.test.tsx","./src/components/tooldetail.test.tsx","./src/components/toolpoliciesview.test.tsx","./src/components/uiaccesscontrolform.integration.test.tsx","./src/components/uiaccesscontrolform.unit.test.tsx","./src/components/userbanner.test.tsx","./src/components/activity_metrics.test.tsx","./src/components/add_pass_through.integration.test.tsx","./src/components/bulk_create_users_button.test.tsx","./src/components/cloudzero_export_modal.integration.test.tsx","./src/components/email_settings.test.tsx","./src/components/key_info_utils.test.tsx","./src/components/key_value_input.test.tsx","./src/components/leftnav.test.tsx","./src/components/logging_settings_view.test.tsx","./src/components/model_info_view.test.tsx","./src/components/navbar.test.tsx","./src/components/onboarding_link.test.tsx","./src/components/pass_through_info.integration.test.tsx","./src/components/per_user_usage.test.tsx","./src/components/price_data_reload.test.tsx","./src/components/provider_info_helpers.test.tsx","./src/components/public_model_hub.test.tsx","./src/components/query_param_input.test.tsx","./src/components/route_preview.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/vector_store_providers.test.tsx","./src/components/aihub/agenthubtablecolumns.test.tsx","./src/components/aihub/mcphubtablecolumns.test.tsx","./src/components/aihub/modelhubtable.test.tsx","./src/components/aihub/modelhubtablecolumns.test.tsx","./src/components/aihub/skillhubtablecolumns.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.integration.test.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.test.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.test.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.test.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.integration.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/modelselect/modelselect.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/passthroughsettings/passthroughendpointstable.test.tsx","./src/components/passthroughsettings/passthroughsettings.test.tsx","./src/components/settings/adminsettings/hashicorpvault/edithashicorpvaultmodal.test.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvault.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/pluginsettings/pluginsettings.integration.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.integration.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/adminsettings/userbannersettings/userbannersettings.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/editfallbacks.test.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.test.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.test.tsx","./src/components/sidebaraccountmenu/sidebaraccountmenu.test.tsx","./src/components/teamspage/teamstable.test.tsx","./src/components/themetoggle/themetoggle.test.tsx","./src/components/toolpolicies/policyselect.test.tsx","./src/components/toolpolicies/toolpoliciespanel.test.tsx","./src/components/toolpolicies/toolpoliciestable.test.tsx","./src/components/toolpolicies/toolpoliciestablecolumns.test.tsx","./src/components/usagepage/components/keymodelusageview.test.tsx","./src/components/usagepage/components/entityusage/topkeyview.test.tsx","./src/components/virtualkeyspage/virtualkeystable.test.tsx","./src/components/add_model/addmodelform.test.tsx","./src/components/add_model/autorouterroutingtest.test.tsx","./src/components/add_model/classifierprompteditor.integration.test.tsx","./src/components/add_model/complexityrouterconfig.test.tsx","./src/components/add_model/heuristicscoringconfig.test.tsx","./src/components/add_model/routerconfigbuilder.test.tsx","./src/components/add_model/semantickeywordmatching.test.tsx","./src/components/add_model/tiermodeleffortrows.test.tsx","./src/components/add_model/add_auto_router_tab.test.tsx","./tests/mounted-form-host.tsx","./src/components/add_model/advanced_settings.test.tsx","./src/components/add_model/auto_router_connection_test.test.tsx","./src/components/add_model/cache_control_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/model_connection_test.test.tsx","./src/components/add_model/provider_specific_fields.test.tsx","./src/components/agent_management/agentselector.test.tsx","./src/components/alerting/dynamic_form.integration.test.tsx","./src/components/chat/chatshell.test.tsx","./src/components/chat/connectflowbanner.test.tsx","./src/components/chat/logspanel.test.tsx","./src/components/chat/mcpappspanel.test.tsx","./src/components/chat/mcpconnectpicker.test.tsx","./src/components/chat_ui/codesnippets.test.tsx","./src/components/chat_ui/reasoningcontent.test.tsx","./src/components/chat_ui/responsemetrics.test.tsx","./src/components/common_components/defaultproxyadmintag.test.tsx","./src/components/common_components/deleteresourcemodal.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/metadatakeyvaluefields.test.tsx","./src/components/common_components/modelaliasmanager.test.tsx","./src/components/common_components/modelselector.test.tsx","./src/components/common_components/mountedformfield.test.tsx","./src/components/common_components/newbadge.tsx","./src/components/common_components/newbadge.test.tsx","./src/components/common_components/organizationdropdown.test.tsx","./src/components/common_components/passthroughguardrailssection.test.tsx","./src/components/common_components/premiumloggingsettings.test.tsx","./src/components/common_components/ratelimittypeformitem.test.tsx","./src/components/common_components/routersettingsaccordion.test.tsx","./src/components/common_components/routersettingssummary.test.tsx","./src/components/common_components/userdropdown.test.tsx","./src/components/common_components/routersettingswiring.test.tsx","./src/components/common_components/team_multi_select.test.tsx","./src/components/common_components/user_search_modal.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/email_events/email_event_settings.test.tsx","./src/components/guardrails/guardrailselector.test.tsx","./src/components/key_team_helpers/budgetfallbackseditor.test.tsx","./src/components/key_team_helpers/modelmaxbudgeteditor.integration.test.tsx","./src/components/key_team_helpers/tagratelimiteditor.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/fetch_models.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/byokcredentialmodal.test.tsx","./src/components/mcp_tools/mcptoolargumentsform.test.tsx","./src/components/mcp_tools/types.test.tsx","./src/components/model_add/credentialmodal.test.tsx","./src/components/model_add/credentialspanel.test.tsx","./src/components/model_add/credentialstable.test.tsx","./src/components/model_add/reuse_credentials.test.tsx","./src/components/model_dashboard/healthcheckcomponent.test.tsx","./src/components/model_dashboard/healthcheckstable.test.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.test.tsx","./src/components/molecules/cost_optimization_feedback_banner.test.tsx","./src/components/molecules/logo/logo.test.tsx","./src/components/molecules/models/providerlogo.test.tsx","./src/components/organisms/regeneratekeymodal.integration.test.tsx","./src/components/organisms/regeneratekeymodal.test.tsx","./src/components/organisms/create_key_button.integration.test.tsx","./src/components/organisms/create_key_button.test.tsx","./src/components/organization/organization_view.test.tsx","./src/components/organization/org-create/orgcreatedialog.test.tsx","./src/components/organization/org-settings/orgsettingsform.test.tsx","./src/components/permissions/mcpserverpermissions.test.tsx","./src/components/policies/policyselector.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/routing_groups/routinggroupmodal.test.tsx","./src/components/routing_groups/routinggroupstable.test.tsx","./src/components/routing_groups/index.integration.test.tsx","./src/components/search_tools/searchtoolselector.test.tsx","./src/components/shared/alert.test.tsx","./src/components/shared/badgelink.test.tsx","./src/components/shared/copybutton.test.tsx","./src/components/shared/createdkeydisplay.test.tsx","./src/components/shared/entitylink.test.tsx","./src/components/shared/inheritedbudgethint.test.tsx","./src/components/shared/meter.test.tsx","./src/components/shared/multiselect.test.tsx","./src/components/shared/pageheader.test.tsx","./src/components/shared/paginatedmultiselect.test.tsx","./src/components/shared/paginatedsearchselect.test.tsx","./src/components/shared/paginationstatusalerts.test.tsx","./src/components/shared/searchselect.test.tsx","./src/components/shared/sidebar.test.tsx","./src/components/shared/toolbarseparator.test.tsx","./src/components/shared/advanced_date_picker.test.tsx","./src/components/shared/chart_loader.test.tsx","./src/components/shared/datatable/datatable.test-d.tsx","./src/components/shared/datatable/datatable.test.tsx","./src/components/shared/datatable/datatablefilterdrawer.test.tsx","./src/components/shared/datatable/datatablepagination.test.tsx","./src/components/shared/datatable/datatablerowselection.test.tsx","./src/components/shared/datatable/datatablesortheader.test.tsx","./src/components/shared/datatable/datatabletoolbar.test.tsx","./src/components/shared/charts/area_chart.test.tsx","./src/components/shared/charts/bar_chart.test.tsx","./src/components/shared/charts/chart_legend.test.tsx","./src/components/shared/charts/chart_tooltip.test.tsx","./src/components/shared/charts/donut_chart.test.tsx","./src/components/shared/charts/line_chart.test.tsx","./src/components/shared/form/formfield.test.tsx","./src/components/shared/table_cells/autoroutertag.test.tsx","./src/components/shared/table_cells/date_cell.test.tsx","./src/components/shared/table_cells/id_cell.test.tsx","./src/components/shared/table_cells/identity_cell.test.tsx","./src/components/shared/table_cells/models_cell.test.tsx","./src/components/shared/table_cells/money_cell.test.tsx","./src/components/shared/table_cells/spend_budget_cell.test.tsx","./src/components/shared/table_cells/status_badge.test.tsx","./src/components/tag_management/tagselector.test.tsx","./src/components/team/availableteamspanel.test.tsx","./src/components/team/editmembership.integration.test.tsx","./src/components/team/editmembership.test.tsx","./src/components/team/loggingsettings.test.tsx","./src/components/team/myusertab.test.tsx","./src/components/team/teaminfo.test.tsx","./src/components/team/teammembertab.test.tsx","./src/components/team/teamvirtualkeystable.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/keysavingstab.integration.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/alert-dialog.test.tsx","./src/components/ui/avatar.test.tsx","./src/components/ui/badge.test.tsx","./src/components/ui/breadcrumb.test.tsx","./src/components/ui/button.test.tsx","./src/components/ui/chart.test.tsx","./src/components/ui/field.test.tsx","./src/components/ui/ref-forwarding.test.tsx","./src/components/ui/select.test.tsx","./src/components/ui/tooltip.test.tsx","./src/components/ui/ui-loading-spinner.test.tsx","./src/components/vector_store_management/vectorstoreselector.test.tsx","./src/components/view_logs/auditlogstable.test.tsx","./src/components/view_logs/configinfomessage.test.tsx","./src/components/view_logs/costbreakdownviewer.test.tsx","./src/components/view_logs/requestlogsfilters.test.tsx","./src/components/view_logs/requestlogspanel.test.tsx","./src/components/view_logs/requestlogstablecolumns.test.tsx","./src/components/view_logs/typebadges.test.tsx","./src/components/view_logs/index.integration.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/auditlogdrawer/auditlogdrawer.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/classifytag.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/jsonviewer.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailsdrawer.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/routingdecisioncard.test.tsx","./src/components/view_logs/logdetailsdrawer/sectionheader.test.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.test.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.test.tsx","./src/components/view_logs/logdetailsdrawer/tokenflow.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/usesyntaxtheme.test.tsx","./src/hooks/usetoolsoauthflow.test.tsx","./src/hooks/policies/usedeletepolicyattachment.test.tsx","./src/lib/forms/usezodform.test.tsx","./tests/createkeypage.expiredtoken.test.tsx","./tests/top_key_view.test.tsx","./.next/types/cache-life.d.ts","./.next/types/validator.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-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/use-sync-external-store/index.d.ts"],"fileIdsList":[[97,143,484,485,486,487],[97,143],[97,143,226,528,531,2638,2745,2779,2789,2818,2832,2843,2847,2854,2871,2978,2990,3033,3071,3094,3114,3158,3194,3218,3290,3302,3316,3444,3486,3510,3547,3568,3579,3592,3601,3613,3620,3623,3626,3646,3666,3686,3702,3704,3708,3711,3713,3716,3718,3720,3721,3723,3728,3729,3730,3731,3741],[97,143,529,530,531],[97,143,3341,3345,3346,3349,3350,3352,3354,3355,3358,3377,3402,3403,3404,3405],[97,143,3345,3353,3406],[97,143,3351],[97,143,3349,3353,3354,3406],[97,143,3406],[97,143,3347,3406],[97,143,3356,3357],[97,143,3352],[97,143,3352,3354,3355,3358,3375,3406],[97,143,3369],[97,143,3349,3355,3406],[97,143,3341,3345,3346,3348],[97,143,176],[97,143,3341],[97,138,143,3344],[97,143,3341,3349,3406],[97,143,3349,3406],[97,143,3401,3406],[97,143,3349,3371,3379,3401,3406],[97,143,3349,3371,3374,3375,3406],[97,143,3377,3406],[97,143,3395],[97,143,3349,3380,3395,3396,3398,3407],[97,143,3397],[97,143,3405],[97,143,3394],[97,143,3349,3354,3355,3359,3364,3402],[97,143,3364,3365],[97,143,3349,3355,3359,3365,3402],[97,143,3359,3360,3361,3362,3363,3365,3368,3385,3389,3392,3401],[97,143,3349,3354,3355,3359,3402],[97,143,3349,3354,3355,3358,3359,3402],[97,143,3360,3361,3362,3363,3381,3382,3383,3387,3390,3393,3402],[97,143,3366,3367,3368],[97,143,3349,3354,3355,3359,3366,3367,3402],[97,143,3349,3354,3355,3359,3366,3402],[97,143,3349,3354,3355,3359,3370,3377,3401,3402],[97,143,3378,3401],[97,143,3348,3349,3354,3359,3377,3378,3379,3380,3399,3400,3401,3402],[97,143,3348,3349,3354,3355,3359,3402],[97,143,3384,3385,3386],[97,143,3349,3354,3355,3359,3385,3402],[97,143,3349,3354,3355,3359,3365,3384,3386,3402],[97,143,3388,3389],[97,143,3349,3354,3355,3358,3359,3388,3402],[97,143,3391,3392],[97,143,3349,3354,3355,3359,3391,3402],[97,143,3348,3349,3354,3359,3377,3402,3403],[97,143,3351,3377,3402,3403,3404],[97,143,3373],[97,143,3349,3351,3354,3355,3359,3370,3377],[97,143,3372,3377],[97,143,3348,3349,3354,3359,3372,3375,3376,3377],[85,97,143,630,635],[97,143,631,635,636,637,638,639],[97,143,631,635,636,637,638],[85,97,143,627,628,630,631,634],[85,97,143,630,631,632,635],[85,97,143,627,628,630],[97,143,689,690],[97,143,693,694,695,696,697,698,699,701,702,703],[97,143,692,693,694,695,696,697,698,699,701,702],[85,97,143,226,628,691,692],[85,97,143,692,700],[97,143,707,708,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,734,736],[97,143,707,708,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,734,735],[85,97,143,630,712,713],[85,97,143,630],[85,97,143,706],[85,97,143],[85,97,143,630,738],[85,97,143,630,632,738],[97,143,738,739,740,741],[97,143,738,739,740],[97,143,743],[85,97,143,627,628,630,712],[97,143,749],[97,143,745,746,747],[97,143,745,746],[85,97,143,630,632,745],[97,143,633,751,752,753],[97,143,633,751,752],[85,97,143,630,632,633],[85,97,143,627,628,630,634],[85,97,143,632,633],[85,97,143,630,633],[85,97,143,630,713],[85,97,143,630,632],[97,143,715,717,718,719,720,721,722,723,724,725,726,727,729,730,731,734,755,756,757,758,759,760,761,762,763,764,766],[97,143,715,717,718,719,720,721,722,723,724,725,726,727,729,730,731,734,735,755,756,757,758,759,760,761,762,763,764,765],[85,97,143,630,712],[85,97,143,630,632,650,713],[85,97,143,684],[85,97,143,627,628,705],[97,143,733],[97,143,768,769,776,777,778,779,780,781,782,783,784,785,786,787,789,792,795,796,797],[97,143,732,768,769,776,777,778,779,780,781,782,783,784,785,786,787,789,792,795,796],[97,143,226,629,775,794],[85,97,143,795],[85,97,143,226],[97,143,799,800],[97,143,799],[97,143,691,694,695,696,697,698,699,700,702,802],[97,143,690,691,694,695,696,697,698,699,700,702],[85,97,143,630,632,650],[85,97,143,226,627,628,688,690],[97,143,689],[85,97,143,632,649,650,658,684,688,691,1014],[85,97,143,630,690],[85,97,143,804],[97,143,805,806],[97,143,804,805],[97,143,808,809,810,811,812,813,815,817,818,819,820,821,822,823,824,825],[97,143,690,808,809,810,811,812,813,815,817,818,819,820,821,822,823,824],[85,97,143,630,632,650,816],[85,97,143,226,627,628,688,690,816],[85,97,143,814,815],[85,97,143,630,814,816],[85,97,143,630,632,712],[97,143,712,827,828,829,830,831,832,833],[97,143,712,827,828,829,830,831,832],[85,97,143,630,711],[85,97,143,632,712],[97,143,835,836,837],[97,143,835,836],[85,97,143,679],[85,97,143,650,657,679],[85,97,143,630,661],[85,97,143,628,632,649,679,688],[85,97,143,657,679],[97,143,679],[85,97,143,672],[97,143,627,679],[97,143,657,679],[97,143,628,658,679],[97,143,668,679],[85,97,143,630,657,668,679],[97,143,667,679],[85,97,143,657,673,679],[97,143,629,649,658,688],[85,97,143,672,679],[97,143,655,657,659,662,663,664,665,669,670,671,674,675,676,677,678,679,680,681,682,683],[97,143,668],[85,97,143,628,655,657,658,659,662,663,664,665,668,669,670,671,674,675,676,677,678,680,684],[97,143,666,688],[85,97,143,627,628,630,709],[97,143,710],[97,143,629,640,704,711,737,742,744,748,750,754,765,767,794,798,801,803,807,826,834,838,840,842,844,851,866,876,881,897,910,917,921,923,931,951,961,965,972,987,989,991,999,1011,1013],[97,143,839],[85,97,143,630,834],[97,143,627],[85,97,143,710,712],[97,143,626],[85,97,143,629],[85,97,143,630,660],[85,97,143,630,775],[97,143,768,769,775,776,777,778,779,780,781,782,783,784,785,786,787,789,790,791,792,793],[97,143,732,768,769,774,775,776,777,778,779,780,781,782,783,784,785,786,787,789,790,791,792],[85,97,143,226,627,628,688,770,771,772,773,774],[85,97,143,770,775],[97,143,770],[85,97,143,630,632,649,650,657,658,684,688,775,794],[85,97,143,226,775,788],[85,97,143,770],[85,97,143,630,774],[97,143,841],[85,97,143,775],[97,143,843],[97,143,845,846,847,848,849,850],[97,143,845,846,847,848,849],[85,97,143,630,845],[97,143,852,853,854,855,856,857,858,859,860,861,862,863,864,865],[97,143,852,853,854,855,856,857,858,859,860,861,862,863,864],[85,97,143,630,632,713],[85,97,143,630,868],[97,143,868,869,870,871,872,873,874,875],[97,143,868,869,870,871,872,873,874],[85,97,143,627,628,630,712,867],[97,143,878,879,880],[97,143,732,878,879],[85,97,143,630,878],[85,97,143,627,628,630,712,877],[97,143,885,886,887,888,889,890,891,892,893,894,895,896],[97,143,884,885,886,887,888,889,890,891,892,893,894,895],[85,97,143,226,627,628,688,884],[97,143,883],[85,97,143,632,649,650,658,684,688,882,885,897,1014],[85,97,143,630,884],[97,143,900,902,903,904,905,906,907,908,909],[97,143,899,900,902,903,904,905,906,907,908],[85,97,143,901],[85,97,143,226,627,628,688,899],[97,143,898],[85,97,143,632,649,658,684,688,900,1014],[85,97,143,630,899],[97,143,911,912,913,914,915,916],[97,143,911,912,913,914,915],[85,97,143,630,911],[97,143,922],[97,143,918,919,920],[97,143,918,919],[85,97,143,630,632,918],[85,97,143,630,924],[97,143,924,925,926,927,928,929,930],[97,143,924,925,926,927,928,929],[97,143,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950],[97,143,732,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949],[97,143,732],[85,97,143,630,952],[97,143,952,953,954,955,956,958,959,960],[97,143,952,953,954,955,956,958,959],[85,97,143,630,952,957],[97,143,962,963,964],[97,143,962,963],[85,97,143,627,629,630,712],[85,97,143,630,962],[97,143,966,967,968,969,970,971],[97,143,966,967,968,969,970],[85,97,143,630,966,967],[85,97,143,630,967],[85,97,143,630,632,966,967],[85,97,143,627,628,630,966],[97,143,974],[97,143,973,974,975,976,977,978,979,980,981,982,983,984,985,986],[97,143,973,974,975,976,977,978,979,980,981,982,983,984,985],[85,97,143,630,713,974],[85,97,143,975],[85,97,143,630,632,974],[85,97,143,973],[97,143,990],[97,143,988],[85,97,143,630,993],[97,143,992,993,994,995,996,997,998],[97,143,630,992,993,994,995,996,997],[85,97,143,630,765],[97,143,1002,1003,1004,1005,1006,1007,1008,1009,1010],[97,143,1001,1002,1003,1004,1005,1006,1007,1008,1009],[85,97,143,226,627,628,688,1001],[97,143,1000],[85,97,143,632,649,658,684,688,1002,1011,1014],[85,97,143,630,1001],[85,97,143,628],[97,143,630,1012],[97,143,656,685,686,687],[85,97,143,655],[85,97,143,627,628,632,649,650,686],[97,143,630,632,658,684,685],[85,97,143,651,684],[97,143,641],[97,143,642],[97,143,642,643,645,646,647,648],[97,143,645],[85,97,143,226,645],[97,143,644,645],[97,143,2604],[97,143,651],[97,143,652,653],[85,97,143,654],[97,143,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],[97,143,2031],[97,143,1069,1255,2030],[97,143,641,2081,2082,2083,2084],[97,143,226],[97,143,1400,1408],[97,143,1088],[97,143,1409,1410,1411,1412,1413],[97,143,1408,1410],[97,143,1409,1410],[85,97,143,1407,1408,1409],[85,97,143,226,1089],[97,143,1090],[97,143,1400,1403],[97,143,1394,1400,1401,1402,1403,1404,1405,1406],[97,143,1400],[85,97,143,1146],[97,143,1396],[97,143,1396,1397,1398,1399],[97,143,1395],[97,143,1127],[97,143,1112,1135],[97,143,1135],[97,143,1135,1146],[97,143,1121,1135,1146],[97,143,1126,1135,1146],[97,143,1116,1135],[97,143,1124,1135,1146],[97,143,1122],[97,143,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],[97,143,1125],[97,143,1112,1113,1114,1115,1116,1117,1118,1119,1120,1122,1123,1125,1127,1128,1129,1130,1131,1132,1133,1134],[97,143,1337],[97,143,1334,1335,1336,1337,1338,1341,1342,1343,1344,1345,1346,1347,1348],[97,143,1333],[97,143,1340],[97,143,1334,1335,1336],[97,143,1334,1335],[97,143,1337,1338,1340],[97,143,1335],[97,143,2615],[97,143,2614],[85,97,143,196,460,1349,1350],[97,143,1606],[97,143,1593,1594,1595],[97,143,1588,1589,1590],[97,143,1566,1567,1568,1569],[97,143,1532,1606],[97,143,1532],[97,143,1532,1533,1534,1535,1580],[97,143,1570],[97,143,1565,1571,1572,1573,1574,1575,1576,1577,1578,1579],[97,143,1580],[97,143,1531],[97,143,1584,1586,1587,1605,1606],[97,143,1584,1586],[97,143,1581,1584,1606],[97,143,1591,1592,1596,1597,1602],[97,143,1585,1587,1597,1605],[97,143,1604,1605],[97,143,1581,1585,1587,1603,1604],[97,143,1585,1606],[97,143,1583],[97,143,1583,1585,1606],[97,143,1581,1582],[97,143,1598,1599,1600,1601],[97,143,1587,1606],[97,143,1542],[97,143,1536,1543],[97,143,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],[97,143,1562,1606],[97,143,600,601],[97,143,4064],[97,143,2071],[97,143,2094],[97,143,4068],[97,143,546,547,4070],[97,143,2657],[97,143,157,184,191,3342,3343],[97,140,143],[97,142,143],[143],[97,143,148,176],[97,143,144,149,154,162,173,184],[97,143,144,145,154,162],[92,93,94,97,143],[97,143,146,185],[97,143,147,148,155,163],[97,143,148,173,181],[97,143,149,151,154,162],[97,142,143,150],[97,143,151,152],[97,143,153,154],[97,142,143,154],[97,143,154,155,156,173,184],[97,143,154,155,156,169,173,176],[97,143,151,154,157,162,173,184],[97,143,154,155,157,158,162,173,181,184],[97,143,157,159,173,181,184],[95,96,97,98,99,100,101,139,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],[97,143,154,160],[97,143,161,184,189],[97,143,151,154,162,173],[97,143,163],[97,143,164],[97,142,143,165],[97,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],[97,143,167],[97,143,168],[97,143,154,169,170],[97,143,169,171,185,187],[97,143,154,173,174,176],[97,143,175,176],[97,143,173,174],[97,143,177],[97,140,143,173,178],[97,143,154,179,180],[97,143,179,180],[97,143,148,162,173,181],[97,143,182],[97,143,162,183],[97,143,157,168,184],[97,143,148,185],[97,143,173,186],[97,143,161,187],[97,143,188],[97,138,143],[97,138,143,154,156,165,173,176,184,187,189],[97,143,173,190],[97,143,173,191],[85,89,97,143,192,193,194,195,196,479,524],[85,89,97,143,192,193,194,195,460,479,524],[85,89,97,143,192,193,195,196,479,524],[85,97,143,196,460,461],[85,97,143,196,460],[85,97,143,1321],[85,89,97,143,193,194,195,196,479,524],[85,89,97,143,192,194,195,196,479,524],[83,84,97,143],[97,143,533,538,539,541],[97,143,587,588],[97,143,539,541,581,582,583],[97,143,539],[97,143,539,541,581],[97,143,539,581],[97,143,594],[97,143,534,594,595],[97,143,534,594],[97,143,534,540],[97,143,535],[97,143,534,535,536,538],[97,143,534],[97,143,1015,1017],[97,143,1015],[97,143,2321],[97,143,2319,2321],[97,143,2319],[97,143,2321,2385,2386],[97,143,2321,2388],[97,143,2321,2389],[97,143,2406],[97,143,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,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,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,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,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],[97,143,2321,2482],[97,143,2321,2386,2506],[97,143,2319,2503,2504],[97,143,2505],[97,143,2321,2503],[97,143,2318,2319,2320],[97,143,2007],[97,143,2006],[97,143,2008],[97,143,546,547,2605,2606,4070],[97,143,2607],[97,143,1194,1195],[97,143,1194,1195,1196,1197],[97,143,1194,1196],[97,143,1194],[97,143,157,173,191],[97,143,574,575],[97,143,2701,2704,2707,2709,2710,2711],[97,143,2668,2696,2701,2704,2707,2709,2711],[97,143,2668,2696,2701,2704,2707,2711],[97,143,2734,2735,2739],[97,143,2711,2734,2736,2739],[97,143,2711,2734,2736,2738],[97,143,2668,2696,2711,2734,2736,2737,2739],[97,143,2736,2739,2740],[97,143,2711,2734,2736,2739,2741],[97,143,2658,2668,2669,2670,2694,2695,2696],[97,143,2658,2669,2696],[97,143,2658,2668,2669,2696],[97,143,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693],[97,143,2658,2662,2668,2670,2696],[97,143,2712,2713,2733],[97,143,2668,2696,2734,2736,2739],[97,143,2668,2696],[97,143,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732],[97,143,2657,2668,2696],[97,143,2701,2702,2703,2707,2711],[97,143,2701,2704,2707,2711],[97,143,2701,2704,2705,2706,2711],[97,143,482],[97,143,430,493,494],[97,143,201,202,204,216,240,355,366,475],[97,143,204,235,236,237,239,475],[97,143,204,372,374,376,377,379,475,477],[97,143,204,238,275,475],[97,143,202,204,215,216,222,228,233,354,355,356,365,475,477],[97,143,475],[97,143,211,217,236,256,351],[97,143,204],[97,143,197,211,217],[97,143,383],[97,143,380,381,383],[97,143,380,382,475],[97,143,157,256,454,472],[97,143,157,327,330,346,351,472],[97,143,157,299,472],[97,143,359],[97,143,358,359,360],[97,143,358],[91,97,143,157,197,204,216,222,228,234,236,240,241,254,255,322,352,353,366,475,479],[97,143,201,204,238,275,372,373,378,475,527],[97,143,238,527],[97,143,201,255,425,475,527],[97,143,527],[97,143,204,238,239,527],[97,143,375,527],[97,143,241,354,357,364],[85,97,143,430],[97,143,168,211,226],[97,143,211,226],[85,97,143,296],[85,97,143,217,226,430],[97,143,211,282,296,297,509,516],[97,143,281,510,511,512,513,515],[97,143,332],[97,143,332,333],[97,143,215,217,284,285],[97,143,217,291,292],[97,143,217,286,294],[97,143,291],[97,143,209,217,284,285,286,287,288,289,290,291,294],[97,143,217,284,291,292,293,295],[97,143,217,285,287,288],[97,143,285,287,290,292],[97,143,514],[97,143,217],[85,97,143,205,503],[85,97,143,184],[85,97,143,238,273],[85,97,143,238,366],[97,143,271,276],[85,97,143,272,481],[97,143,2631],[85,89,97,143,157,192,193,194,195,196,479,523],[97,143,157,217],[97,143,157,216,221,302,319,361,362,366,422,424,475,476],[97,143,254,363],[97,143,479],[97,143,203],[85,97,143,208,211,427,443,445],[97,143,168,211,427,442,443,444,526],[97,143,436,437,438,439,440,441],[97,143,438],[97,143,442],[97,143,226,390,391,393],[85,97,143,217,384,385,386,387,392],[97,143,390,392],[97,143,388],[97,143,389],[85,97,143,226,272,481],[85,97,143,226,480,481],[85,97,143,226,481],[97,143,319,320],[97,143,320],[97,143,157,476,481],[97,143,349],[97,142,143,348],[97,143,211,217,223,225,327,340,344,346,424,427,464,465,472,476],[97,143,217,266,288],[97,143,327,338,341,346],[85,97,143,208,211,327,330,346,349,383,431,432,433,434,435,446,447,448,449,450,451,452,453,527],[97,143,208,211,236,327,334,335,336,339,340],[97,143,173,217,236,338,345,427,428,472],[97,143,342],[97,143,157,168,205,217,221,231,263,264,267,319,322,387,422,423,464,475,476,477,479,527],[97,143,208,209,211],[97,143,327],[97,142,143,236,263,264,321,322,323,324,325,326,476],[97,143,346],[97,142,143,210,211,221,225,261,327,334,335,336,337,338,341,342,343,344,345,465],[97,143,157,261,262,334,476,477],[97,143,236,264,319,322,327,424,476],[97,143,157,475,477],[97,143,157,173,472,476,477],[97,143,157,168,197,211,216,223,225,228,231,238,258,263,264,265,266,267,302,303,305,308,310,313,314,315,316,318,366,422,424,472,475,476,477],[97,143,157,173],[97,143,204,205,206,234,472,473,474,479,481,527],[97,143,201,202,475],[97,143,395],[97,143,157,173,184,213,379,383,384,385,386,387,393,394,527],[97,143,168,184,197,211,213,225,228,264,303,308,318,319,372,399,400,401,408,411,412,422,424,472,475],[97,143,228,234,241,254,264,322,475],[97,143,157,184,205,216,225,264,406,472,475],[97,143,426],[97,143,157,395,409,410,419],[97,143,472,475],[97,143,324,465],[97,143,225,263,366,481],[97,143,157,168,203,308,368,372,401,408,411,414,472],[97,143,157,241,254,372,415],[97,143,204,265,366,417,475,477],[97,143,157,184,387,475],[97,143,157,238,265,366,367,368,377,395,416,418,475],[91,97,143,157,263,421,479,481],[97,143,317,422],[97,143,157,168,211,214,216,217,223,225,231,240,241,254,264,267,303,305,315,318,319,366,399,400,401,402,404,407,422,424,472,481],[97,143,157,173,241,408,413,419,472],[97,143,244,245,246,247,248,249,250,251,252,253],[97,143,258,309],[97,143,311],[97,143,309],[97,143,311,312],[97,143,157,215,216,217,221,222,476],[97,143,157,168,203,205,223,227,263,266,267,301,422,472,477,479,481],[97,143,157,168,184,207,214,215,225,227,264,420,465,471,476],[97,143,334],[97,143,335],[97,143,217,228,464],[97,143,336],[97,143,210],[97,143,212,224],[97,143,157,212,216,223],[97,143,219,224],[97,143,220],[97,143,212,213],[97,143,212,268],[97,143,212],[97,143,214,258,307],[97,143,306],[97,143,211,213,214],[97,143,214,304],[97,143,211,213],[97,143,263,366],[97,143,464],[97,143,157,184,223,225,229,263,366,421,424,427,428,429,455,456,459,463,465,472,476],[97,143,277,280,282,283,296,297],[85,97,143,194,195,196,226,457,458],[85,97,143,194,195,196,226,457,458,462],[97,143,350],[97,143,236,257,262,263,327,328,329,330,331,333,346,347,349,352,421,424,475,477],[97,143,296],[97,143,157,301,472],[97,143,301],[97,143,157,223,269,298,300,302,421,472,479,481],[97,143,277,278,279,280,282,283,296,297,480],[91,97,143,157,168,184,212,213,225,231,263,264,267,366,419,420,422,472,475,476,479],[97,143,208,211,218],[97,143,262,264,396,399],[97,143,262,397,466,467,468,469,470],[97,143,157,258,475],[97,143,157],[97,143,261,346],[97,143,260],[97,143,262,315],[97,143,259,261,475],[97,143,157,207,262,396,397,398,472,475,476],[85,97,143,211,217,295],[85,97,143,209],[97,143,199,200],[85,97,143,205],[85,97,143,211,281],[85,91,97,143,263,267,479,481],[97,143,205,503,504],[85,97,143,276],[85,97,143,168,184,203,270,272,274,275,481],[97,143,211,238,476],[97,143,211,403],[85,97,143,155,157,168,201,203,276,374,479,480],[85,97,143,192,193,194,195,196,479,524],[85,86,87,88,89,97,143],[97,143,148],[97,143,369,370,371],[97,143,369],[85,89,97,143,157,159,168,191,192,193,194,195,196,197,203,231,236,414,442,477,478,481,524],[97,143,489],[97,143,491],[97,143,495],[97,143,2632],[97,143,497],[97,143,499,500,501],[97,143,505],[90,97,143,483,488,490,492,496,498,502,506,508,518,519,521,525,526,527,528],[97,143,507],[97,143,517],[97,143,272],[97,143,520],[97,142,143,262,396,397,399,466,467,469,470,522,524],[97,143,191],[85,97,143,1612],[85,97,143,1611],[97,143,1611,1614],[97,143,2885,2886,2891],[97,143,2887,2888,2890,2892],[97,143,2891],[97,143,2888,2890,2891,2892,2893,2895,2897,2898,2899,2900,2901,2902,2903,2907,2922,2933,2936,2940,2948,2949,2951,2954,2957,2960],[97,143,2891,2898,2911,2915,2924,2926,2927,2928,2955],[97,143,2891,2892,2908,2909,2910,2911,2913,2914],[97,143,2915,2916,2923,2926,2955],[97,143,2891,2892,2897,2916,2928,2955],[97,143,2892,2915,2916,2917,2923,2926,2955],[97,143,2888],[97,143,2894,2915,2922,2928],[97,143,2922],[97,143,2891,2911,2918,2920,2922,2955],[97,143,2915,2922,2923],[97,143,2924,2925,2927],[97,143,2955],[97,143,2904,2905,2906,2956],[97,143,2891,2892,2956],[97,143,2887,2891,2905,2907,2956],[97,143,2891,2905,2907,2956],[97,143,2891,2893,2894,2895,2956],[97,143,2891,2893,2894,2908,2909,2910,2912,2913,2956],[97,143,2913,2914,2929,2932,2956],[97,143,2928,2956],[97,143,2891,2915,2916,2917,2923,2924,2926,2927,2956],[97,143,2894,2930,2931,2932,2956],[97,143,2891,2956],[97,143,2891,2893,2894,2914,2956],[97,143,2887,2891,2893,2894,2908,2909,2910,2912,2913,2914,2956],[97,143,2891,2893,2894,2909,2956],[97,143,2887,2891,2894,2908,2910,2912,2913,2914,2956],[97,143,2894,2897,2956],[97,143,2897],[97,143,2887,2891,2893,2894,2896,2897,2898,2956],[97,143,2896,2897],[97,143,2891,2893,2897,2956],[97,143,2957,2958],[97,143,2887,2891,2897,2898,2956],[97,143,2891,2893,2935,2956],[97,143,2891,2893,2934,2956],[97,143,2891,2893,2894,2922,2937,2939,2956],[97,143,2891,2893,2939,2956],[97,143,2891,2893,2894,2922,2938,2956],[97,143,2891,2892,2893,2956],[97,143,2942,2956],[97,143,2891,2937,2956],[97,143,2944,2956],[97,143,2891,2893,2956],[97,143,2941,2943,2945,2947,2956],[97,143,2891,2893,2941,2946,2956],[97,143,2937,2956],[97,143,2922,2956],[97,143,2894,2895,2898,2899,2900,2901,2902,2903,2907,2922,2933,2936,2940,2948,2949,2951,2954,2959],[97,143,2891,2893,2922,2956],[97,143,2887,2891,2893,2894,2918,2919,2921,2922,2956],[97,143,2891,2900,2950,2956],[97,143,2891,2893,2952,2954,2956],[97,143,2891,2893,2954,2956],[97,143,2891,2893,2894,2952,2953,2956],[97,143,2892],[97,143,2889,2891,2892],[97,143,1290],[97,143,1091,1290,1291],[97,143,568],[97,143,566,568],[97,143,557,565,566,567,569,571],[97,143,555],[97,143,558,563,568,571],[97,143,554,571],[97,143,558,559,562,563,564,571],[97,143,558,559,560,562,563,571],[97,143,555,556,557,558,559,563,564,565,567,568,569,571],[97,143,571],[97,143,553,555,556,557,558,559,560,562,563,564,565,566,567,568,569,570],[97,143,553,571],[97,143,558,560,561,563,564,571],[97,143,562,571],[97,143,563,564,568,571],[97,143,556,566],[97,143,1339],[85,97,143,1051],[97,143,1051,1052,1053,1054,1055,1058,1059,1060,1061,1062,1063,1064,1067,1068],[97,143,1051],[97,143,1056,1057],[85,97,143,1048,1051],[97,143,1045,1046,1048],[97,143,1041,1044,1046,1048],[97,143,1045,1048],[85,97,143,1036,1037,1038,1041,1042,1043,1045,1046,1047,1048],[97,143,1038,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050],[97,143,1045],[97,143,1039,1045,1046],[97,143,1039,1040],[97,143,1044,1046,1047],[97,143,1044],[97,143,1036,1041,1044,1046,1047],[85,97,143,1041,1044,1045,1046],[97,143,1065,1066],[85,97,143,2258],[85,97,143,2257],[97,143,2699],[85,97,143,2658,2667,2696,2698],[85,97,143,2109,2110,2157],[97,143,2202,2203],[97,143,2109],[97,143,2157],[85,97,143,2204],[85,97,143,2076,2086,2089,2091,2097,2098,2105,2107,2108,2110,2111,2112,2114,2154,2157],[85,97,143,2097,2157],[85,97,143,2076,2086,2089,2091,2096,2098,2107,2109,2110,2111,2115,2117,2118,2154,2157],[85,97,143,2107,2115,2159],[85,97,143,2090,2157],[85,97,143,2075,2076,2078,2086,2157],[85,97,143,2076,2086,2107,2148,2157],[85,97,143,2076,2116,2137,2141,2157],[85,97,143,2089,2098,2110,2111,2124,2125,2157,2198],[97,143,2075,2157],[97,143,2086,2157],[85,97,143,2076,2086,2089,2091,2097,2098,2110,2111,2136,2154,2157],[85,97,143,2076,2078,2115,2128,2181],[85,97,143,2074,2076,2078,2128],[85,97,143,2076,2078,2106,2128,2129,2157],[85,97,143,2076,2086,2089,2093,2097,2098,2110,2111,2125,2138,2140,2154,2157],[85,97,143,2080,2086,2157],[85,97,143,2080,2086,2154,2157],[85,97,143,2157],[85,97,143,2157,2214],[85,97,143,2115,2125,2157],[85,97,143,2075,2125,2157],[85,97,143,2125,2157],[85,97,143,2087],[85,97,143,2076,2125,2157],[85,97,143,2074,2076,2157],[85,97,143,2075,2076,2077,2157],[85,97,143,2075,2076,2078,2157,2214],[85,97,143,2099,2100,2101],[85,97,143,2086,2088,2089,2100,2125,2157,2160],[97,143,2147,2157],[97,143,2086,2087,2106,2152,2154,2157],[97,143,2074,2075,2076,2078,2079,2080,2086,2087,2089,2097,2098,2099,2102,2106,2108,2109,2110,2111,2112,2113,2115,2116,2125,2128,2130,2136,2137,2138,2140,2141,2142,2149,2152,2153,2154,2157,2158,2159,2161,2162,2163,2164,2165,2166,2167,2168,2170,2172,2174,2175,2176,2177,2178,2179,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2208,2209,2210,2211,2212,2213],[85,97,143,2076,2089,2091,2098,2110,2111,2120,2122,2124,2139,2157,2173,2214],[85,97,143,2076,2080,2086,2129,2157,2171],[85,97,143,2076,2086],[85,97,143,2076,2080,2086,2129,2157,2169],[85,97,143,2076,2098,2106,2110,2111,2121,2129,2157],[85,97,143,2076,2086,2089,2091,2096,2098,2107,2110,2111,2154,2157,2165,2173,2176],[85,97,143,2096,2157],[85,97,143,2109,2157],[97,143,2081,2085,2157],[97,143,2079,2080,2081,2085,2154,2157],[97,143,2081,2085,2090],[97,143,2081,2085,2124,2142,2157],[97,143,2081,2085,2086,2091,2092,2093,2114,2118,2119,2122,2123,2157],[97,143,2081,2085,2099,2102,2157],[97,143,2081,2085,2125,2157],[97,143,2081,2085,2086],[97,143,2081,2085],[97,143,2081,2082,2085,2086,2128,2130],[97,143,2081,2082,2085,2086,2157],[97,143,2081,2085,2087,2113,2157],[97,143,2105,2124,2147,2157],[97,143,2086,2091,2104,2105,2106,2124,2131,2134,2143,2147,2149,2150,2151,2153,2157],[97,143,2086,2091,2104,2105],[97,143,2147],[97,143,2085,2086,2091,2103,2124,2125,2126,2127,2131,2132,2133,2134,2135,2143,2144,2145,2146],[97,143,2081,2085,2086,2088,2089,2124,2157],[97,143,2091,2104,2113,2124,2157],[97,143,2104,2117,2124],[97,143,2091,2124,2157],[85,97,143,2089,2120,2121,2124,2157],[97,143,2124],[97,143,2104,2124],[97,143,2089,2091,2124,2157],[97,143,2107,2124,2157],[97,143,2125,2157],[85,97,143,2115,2116,2157],[97,143,2089,2096,2103,2105,2106,2125,2154,2157],[85,97,143,2089,2113,2116,2137,2141,2157,2161,2184,2185,2186,2199],[85,97,143,2089,2157,2161,2170,2172,2174,2175,2177],[85,97,143,2157,2177,2214],[97,143,2086,2157,2207],[97,143,2080,2157],[85,97,143,2124,2138,2139,2141,2157],[97,143,2096,2104,2107,2124],[85,97,143,2120,2180],[85,97,143,2073,2074,2075,2078,2079,2080,2086,2087,2088,2091,2109,2113,2120,2154,2155,2156,2214],[97,143,2081],[97,143,2708,2741,2742],[97,143,2743],[97,143,2696,2697],[97,143,2658,2662,2667,2668,2696],[97,143,547,579,580],[97,143,173,191,405],[97,143,537],[97,143,2664],[97,110,114,143,184],[97,110,143,173,184],[97,105,143],[97,107,110,143,181,184],[97,143,162,181],[97,105,143,191],[97,107,110,143,162,184],[97,102,103,106,109,143,154,173,184],[97,110,117,143],[97,102,108,143],[97,110,131,132,143],[97,106,110,143,176,184,191],[97,131,143,191],[97,104,105,143,191],[97,110,143],[97,104,105,106,107,108,109,110,111,112,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,132,133,134,135,136,137,143],[97,110,125,143],[97,110,117,118,143],[97,108,110,118,119,143],[97,109,143],[97,102,105,110,143],[97,110,114,118,119,143],[97,114,143],[97,108,110,113,143,184],[97,102,107,110,117,143],[97,143,173],[97,105,110,131,143,189,191],[97,143,2662,2666],[97,143,2657,2662,2663,2665,2667],[97,143,3321,3322,3323,3324,3325,3326,3327,3329,3330,3331,3332,3333,3334,3335,3336],[97,143,3323],[97,143,3323,3328],[97,143,2659],[97,143,2660,2661],[97,143,2657,2660,2662],[97,143,2072],[97,143,2095],[97,143,591,592],[97,143,591],[97,143,543],[97,143,154,155,157,158,159,162,173,181,184,190,191,543,544,545,547,548,550,551,552,572,573,577,578,579,580],[97,143,543,544,545,549],[97,143,545],[97,143,576],[97,143,547,580],[97,143,542,611,1191],[97,143,584,603,604,1191],[97,143,534,541,584,596,597,1191],[97,143,606],[97,143,585],[97,143,534,542,584,586,596,605,1191],[97,143,589],[97,143,146,155,173,534,539,541,580,584,586,589,590,593,596,598,599,602,605,607,608,610,1191],[97,143,584,603,604,605,1191],[97,143,580,609,610],[97,143,584,586,593,596,598,1191],[97,143,189,599],[97,143,146,155,173,534,539,541,580,584,585,586,589,590,593,596,597,598,599,602,603,604,605,606,607,608,609,610,1191],[97,143,585,586],[97,143,146,155,173,189,533,534,539,541,542,580,584,585,586,589,590,593,596,597,598,599,602,603,604,605,606,607,608,609,610,1190,1191,1192,1193,1198],[97,143,2020,2021],[97,143,2018,2019,2020,2022,2023,2028],[97,143,2019,2020],[97,143,2028],[97,143,2029],[97,143,2020],[97,143,2018,2019,2020,2023,2024,2025,2026,2027],[97,143,2018,2019,2030],[97,143,1255],[97,143,1255,1258],[97,143,1248,1255,1256,1257,1258,1259,1260,1261,1262],[97,143,1263],[97,143,1255,1256],[97,143,1255,1257],[97,143,1201,1203,1204,1205,1206],[97,143,1201,1203,1205,1206],[97,143,1201,1203,1205],[97,143,1201,1203,1204,1206],[97,143,1201,1203,1206],[97,143,1201,1202,1203,1204,1205,1206,1207,1208,1248,1249,1250,1251,1252,1253,1254],[97,143,1203,1206],[97,143,1200,1201,1202,1204,1205,1206],[97,143,1203,1249,1253],[97,143,1203,1204,1205,1206],[97,143,1264],[97,143,1205],[97,143,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],[97,143,164,226],[85,97,143,226,1091,1199,1351,1607,2785],[85,97,143,226,617,1020,1021,1022,1024,1029,1030,1091,1095,1266,1267,1293,1301,1384,1389,1460,1906,2033,2781],[97,143,226,1199,1267],[97,143,226,624,1266],[97,143,226,1265],[97,143,226,1199,1351,1384,1385,1607,2784,2790],[85,97,143,226,1020,1024,1077,1099,1301,1313,1385,1952,2752,2783],[97,143,226,1021,1022,1024,1029,1030,1069,1265,1301,1389,1460,1906,2781],[97,143,226,1199,1384,1607,2783,2790],[85,97,143,226,617,1020,1095,1384,1388,2033,2782],[97,143,226,1199,1351,1384,1607,2788,2790],[85,97,143,226,1020,1023,1024,1087,1094,1189,1384,1387,2749,2760,2784,2785,2787],[85,97,143,226,1024,1147,1149,1161,1189,2786],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1189,1314],[97,143,226,1094,2788],[97,143,226,1199,1351,1607,2817],[85,97,143,226,617,1020,1021,1024,1029,1077,1094,1095,1151,1187,1265,1301,1906,1908,2033,2795,2796,2797,2799,2807,2809,2810,2813,2814,2815,2816],[97,143,226,1094,1380,2817],[97,143,226,1199,1351,2745],[85,97,143,226,1187,1199,1351,1607,2825],[85,97,143,226,617,1020,1021,1022,1024,1026,1028,1029,1030,1032,1035,1069,1079,1081,1083,1094,1095,1099,1176,1187,1269,1270,1306,1313,1903,1915,1918,1919,2769,2798,2820,2822,2823,2824],[85,97,143,226,1187,1199,1351,1607,2790,2823],[85,97,143,226,1020,1021,1022,1024,1035,1079,1080,1099,1156,1187,1270,1313,1414,1908],[85,97,143,226,1199,1272,1351,2790,2827],[85,97,143,226,1272],[97,143,226,1199,1270],[97,143,226,1187],[85,97,143,226,1020,1021,1022,1024,1029,1030,1069,1079,1269,2820,2821],[85,97,143,226,1187,1199,1351,1607,2828],[85,97,143,226,1187,1199,1272,1351,2828],[85,97,143,226,617,1019,1020,1021,1024,1028,1029,1032,1035,1069,1077,1187,1269,1270,1272,1273,1301,1313,1445,2774,2820,2822,2823,2824,2826,2827],[97,143,226,1187,1272],[85,97,143,226,1032,1199,1351,1607,2790,2826],[85,97,143,226,1020,1024,1032,1035],[85,97,143,226,1021,1024,1025,1029,1035,1069,1080],[85,97,143,226,1187,1199,1351,1607,2831],[85,97,143,226,617,1020,1024,1032,1087,1187,1272,1300,1908,2825,2828,2830],[97,143,226,1199,1272,1351,1607,2830],[85,97,143,226,1024,1035,1079,1147,1149,1161,1272,2829],[97,143,226,1019,1020,1024,1099,1147,1149,1161,1177,1272,1314],[85,97,143,226,1021,1269,2820],[85,97,143,226,1021,1022,1029,1030,1187,1269,2798,2820,2821],[97,143,226,1094,1503,2831],[97,143,226,1199,1351,2778],[85,97,143,226,518,1032,1094,1503,1923,2635,2777],[85,97,143,226,1094,2652,2778],[97,143,226,1199,1351,1607,2845],[85,97,143,226,1301,1324,2844],[85,97,143,226,1019,1024],[97,143,226,1094,1380,2845,2846],[85,97,143,226,1199,1351,1607,2849],[85,97,143,226,617,1020,1021,1024,1029,1030,1080,1095,1265,1274,1417,1906,2033],[85,97,143,226,616,1091,1199,1351,1607,2853],[85,97,143,226,617,1020,1024,1087,1094,1276,1301,1321,1323,1417,2749,2760,2849,2851,2852],[97,143,226,1199,1274],[97,143,226,616,1166,1199,1351,1416,1417,1607,2790,2851],[85,97,143,226,616,1021,1024,1027,1156,1161,1392,1416,1417,2850],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1314,1417,1652],[85,97,143,226,624,1199,1351,1607,2852],[85,97,143,226,617,1020,1021,1024,1029,1030,1069,1080,1095,1274,1417,1906],[97,143,226,1094,2853],[85,97,143,226,1199,1351,2790,2870],[85,97,143,226,617,1020,1024,1025,1077,1187,1301,1418,1964,2222,2755,2860,2864,2868,2869],[85,97,143,226,1199,1351,1607,2790,2860],[85,97,143,226,1020,1024,1301,2859],[85,97,143,226,1277,1278,2862],[85,97,143,226,1021,1022,1025,1069,1079,1277,1278,1906,2798],[97,143,226,1199,1277,1278],[97,143,226,1277],[97,143,226,1199,1351,1607,2864],[85,97,143,226,617,1020,1024,1069,1080,1082,1187,1277,1278,2861,2862,2863],[97,143,226,1199,1351,2861],[85,97,143,226,1030],[85,97,143,226,1280,1281,2865],[85,97,143,226,1021,1022,1069,1079,1280,1281,1906,2798],[85,97,143,226,1199,1280,1351,1607,2790,2867],[85,97,143,226,1030,1280],[97,143,226,1074,1199,1281],[97,143,226,1074,1176,1280],[97,143,226,617,1091,1187,1199,1351,1607,2868],[97,143,226,617,1074,1091,1187,1199,1351,1607,2868],[85,97,143,226,617,1020,1069,1176,1280,1281,1313,1434,2866,2867],[85,97,143,226,1199,1351,2869],[85,97,143,226,624,1020,1024,1077,2216,2222],[97,143,226,1094,2870],[85,97,143,226,1094,1187,1958],[97,143,226,1199,1283],[97,143,226,624],[85,97,143,226,616,1091,1162,1199,1283,1296,1351,2976],[85,97,143,226,616,1028,1030,1035,1077,1099,1151,1162,1166,1283,1286,1295,1296,1301,2755,2974,2975],[97,143,226,1199,1285,1295,1351,2971],[85,97,143,226,1024,1035,1077,1151,1166,1286,1295,1301,2755],[97,143,226,1187,1199,1285,1286],[97,143,226,1166,1187,1285],[85,97,143,226,1091,1199,1351,2977],[85,97,143,226,1024,1295,1301,1369,2749,2881,2882,2883,2972,2976],[97,143,226,1199,1288],[97,143,226,1199,1351,2972],[85,97,143,226,617,1187,1295,2970,2971],[97,143,226,1199,1351,1607,2883],[85,97,143,226,617,1020,1021,1024,1029,1035,1077,1079,1187,1265,1288,1313,1906,2033],[85,97,143,226,616,1199,1298,1351,1445,1607,2974],[85,97,143,226,616,1020,1021,1026,1027,1030,1077,1094,1099,1151,1162,1286,1298,1445,1463,2973],[85,97,143,226,1162,1199,1283,1351,2975],[85,97,143,226,1077,1106,1108,1110,1162,1283,2222],[97,143,226,1187,1199,1285,1351,1607,2882],[85,97,143,226,1077,1187,1286,1295,1301,1369,2222,2755,2757],[97,143,226,1199,1296],[97,143,226,1187,1293,1295],[97,143,226,1199,1295,1351],[97,143,226,1187,1199,1295,1351],[85,97,143,226,1087,1187,1285,1294],[97,143,226,1199,1298],[97,143,226,617,624,1091,1094,1293],[97,143,226,1094,2977],[85,97,143,226,1199,1302,1310,1351,1607,2790],[85,97,143,226,1020,1021,1024,1025,1029,1035,1083,1302,1305,1306],[97,143,226,1199,1302,1308,1351,1607,2790],[85,97,143,226,1199,1302,1305,1308,1351,1607,2790],[85,97,143,226,1020,1021,1023,1024,1025,1029,1035,1302,1305,1306],[85,97,143,226,1199,1330,1351,1607,2790],[85,97,143,226,1020,1024,1079,1080,1082,1095,1300,1301,1302,1307,1308,1309,1310,1319,1320,1325,1327,1328,1329],[85,97,143,226,1199,1325,1351,1607,2790],[85,97,143,226,1021,1027,1324],[97,143,226,1302,1307,1308,1309,1310,1325,1326,1327,1328,1330],[85,97,143,226,1199,1311,1319,1351,1607,2790],[85,97,143,226,1020,1021,1024,1026,1083,1151,1311,1317,1318],[85,97,143,226,1199,1302,1311,1317,1351,1607,2790],[85,97,143,226,1020,1024,1028,1077,1099,1151,1166,1302,1311,1313,1316],[85,97,143,226,1199,1311,1315,1316,1607,2790],[85,97,143,226,1020,1024,1311,1314,1315],[97,143,226,1199,1302,1311,1315],[97,143,226,1166,1302,1311],[97,143,226,1302],[97,143,226,1199,1302,1311,1318,1351],[85,97,143,226,1187,1302,1311],[85,97,143,226,1199,1307,1351,1607,2790],[85,97,143,226,1020,1021,1024,1302,1303,1305,1306],[97,143,226,1199,1326],[97,143,226,1305],[85,97,143,226,1199,1305,1309,1351,1607,2790],[97,143,226,617,1187,1199,1329,1351],[85,97,143,226,617,1187],[97,143,226,617,1199,1327,1351],[85,97,143,226,617,1187,1302,1305,1326],[97,143,226,617,1199,1328,1351],[97,143,226,1094,1331],[97,143,226,1199,1351,1607,3063],[85,97,143,226,1020,1022,1024,1026,1082,1095],[97,143,226,1199,1351,1607,3074],[85,97,143,226,1020,1021,1022,1024,1027,1030,1079],[97,143,226,1091,1199,1351,1607,3066],[85,97,143,226,1020,1024,1091,1099,1176,1187,1301,1313,1975,3063,3064,3065],[97,143,226,1091,1187,1199,1351,3069],[85,97,143,226,1187,1964,2755,3066,3068],[97,143,226,1091,1187,1199,1351,1607,3068],[85,97,143,226,1020,1024,1091,1147,1149,1161,1187,1313,1975,2749,3063,3065,3067],[85,97,143,226,1199,1351,2790,3067],[85,97,143,226,1077,2222],[97,143,226,1199,1351,2790,3071],[97,143,226,1094,1369,3069,3070],[85,97,143,226,1187,1199,1351,1607,2790,3018],[85,97,143,226,1199,1351,2790,3018],[85,97,143,226,617,1020,1021,1022,1025,1029,1030,1035,1069,1076,1095,1187,1306,1313,1358,3009,3010,3011,3012,3013,3014,3016,3017],[85,97,143,226,1020,1024,1030,1099,1147,1149,1161,1361],[85,97,143,226,1187,1199,1351,1607,3009],[85,97,143,226,1029,1030,1077,1079,1187,1649,3008],[85,97,143,226,1020,1024,1025,1030,1077,1080,1099,1147,1149,1161,1187,1361],[97,143,226,1199,1607,2790,3010],[85,97,143,226,617,1020,1024,1077,1187,1313,3002,3003,3004,3005,3006,3007,3009],[97,143,226,1199,2790,3022],[85,97,143,226,1077,1099,3005,3006,3021],[97,143,226,1199,1351,1607,3023],[85,97,143,226,1024,1028,1908,3009,3010,3022],[97,143,226,1199,1607,2790,3005,3006,3007,3021],[97,143,226,1199,1351,1607,3003],[85,97,143,226,1020,1021,1030,1095,1361],[97,143,226,1199,1351,1607,3004],[85,97,143,226,1020,1021,1022,1030,1095,1361],[85,97,143,226,1020,1024,1030,1147,1149,1161,1361],[97,143,226,1199,1351,1607,3002],[85,97,143,226,1020,1025,1030,1095,1361],[85,97,143,226,1199,1351,1607,1649],[85,97,143,226,1025],[85,97,143,226,1199,1351,1607,3008],[85,97,143,226,1021],[97,143,226,1187,1199,1351,1362,1607],[85,97,143,226,617,1020,1021,1022,1024,1025,1030,1079,1080,1095,1187,1313],[97,143,226,1362],[97,143,226,1199,1351,1359,1607,3030],[85,97,143,226,1023,1024,1359,3028,3029],[97,143,226,1199,1351,1359,1607,3028],[85,97,143,226,1024,1306,1359],[97,143,226,1199,1359],[97,143,226,1358],[97,143,226,1199,1351,1359,3029],[85,97,143,226,1020,1024,1306,1357,1359,3018],[97,143,226,1187,1199,1351,1607,2790,3024],[97,143,226,1187,1199,1351,1607,3024],[85,97,143,226,617,1020,1021,1022,1024,1028,1029,1030,1035,1069,1077,1099,1166,1187,1301,1306,1358,1362,3011,3012,3013,3016,3017,3023],[97,143,226,1199,1358],[97,143,226,530],[85,97,143,226,1020,1021,1030,1076,1889,2798,3011],[85,97,143,226,1021,1029,1030,1076,1084,1187,1313,1358,1889,2798,3011],[97,143,226,1199,1351,1607,2042,3020],[85,97,143,226,1024,1147,1149,1161,2042,3019],[85,97,143,226,1024,1029,1030,1035,1069],[85,97,143,226,1187,1199,1351,1358,3032],[85,97,143,226,617,1019,1020,1024,1087,1187,1301,1314,1358,1363,2042,2760,3018,3020,3024,3027,3030,3031],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1306,1314,1358,2042],[97,143,226,1199,1351,1607,3026],[85,97,143,226,617,1020,1022,1024,1035,1313,3025],[97,143,226,1199,1351,1607,3027],[85,97,143,226,617,1023,1024,1077,1187,1313,1358,2042,3026],[97,143,226,1199,1351,1607,3025],[85,97,143,226,617,1020,1024,1077],[85,97,143,226,1020,1021,1023,1024,1025,1029,1030,1069,3011],[97,143,226,1199,1351,2042,3015],[85,97,143,226,1020,1024,1025,1030,1035,1099,1156,2042],[97,143,226,1199,1351,3016],[85,97,143,226,2042,3015],[97,143,226,1094,1187,1199,1351,1607,2790,3031],[97,143,226,1094,1187,1199,1351,2790,3031],[85,97,143,226,617,1020,1021,1022,1024,1029,1030,1035,1087,1094,1095,1187,1265,1414,1415,1441,1903,1906,2033,2303],[85,97,143,226,1199,1351,1607,3017],[85,97,143,226,1020,1021,1022,1024,1028,1030,1035,1077],[97,143,226,1094,3032],[97,143,226,1087,1091,1094,1187,1384],[85,97,143,226,1091,1094,1187,1199,1351,1384],[97,143,226,1087,1091,1092,1094,1187],[97,143,226,1091,1094,1187,1384],[85,97,143,226,1091,1187,1199,1272,1351,1389],[97,143,226,1087,1091,1092,1094,1187,1272],[97,143,226,1091,1092,1187],[97,143,226,1091,1187],[97,143,226,1199,1392],[97,143,226,1147,1149],[85,97,143,226,624,1091,1092,1094,1147,1149,1187,1392,1416],[97,143,226,1199,1351,1418],[97,143,226,624,1094,1293],[85,97,143,226,1091,1199,1351,1420],[85,97,143,226,1091,1199,1351,1422],[85,97,143,226,1091,1199,1351,1424],[85,97,143,226,1091,1199,1351,1426,1427],[97,143,226,1091,1092,1187,1426],[97,143,226,1092,1199],[85,97,143,226,1091,1147,1149,1199,1351,1416],[85,97,143,226,624,1091,1147,1149,1414,1415],[97,143,226,1091,1430,1431],[97,143,226,1091,1092,1094,1430],[97,143,226,1074,1091,1092,1094,1187],[85,97,143,226,1091,1187,1199,1351,1435],[97,143,226,1091,1092,1094,1187],[97,143,226,1199,1351,1437],[97,143,226,624,1087,1094,1293],[85,97,143,226,1091,1187,1199,1351,1439],[85,97,143,226,1091,1187,1199,1351,1443],[97,143,226,1032,1091,1094,1187,1445],[85,97,143,226,1032,1091,1199,1351,1445],[97,143,226,1032,1091,1092,1094,1187],[97,143,226,1091,1094,1187,1445],[85,97,143,226,1091,1187,1199,1351,1449],[97,143,226,1091,1094,1187],[85,97,143,226,1091,1094,1187,1199,1351,1456],[85,97,143,226,1091,1094,1187,1199,1351,1458],[85,97,143,226,1091,1092,1094,1187],[85,97,143,226,1091,1094,1187,1199,1351,1460],[97,143,226,1073,1091,1092,1094,1187],[85,97,143,226,1091,1187,1199,1351,1463],[85,97,143,226,1091,1162,1187,1199,1351],[85,97,143,226,1091,1187,1199,1351,1466],[97,143,226,1091,1092,1093,1187],[85,97,143,226,1091,1187,1199,1351,1367],[85,97,143,226,1091,1199,1351,1469,1470],[97,143,226,1091,1094,1187,1469],[85,97,143,226,1091,1199,1351,1469,1472],[85,97,143,226,1091,1199,1351,1469,1474],[97,143,226,1087,1091,1094,1187,1469],[85,97,143,226,1091,1199,1351,1469],[85,97,143,226,1091,1199,1351,1469,1477],[85,97,143,226,1091,1187,1199,1351,1479],[85,97,143,226,1091,1199,1351,1481],[97,143,226,1091,1092,1379],[85,97,143,226,1091,1199,1351,1483],[97,143,226,1091,1092,1094,1187,1486],[97,143,226,1199,1351,1488],[97,143,226,1199,1351,1490],[97,143,226,1094,1293,1488],[85,97,143,226,1091,1187,1199,1351,1492],[85,97,143,226,1091,1187,1199,1351,1494],[85,97,143,226,1091,1094,1199,1351,1496],[97,143,226,1091,1094,1187,1481],[85,97,143,226,623,1091,1187,1199,1351,1499],[97,143,226,623,1091,1092,1094,1187],[97,143,226,1199,1501],[97,143,226,616,1091,1092,1094,1187],[85,97,143,226,1032,1091,1187,1188,1199,1351,1503],[97,143,226,1032,1087,1091,1092,1094,1187,1188],[85,97,143,226,1091,1093,1187,1199,1351],[85,97,143,226,1091,1187,1199,1351,1506,1507],[97,143,226,1506],[85,97,143,226,1091,1187,1199,1351,1506],[85,97,143,226,1091,1187,1199,1351,1510],[85,97,143,226,1091,1094,1199,1351],[85,97,143,226,620,622,1086,1091,1094,1187,1199,1351],[85,97,143,226,620,622,1086,1087,1093,1187],[97,143,226,1094,1366,1368],[85,97,143,226,1370],[97,143,226,1199,1351,1370,1373],[97,143,226,1199,1351,1370,1375],[97,143,226,1187,1199,1351,1368],[97,143,226,1087,1094,1367],[97,143,226,620,1086,1380],[97,143,226,1091,1187,1512],[85,97,143,226,1091,1187,1199,1351,1514],[85,97,143,226,1091,1187,1199,1351,1516],[97,143,226,1199,1351,1382,1383],[85,97,143,226,518,1382],[85,97,143,226,1032,1094,1188],[97,143,226,1187,1199,1351,2635,2745],[85,97,143,226,518,616,1178,1187,1947,2635,2645,2649,2651,2652,2653,2654,2655,2656,2744],[97,143,226,1094,3093],[97,143,226,1094,3113],[85,97,143,226,1021,1024,1035,1071,1526,2015,2798],[97,143,226,619,1187,1199,1351,1607,1608,3139],[97,143,226,1187,1199,1351,1607,1608,3139],[85,97,143,226,530,617,619,1020,1021,1024,1030,1035,1069,1071,1073,1080,1087,1095,1187,1313,1518,1520,1521,1526,1528,2015,2798,3119,3120,3122,3123,3125,3126,3127,3128,3129,3130,3131,3132,3134,3135,3136,3137,3138],[97,143,226,618,1199,1518],[97,143,226,618,1073],[97,143,226,1199,1521],[97,143,226,1073,1520],[85,97,143,226,1024,1035,1071,1073,1079,1526],[97,143,226,1073,1523],[97,143,226,1073,1199,1520,1521,1523,1524],[97,143,226,1073,1520,1521],[85,97,143,226,1069,1071,1199,1351,1607,3136],[85,97,143,226,1020,1021,1023,1024,1030,1035,1069,1071,1526,1528,2015],[85,97,143,226,1021,1022,1024,1035,1071,1076,1526,2015,2798],[97,143,226,3151,3156],[85,97,143,226,1199,1351,1607,3140],[85,97,143,226,1020,1024,1027,1077,1079,1166,1187,1301,1908],[85,97,143,226,1199,1351,1607,3129],[85,97,143,226,1020,1024,1077,1080,1313,1908],[97,143,226,1073,1187,1199,1351,1607,3148],[85,97,143,226,1019,1020,1023,1024,1073,1095,1150,1187,1304,3139],[97,143,226,1199,1351,1607,3128],[85,97,143,226,1023,1024,1035,1073,1077,1080,1099],[97,143,226,1199,1351,3143],[85,97,143,226,1073],[85,97,143,226,1073,1187,1199,1351,3142],[85,97,143,226,617,618,1187,1199,1351,1607,1608,3142],[85,97,143,226,617,618,619,1020,1021,1022,1024,1030,1035,1069,1071,1073,1076,1187,1301,1520,1523,1526,1528,1908,2015,2583,2798,3122,3123,3125,3126,3127,3128,3130,3131,3132,3135,3136,3137],[97,143,226,1073,1199,1351,1607,3144],[85,97,143,226,618,1020,1024,1073,1077,1099,1166,1301,1520,3142,3143,3157],[85,97,143,226,1091,1187,1199,1351,1607,3151],[85,97,143,226,617,618,1020,1023,1024,1030,1035,1073,1087,1091,1099,1187,1300,1301,1313,1458,1460,2291,3116,3118,3139,3140,3141,3144,3146,3147,3148,3149,3150],[85,97,143,226,1199,1351,3130],[85,97,143,226,1019,1020,1021,1022,1023,1024,1077,1099,1156,1313,1520,1917],[97,143,226,619,1091,1187,1199,1351,3156],[85,97,143,226,618,619,1019,1020,1023,1024,1073,1077,1091,1099,1187,1304,1313,2291,2583,3153,3154,3155],[97,143,226,1071,1199,1526],[85,97,143,226,1069,1071],[85,97,143,226,1069,1071,1199,1351,1528],[97,143,226,1069,1071],[85,97,143,226,1069,1071,1351],[85,97,143,226,1199,1351,1607,3135],[85,97,143,226,530,1019,1023,1024,1035,1306],[97,143,226,1187,1199,1351,1607,3147],[85,97,143,226,1020,1021,1024,1077,1099,1187,1313,2846],[85,97,143,226,1199,1351,1607,3132,3164],[85,97,143,226,1020,1023,1024,1029,1035,1069,1071,1073,1076,1079,1080,1526,1528,1908,2015],[85,97,143,226,1073,1186,1199,1351,3141],[85,97,143,226,1019,1020,1024,1035,1073,1099,1306,1314,1520],[97,143,226,1073,1199,3115],[97,143,226,1073],[85,97,143,226,617,1024,1073,1187,3115],[85,97,143,226,1073,1091,1187,1199,1351,1460,1462,1607,3118],[85,97,143,226,617,1020,1021,1023,1024,1029,1073,1091,1095,1147,1149,1161,1187,1265,1313,1460,1462,1906,2033,3117],[97,143,226,1073,1161,1199,1351,1607,3117],[97,143,226,1019,1020,1024,1073,1147,1149,1161,1166,1177,1187,1314],[97,143,226,1199,1529],[97,143,226,1073,1521],[85,97,143,226,1199,1351,3122,3164],[85,97,143,226,1020,1021,1022,1024,1030,1035,1071,1073,1076,1526,2015,2798,3121],[85,97,143,226,1021,1024,1035,1069,1071,1076,1079,1526],[85,97,143,226,1021,1024,1035,1071,1073,1526,1528,2015,3133],[97,143,226,1187,1199,1351,1607,3133],[85,97,143,226,1019,1187,1313],[85,97,143,226,1199,1351,3125,3164],[85,97,143,226,1020,1027,1071,1073,1156,1526,2798,3124],[85,97,143,226,1022,1024,1035,1071,1526,2015],[97,143,226,1199,1351,1607],[85,97,143,226,1024,1030,1035,1071,1526],[85,97,143,226,1021,1024,1030,1035,1069,1071,1076,1526,2015,2798],[85,97,143,226,1020,1021,1022,1024,1029,1030,1035,1069,1073,1313,1609,1906],[97,143,226,1073,1199,1609],[97,143,226,1069,1073],[85,97,143,226,1073,1199,1351,1607,3153],[85,97,143,226,617,1020,1024,1035,1073,1304,1609,3152],[97,143,226,1073,1199,1351,3123],[85,97,143,226,1024,1073,1908],[85,97,143,226,1073,1091,1187,1199,1351,1607,3150],[85,97,143,226,617,1020,1024,1029,1073,1091,1095,1099,1176,1187,1265,1313,1906,1908,2033,2798],[97,143,226,1199,1520],[97,143,226,1094,3157],[85,97,143,226,1187,1199,1351,1607,3189],[85,97,143,226,1154,1187],[85,97,143,226,1187,1199,1351,1607,3190],[85,97,143,226,1020,1021,1022,1024,1029,1035,1095,1187,1265,1906,2033],[85,97,143,226,1147,1149,1187,1199,1351,1607,3192],[85,97,143,226,1024,1147,1149,1161,1187,3191],[97,143,226,1019,1020,1024,1147,1149,1177,1187,1314],[85,97,143,226,1091,1187,1199,1351,1607,3193],[85,97,143,226,617,1020,1024,1091,1147,1149,1187,1414,1415,2760,3189,3190,3192],[97,143,226,1199,1351,2790,3194],[97,143,226,1094,1369,2846,3070,3193],[97,143,226,1087,1094,3216,3217],[97,143,226,1091,1094,1199,1351,1607,3243,3245],[85,97,143,226,617,1024,1091,1094,1147,1149,1162,1187,1414,1463,1503,1625,2057,2760,3242,3243,3244],[97,143,226,1199,1351,1607,2057,3244],[85,97,143,226,1019,1020,1024,1026,1030,1147,1149,1161,2057,2640,3243],[97,143,226,1199,1619,1621],[97,143,226,1108,1162,1187,1619,1620],[97,143,226,1199,1607,2790,3252],[85,97,143,226,617,1020,1024,1095,1162,1187,1616,1620,1621,2760,3249,3251],[85,97,143,226,1147,1149,1161,1177,1621,3250],[85,97,143,226,1019,1020,1024,1099,1147,1149,1161,1177,1314,1621,1623],[97,143,226,1199,1623],[97,143,226,1199,1351,1607,3283],[85,97,143,226,1020,1021,1024,1027,1030],[97,143,226,1020,1024,1079,1099,1147,1149,1161,1166,1177,2057,2751,2962,3226],[97,143,226,1199,1351,3288],[85,97,143,226,1094,1463,3287],[97,143,226,1199,1351,1613,1616],[85,97,143,226,1615],[97,143,226,1091,1199,1351,1607,3290],[85,97,143,226,1020,1024,1087,1091,1094,1301,1503,1506,1616,1618,1620,1950,3219,3227,3241,3246,3253,3262,3267,3278,3282,3284,3286,3289],[97,143,226,1187,1199,1607,2790,3262],[85,97,143,226,1069,1071,1091,1094,1305,1435,1463,1503,3257,3261],[85,97,143,226,1616,1618,3245],[97,143,226,1087,1094,1503,1506,1620,3252],[97,143,226,1199,1351,1613,3282],[85,97,143,226,1094,1147,1149,1162,1463,1503,1616,1625,3226,3281],[97,143,226,3266],[85,97,143,226,1094,1187,3285],[85,97,143,226,617,1094,1187,1485,1618,3283],[97,143,226,1094,3277],[97,143,226,3288],[85,97,143,226,1162],[97,143,226,1199,1625],[85,97,143,226,1199,1351,1607,2790,3301],[85,97,143,226,1020,1025,1030,1077,1151,1166,1174,1177,1187,1301,1366,2222,2755,2777,3299,3300],[97,143,226,1094,2846,3301],[85,97,143,226,1091,1199,1351,1613,3312,3314,3315],[85,97,143,226,617,1020,1091,1162,1187,1367,1615,2760,3307,3310,3312,3314],[85,97,143,226,1187,1199,1351,1607,3314],[85,97,143,226,1024,1147,1149,1161,1187,3313],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1187,1314],[97,143,226,1199,1351,1607,3307],[97,143,226,1024,3304,3305,3306],[97,143,226,1094,3315],[97,143,226,1199,1351,2779],[85,97,143,226,518,1086,1178,1187,2635,2652,2778],[85,97,143,226,1020,1024,1035,1080],[97,143,226,1199,1351,1607,3416],[85,97,143,226,1019,1021,1024,1035,1156,1954],[85,97,143,226,1199,1351,1607,1631,3438],[85,97,143,226,617,1020,1021,1022,1024,1030,1073,1076,1082,1187,1300,1301,1313,1324,1631,2292,3320,3437],[97,143,226,1199,1351,1636,3425],[85,97,143,226,1636],[97,143,226,1199,1351,3417],[85,97,143,226,1019,1020,1023,1024,1035],[97,143,226,1627],[85,97,143,226,506,1024,1636,3419],[85,97,143,226,617,1020,1024,1035,1629],[97,143,226,1199,1636,3419],[97,143,226,1636],[97,143,226,1199,1351,1627,1636,3433],[85,97,143,226,1024,1073,1321,1323,1627,1635,1636,1639,2700,3424,3425,3426,3427,3428,3429,3431,3432],[97,143,226,1082,1199,1351,1607,2790,3319,3437],[85,97,143,226,617,618,1020,1021,1024,1026,1030,1035,1073,1076,1082,1095,1187,1321,1323,1369,1414,1627,1628,1629,1631,1636,1637,1640,1920,1954,2769,2770,3149,3215,3319,3337,3338,3339,3340,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3428,3430,3433,3434,3435,3436],[97,143,226,1199,1351,1607,3427],[85,97,143,226,1020,1024,1080,1187,1321,1323],[85,97,143,226,617,1024,1035,1079],[97,143,226,1199,1351,1607,1628,3421],[85,97,143,226,1026,1628],[97,143,226,1082,1199,1627,3422],[97,143,226,1082,1627],[97,143,226,1199,1351,1607,3423],[97,143,226,1020,1024],[97,143,226,1199,1351,1607,3436],[85,97,143,226,1020,1021,1024,1030,1187,1628],[85,97,143,226,1024,1636,3430],[85,97,143,226,1020,1024,1080,1636],[85,97,143,226,617,1020,1024,1035,1079,1627],[97,143,226,1199,1629],[97,143,226,1199,1351,1607,3319,3443],[85,97,143,226,617,1020,1021,1024,1030,1035,1082,1414,1415,1631,1632,1635,1636,3319,3337,3340,3418,3419,3441,3442],[97,143,226,1199,1351,1607,1632,3441,3443],[85,97,143,226,1024,1028,1084,1156,1632,1920,1954,2769,3339,3439,3440,3443],[97,143,226,1199,1351,1636,3439],[85,97,143,226,1024,1321,1323,1635,1636,2700,3426,3429,3432],[97,143,226,1199,1351,1607,3442],[85,97,143,226,1020,1022,1024],[97,143,226,1199,1351,1607,3462],[85,97,143,226,1021,1026],[97,143,226,1199,1351,1607,1632,3440],[97,143,226,1025,1313,1632],[97,143,226,1199,1631,1632],[97,143,226,1631],[85,97,143,226,1024,1187,1369,1641,1969,2289,2770,3319],[97,143,226,1199,1351,1637],[85,97,143,226,1070,1073,1414,1635,1636],[85,97,143,226,1639],[97,143,226,1187,1636,3337],[97,143,226,1199,1635,3408],[97,143,226,617,1073,1187,1634,1635,1636,2052,3407],[97,143,226,1199,2961,3409],[97,143,226,617,1187,1628,2961],[97,143,226,1199,2961,3410],[97,143,226,617,1187,2961],[97,143,226,1199,3411],[97,143,226,617,1187],[97,143,226,1199,1351,3444],[85,97,143,226,1094,1301,1379,2846,3320,3437,3438,3443],[85,97,143,226,1187,1199,1351,1607,1641,2790,3479],[85,97,143,226,617,1020,1024,1028,1029,1035,1083,1094,1095,1187,1265,1313,1641,1642,1644,1906,2033,3477,3478],[97,143,226,1199,1351,1607,1641,2790,3473],[85,97,143,226,617,1020,1021,1022,1024,1026,1028,1029,1035,1076,1083,1094,1095,1099,1176,1187,1265,1313,1641,1906,1908,2033,2042],[85,97,143,226,1199,1351,1607,2790,3484],[85,97,143,226,1020,1021,1022,1024,1026,1035,1077,1095,1156,1187,1313],[85,97,143,226,1199,1351,1607,1641,2790,3476],[85,97,143,226,1024,1147,1149,1161,1641,3475],[97,143,226,1019,1020,1024,1147,1149,1161,1166,1177,1314,1641,3474],[97,143,226,1199,1642],[97,143,226,1641],[85,97,143,226,1199,1351,1607,2790,3482],[85,97,143,226,1020,1024,1028,1095,1099,1156,1358],[85,97,143,226,1187,1199,1607,1641,2790,3474],[85,97,143,226,1020,1024,1035,1099,1187,1641,1954],[97,143,226,1199,1351,2790,3477],[85,97,143,226,1024,1099,1908],[85,97,143,226,1199,1351,1607,2790,3485],[85,97,143,226,617,1020,1024,1087,1187,1301,1641,1908,2042,2294,2760,3470,3471,3472,3473,3476,3479,3480,3481,3482,3483,3484],[85,97,143,226,1199,1351,1607,1641,2042,2790,3471],[85,97,143,226,617,1020,1021,1024,1026,1030,1187,1313,1641,2042,2289],[97,143,226,1187,1199,1351,1607,1641,2790,3472],[85,97,143,226,1020,1024,1028,1077,1099,1150,1187,1641,1908,3471],[85,97,143,226,1187,1199,1351,1607,2790,3481],[85,97,143,226,617,1020,1024,1077,1099,1150,1156,1187],[85,97,143,226,1187,1199,1351,1607,2790,3480],[85,97,143,226,1020,1024,1025,1029,1069,1094,1099,1187,1313,1906,1908,3478],[85,97,143,226,1199,1351,1607,1641,2790,3470],[85,97,143,226,1024,1147,1149,1161,1641,3469],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1314,1641],[97,143,226,1199,1644],[85,97,143,226,1199,1351,1607,2790,3483],[85,97,143,226,1020,1021,1024,1026,1083,1095,1099,1187,1313],[97,143,226,1094,3485],[97,143,226,1199,1469,1607,2790,3506],[85,97,143,226,1020,1024,1077,1099,1174,1176,1313,1474,1503,1952,2222,2752,3502,3505],[97,143,226,1199,2790,3505],[85,97,143,226,1023,1024,1077,1147,1149,1445,3504],[97,143,226,1032,1199,1607,2790,3504],[85,97,143,226,1024,1032,1147,1149,1161,3503],[97,143,226,1032,1147,1149,1177,1179,2752],[97,143,226,1187,1199,1607,2790,3501],[97,143,226,1199,1607,2790,3501],[85,97,143,226,617,1020,1024,1095,1313,1470,1646,1924,1925,2033],[97,143,226,1187,1199,1469,1607,2790,3502],[97,143,226,1199,1469,1607,2790,3502],[85,97,143,226,617,1020,1024,1095,1313,1469,1477,1646,1924,1925,2033],[85,97,143,226,1199,1607,1646,1924,2033,2790],[85,97,143,226,1020,1021,1022,1023,1024,1026,1028,1029,1030,1032,1069,1079,1080,1081,1094,1187,1503,1646,1649,1906,1908,1923],[97,143,226,1199,1924,1925],[97,143,226,1924],[97,143,226,1199,1469,1607,1613,2790,3509],[85,97,143,226,1020,1023,1024,1469,1503,1615,2749,3501,3506,3508],[97,143,226,1199,1469,1607,1613,2790,3508],[85,97,143,226,1024,1147,1149,1161,1469,1615,3507],[97,143,226,1024,1099,1147,1149,1150,1161,1177,1469],[97,143,226,1094,3509],[85,97,143,226,617,1187,1199,1351,3526],[85,97,143,226,617,1020,1021,1024,1029,1030,1095,1187,1265,1313,1906,2033],[97,143,226,1187,1199,1351,1607,3546],[85,97,143,226,617,1020,1024,1030,1087,1187,1300,3523,3525,3526,3545],[97,143,226,1927,3544],[97,143,226,1199,1351,3535],[85,97,143,226,1024],[97,143,226,1199,1351,3540],[85,97,143,226,1020,1024,1930,1931,3534,3537,3538,3539],[97,143,226,1199,1351,3536],[85,97,143,226,1024,1321,1323,1635,1930,2700],[97,143,226,1199,1351,3539],[85,97,143,226,1199,1351,3537],[85,97,143,226,1024,1930,3535,3536],[97,143,226,1635],[85,97,143,226,617,1187,1635,1928,1930],[97,143,226,1199,1351,3534],[97,143,226,1199,1351,3532],[85,97,143,226,1077,3531],[85,97,143,226,1927,1928],[85,97,143,226,617,1187,1927,1928,3527,3528,3529,3530,3532,3533,3540,3541,3542,3543],[97,143,226,1199,1351,3529],[85,97,143,226,1020,1021,1024,1095,1885],[97,143,226,1199,1351,1607,3524],[85,97,143,226,617,1020,1024,1030,1095,1301,1321,1323],[97,143,226,1199,1351,3528],[85,97,143,226,1020,1021,1024,1030,1099,3524],[97,143,226,1199,1351,3533],[85,97,143,226,1020,1024,1030,1077,1927,3531],[97,143,226,1199,1351,3541],[85,97,143,226,1020,1021,1024,1095],[97,143,226,1199,1351,1927,3530],[85,97,143,226,1020,1024,1077,1927],[97,143,226,1199,1927,1928],[97,143,226,1927],[97,143,226,1095,1187,1199,1607,2790,3543],[85,97,143,226,1020,1024,1099,1150,1187],[85,97,143,226,1187,1199,1351,1607,3525],[85,97,143,226,617,1020,1024,1077,1095,1099,1151,1166,1187,1301,3521,3524],[97,143,226,1187,1928],[97,143,226,1187,1199,1351,1607,3523],[85,97,143,226,1024,1147,1149,1161,1187,3521,3522],[97,143,226,1019,1020,1024,1147,1149,1161,1166,1177,1187,1305,1314,3521],[97,143,226,1199,1351,3527],[85,97,143,226,1020,1095],[97,143,226,1199,1351,3531],[85,97,143,226,1020,1021,1022,1024,1099,1954],[97,143,226,1094,2846,3546],[97,143,226,1187,1199,1607,2790,2970],[85,97,143,226,1020,1021,1023,1024,1030,1077,1079,1151,1177,1187,1301,2884,2964,2969],[97,143,226,1094,2970],[97,143,226,1091,1187,1199,1351,1607,3572],[97,143,226,1199,1351,3572],[85,97,143,226,530,617,1020,1021,1022,1024,1025,1029,1035,1069,1087,1091,1095,1187,1265,1306,1313,1906,1932,2033,2798,3570,3571],[97,143,226,3577],[97,143,226,617,1187,1199,1351,1607,3570],[85,97,143,226,617,1020,1024,1028,1187,1313],[97,143,226,1199,1932],[97,143,226,1087,1091,1187,1199,1351,1607,3571,3577],[85,97,143,226,617,1020,1021,1022,1029,1030,1087,1091,1095,1187,1265,1313,1906,1932,2033,2760,2798,3571,3572,3574,3576],[97,143,226,1199,1351,1607,2790,3571,3574],[85,97,143,226,1024,1147,1149,1161,3571,3573],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1314,3571],[97,143,226,617,1187,1199,1351,1607,3575],[85,97,143,226,617,1020,1021,1024,1077,1187,1313],[97,143,226,1166,1199,1351,1607,3571,3576],[85,97,143,226,1020,1024,1077,1166,3571,3575],[97,143,226,1094,3578],[85,97,143,226,617,1187,1199,1351,2790,3588],[85,97,143,226,617,1020,1021,1022,1024,1025,1029,1034,1035,1095,1187,1265,1313,1906,2013,2033],[97,143,226,1034,1187,1199,1351,1607,3591],[85,97,143,226,617,1020,1034,1087,1187,1300,3211,3588,3590],[97,143,226,1034,1199,1351,1607,3590],[85,97,143,226,1024,1034,1147,1149,1161,3589],[97,143,226,1019,1020,1024,1034,1099,1147,1149,1161,1166,1177,1314,2013],[97,143,226,1094,3591],[97,143,226,1199,1351,1607,3599],[85,97,143,226,1020,1021,1022,1024,1029,1035,1076,1080,1095,1265,1652,1889,1906,2033],[97,143,226,1187,1199,1351,1607,3600],[85,97,143,226,617,623,1020,1024,1187,2760,3596,3598,3599],[97,143,226,623,1187,1199,1351,1607,3596],[85,97,143,226,617,623,1020,1021,1022,1024,1029,1035,1076,1077,1080,1081,1099,1166,1187,1265,1652,1889,1906,1923,2033],[97,143,226,623,1177,1199,1351,1607,3598],[85,97,143,226,623,1024,1147,1149,1161,3597],[97,143,226,623,1019,1020,1024,1099,1147,1149,1161,1177,1314],[97,143,226,1094,3600],[97,143,226,1094,3612],[97,143,226,1094,3619],[97,143,226,1094,3621],[97,143,226,617,1187,1199,1351,1607,3621],[85,97,143,226,617,1020,1022,1024,1077,1187,1313],[97,143,226,1094,3624],[97,143,226,617,1199,1351,1607,3624],[85,97,143,226,617,1020,1021,1027,1077,1187,1313,1947],[97,143,226,1199,1285,1351,2790,3634],[85,97,143,226,1077,1285,2222],[97,143,226,1199,1285,1351,2790,3635],[97,143,226,1199,2790,3636],[85,97,143,226,1147,1149,1161,1174,1177,1285],[97,143,226,1199,1351,3637],[85,97,143,226,1285,3634,3635,3636],[85,97,143,226,1187,1199,1351,1516,1607,3641],[85,97,143,226,1024,1035,1077,1147,1149,1161,1166,1177,1187,1285,1294,1301,1306,1366,1936,1937,1964,1965,1973,1991,2222,2750,2881,3300,3628,3630,3637,3638,3639,3640],[97,143,226,1285],[97,143,226,1199,1937],[97,143,226,1166],[97,143,226,1199,1351,3642],[85,97,143,226,1024,1035,1077,1079,1147,1149,1161,1166,1177,2222,2962,3631],[97,143,226,1199,1351,1607,3640],[85,97,143,226,1161,1166,1177,1301,2222],[97,143,226,1199,1934],[97,143,226,1199,1351,2790,3643],[85,97,143,226,1020,1022,1025,1187,1313,2700],[85,97,143,226,1094,1187,1199,1351,1368,1389,1437,1514,1516,1607,2790,3645],[85,97,143,226,623,1020,1024,1032,1035,1077,1087,1094,1166,1187,1285,1294,1301,1366,1368,1389,1437,1514,1908,1934,1964,1973,1991,2222,2755,2881,3299,3300,3628,3629,3630,3631,3633,3637,3639,3640,3641,3642,3643,3644],[97,143,226,1199,1351,1607,2790,3644],[85,97,143,226,1024,1030,1087,1099,1366],[97,143,226,1199,1285,1294,1351],[85,97,143,226,1285],[97,143,226,1094,1367,1503,3645],[97,143,226,617,1187,1199,1607,2790,3658],[85,97,143,226,617,1028,1076,1077,1094,1095,1151,1156,1177,1187,1889,3657],[85,97,143,226,617,1091,1199,1351,1607,1941,2781,3660],[85,97,143,226,617,1020,1021,1026,1029,1030,1069,1077,1091,1150,1293,1503,1648,1906,1940,1941,2033,2781],[97,143,226,1199,1940,1941],[97,143,226,624,1265,1940],[97,143,226,1199,1940],[97,143,226,3664],[97,143,226,1199,1351,1607,2790,3657],[85,97,143,226,625,1020,1021,1022,1024,1029,1030,1031,1035,1076,1081,1087,1156,1265,1652,1906,1915,1918,2033,2046,2050],[85,97,143,226,1091,1199,1351,1607,2790,3664],[85,97,143,226,617,1020,1087,1091,1147,1149,1150,1187,1301,1414,1415,1615,1910,1911,2760,3658,3659,3660,3662,3663],[97,143,226,1199,1351,1607,3663],[97,143,226,1199,1351,1607,2054,3663],[85,97,143,226,617,1020,1024,1025,1029,1030,1035,1077,1087,1094,1095,1151,1166,1179,1187,1301,1460,1462,1652,1910,2054,2760,2764,3311,3657],[85,97,143,226,1147,1149,1187,1199,1351,1607,3662],[85,97,143,226,1021,1024,1026,1147,1149,1161,1187,3661],[97,143,226,1019,1020,1024,1099,1147,1149,1161,1166,1177,1187,1314],[97,143,226,1094,1503,3665],[97,143,226,1082,1187,1199,1351,1607,3683],[97,143,226,1187,1199,1351,1607,3683],[85,97,143,226,617,1020,1021,1022,1024,1029,1030,1035,1077,1111,1187,1306,1313,1908,3337,3674,3681,3682],[97,143,226,1111,1199,1351,1607,3681],[85,97,143,226,1024,1111,1161,3680],[97,143,226,1019,1020,1024,1111,1147,1149,1166,1177,1314],[97,143,226,1187,1199,1351,1607,3685],[85,97,143,226,617,1020,1024,1087,1111,1182,1187,1301,2292,2760,3676,3677,3679,3683,3684],[85,97,143,226,617,1111,1181,1187],[97,143,226,1181,1182,1199,1351,1607],[85,97,143,226,1024,1147,1149,1161,1180,1182],[97,143,226,1147,1149,1161,1177,1179,1182],[97,143,226,1082,1199,1351,3682],[85,97,143,226,1021,1024,1025,1029,1035,1082,1908],[97,143,226,1111,1199,1351,1607,3684],[85,97,143,226,1025,1077,1111,3678],[97,143,226,617,1187,1199,1351,1607,3679],[85,97,143,226,1187,1199,1351,1607,3679],[85,97,143,226,617,1020,1021,1022,1024,1025,1029,1030,1035,1077,1099,1111,1187,1265,1301,1305,1306,1906,2033,3674,3678],[97,143,226,617,1187,1199,1351,1607,3677],[97,143,226,1187,1199,1305,1351,1607,3674,3677],[85,97,143,226,617,1020,1021,1022,1023,1024,1025,1029,1030,1035,1069,1082,1095,1187,1265,1306,1906,1908,2033,3674],[97,143,226,1111,1199,1351,1607,3676],[85,97,143,226,1024,1111,1147,1149,1161,3675],[97,143,226,1019,1020,1024,1111,1147,1149,1161,1166,1177,1314,3674],[97,143,226,617,1187,1199,1351,1607,3678],[85,97,143,226,617,1020,1022,1024,1028,1077,1187,1313],[97,143,226,1094,3685],[97,143,226,1199,1351,2790,3702],[97,143,226,1094,1369,2846,3070,3700],[97,143,226,1199,1351,1607,3700],[85,97,143,226,1019,1020,1021,1024,1030,1035,1080,1147,1149,1154,1161,1187,1313],[97,143,226,2005,3710],[97,143,226,2005,3712],[85,97,143,226,518,2005,3714,3715],[97,143,226,1199,1351,3704],[85,97,143,226,518,1094,1178,1506,1947,2005,2010,2651],[97,143,226,2005,3717],[85,97,143,226,1199,1351,2004,3708],[85,97,143,226,518,617,1020,1021,1024,1082,1150,1305,1635,1948,1954,2003,2005,2010,3414,3706,3707],[97,143,226,2005,3719],[97,143,226,1199,1351,3721],[97,143,226,1094,1947,2651],[97,143,226,1199,1351,3723],[85,97,143,226,518,1094,3714,3715],[97,143,226,526,529,1322,2633,2634,2635,2636,2637],[97,143,226,1091,1093,1187,1199,1351,1607,3725],[97,143,226,620,622,1091,1093,1187,1199,1351,3725],[85,97,143,226,518,620,622,1020,1021,1024,1029,1030,1035,1077,1086,1093,1187,1265,1313,1453,1906,1908,2033,2293,2652,2798],[97,143,226,3725],[85,97,143,226,518,618],[85,97,143,226,518,3216],[85,97,143,226,518,3217],[85,97,143,226,1199,1351,3732],[85,97,143,226,1020,1024,1086,1908],[85,97,143,226,1199,1351,3736],[85,97,143,226,518,620,621,1187,1466,3732,3734,3735],[97,143,226,1199,1351,1607,3735],[85,97,143,226,1199,1351,1607,3735],[85,97,143,226,1019,1020,1021,1024,1029,1077,1265,1313,1906,1908,2033,2798],[85,97,143,226,1199,1351,3734],[85,97,143,226,1313],[85,97,143,226,518,3736],[85,97,143,226,1032,1166,1199,1285,1351,3628],[85,97,143,226,1024,1032,1077,1080,1166,1285,1963,1991,2222,3627],[85,97,143,226,1021,1027,1077,1079,1083,1084,1106],[97,143,226,617,1082,1110,1162,1187,1199,1607,2297,2630,2790,3247,3249],[85,97,143,226,617,1020,1021,1024,1029,1030,1035,1069,1077,1082,1087,1091,1095,1104,1105,1106,1107,1108,1109,1110,1162,1187,1265,1313,1620,1903,1906,1995,2033,2034,2297,3220,3247,3248],[97,143,226,1032,1069,1071,1094,1187,1199,1305,1607,2790,3261],[85,97,143,226,1020,1024,1026,1029,1030,1032,1035,1069,1071,1077,1079,1087,1094,1095,1187,1305,1439,1479,1499,1620,1903,1908,2015,2034,2962,3229,3254,3255,3256,3258,3259,3260],[97,143,226,1199,1351,3254,3865],[85,97,143,226,623,1021,1022,1024,1030,1032,1035,1070,1071,1076,1079,1080,1507,1920,2008,2015,2595,3223,3224,3229],[97,143,226,1187,1199,1995,2790,3220],[85,97,143,226,1024,1187,1995],[97,143,226,1110,1187,1199,1607,2630,2790,3248],[85,97,143,226,1020,1022,1024,1099,1110,1183,1187,1993],[97,143,226,1110,1993],[97,143,226,1110,1187],[97,143,226,1995],[97,143,226,1107,1110],[97,143,226,1078,1106,1107,1108,1109],[97,143,226,1199,1351,1607,3224],[85,97,143,226,1020,1024,1027,1030,1035,1889],[85,97,143,226,1021,1024,1026,1027,1030,1035,1076,1077,1079,1083,1097,1098,1101,1106],[97,143,226,1097,1106,1199,1607,2630,2790],[85,97,143,226,617,1020,1022,1024,1094,1095,1096,1106,1187],[97,143,226,1096],[97,143,226,1078,1199],[97,143,226,1109],[97,143,226,1106,1107,1108,1199],[97,143,226,1107,1109],[97,143,226,1106,1199,1607,2630,2790],[85,97,143,226,1023,1024,1026,1028,1030,1035,1076,1077,1079,1080,1082,1085,1100,1102,1103,1104,1105,1107,1108,1109],[85,97,143,226,1069,1071,1199,1351,1607,3255,3865],[85,97,143,226,1021,1035,1069,1071,1147,1149,1161,1305,2015],[85,97,143,226,1024,1035,1076],[97,143,226,617,1110,1187],[97,143,226,1199,3257],[97,143,226,617,1187,1305,2593],[97,143,226,1100,1106,1199],[97,143,226,1098,1100,1101,1102,1106,1199,1607,2630,2790],[85,97,143,226,1020,1021,1024,1027,1080,1084,1098,1099,1100,1106],[85,97,143,226,1020,1024,1030,1035,1076,1077,1078,1108],[97,143,226,1199,1305,1351,3256,3865],[85,97,143,226,1021,1069,1071,1076,1305,2015,3229],[97,143,226,617,1187,1199,1351,3257,3258],[85,97,143,226,617,1020,1024,1028,1187,3257],[97,143,226,1069,1071,1091,1199,1305,1351,3259,3865],[85,97,143,226,1020,1021,1022,1024,1030,1069,1071,1187,1305,1479,2015,2798,3229],[97,143,226,1199,1351,1607,2036],[85,97,143,226,1020,1021,1022,1024,1026,1027,1028,1035,1077,1080,1082,1099],[97,143,226,1105,1199,1607,2790],[85,97,143,226,1021,1024,1026,1035,1079,1082],[97,143,226,1107,1199],[97,143,226,1106,1109],[97,143,226,1103,1199],[85,97,143,226,1024,1030,1035,1108],[97,143,226,1187,1199,1607,2790,3273],[85,97,143,226,617,1020,1021,1024,1030,1035,1069,1077,1079,1095,1187,1265,1313,1889,1906,1908,2033,3268,3269,3270,3271,3272,3277],[97,143,226,1199,1351,1607,1650],[85,97,143,226,1076,1187],[97,143,226,1161,1199,1351,1607,3200],[97,143,226,1019,1020,1024,1099,1147,1149,1161,1166,1177,1314],[97,143,226,1187,1199,1351,3200,3201],[85,97,143,226,617,1019,1020,1024,1095,1099,1156,1187,3200],[97,143,226,1187,1199,1351,3202,3203],[85,97,143,226,617,1019,1020,1024,1095,1099,1156,1187,3202],[97,143,226,1187,1199,1351,3205],[85,97,143,226,617,1019,1020,1024,1095,1099,1156,1187,3204],[97,143,226,1161,1199,1351,1607,3202],[97,143,226,1187,1199,1607,2790,3217],[85,97,143,226,518,620,622,1020,1024,1034,1077,1086,1087,1095,1099,1147,1149,1161,1166,1187,1301,1321,1323,1506,3200,3201,3202,3203,3204,3205,3206,3209,3212,3213,3216],[97,143,226,1161,1199,1351,1607,3206],[85,97,143,226,1023,1024,1030,1034,1147,1149,1161,3210,3211],[97,143,226,1034,1161,1199,1351,1607,3210],[97,143,226,1019,1020,1024,1034,1099,1147,1149,1161,1166,1177,1314],[97,143,226,617,1187,1199,1351,1607,3209],[85,97,143,226,508,617,1077,1087,1151,1187,1884,3208],[85,97,143,226,617,1187,3083],[85,97,143,226,1199,1351,1607,3083],[85,97,143,226,1020,1021,1024,1069,1079,1099,1151],[97,143,226,1199,1351,1373,1950],[97,143,226,1099,1373],[97,143,226,1199,1351,1607,3659],[85,97,143,226,617,1020,1024,1095,1151,1187,1884,1909,1969],[85,97,143,226,1020,1024,1035,1080,1321,1323,1635,2003,2700,2743,3428,3429],[97,143,226,1186,1199,2010],[97,143,226,1199,1351,2010],[85,97,143,226,518,1020,1024,1028,1178,2005,2009],[97,143,226,1199,1351,3715],[85,97,143,226,1024,1187],[85,97,143,226,1020,1021,1024,1035,1095,1300,1948,2003,2008],[85,97,143,226,617,1020,1021,1024,1027,1032,1091,1095,1099,1150,1151,1187,1909,2576],[97,143,226,1187,1199,1351,2790,3717],[85,97,143,226,1020,1024,1091,1095,1150,1151,1187,2244],[85,97,143,226,1073,1091,1186,1187,1199,1351,3714],[85,97,143,226,617,1020,1021,1024,1073,1091,1150,1187,1301,1306,3155],[85,97,143,226,1073,1186,1187,1199,1351,3707],[85,97,143,226,617,1024,1073,1079,1150,1187,1306],[85,97,143,226,617,1020,1024,1091,1099,1150,1151,1187,1300],[97,143,226,1073,1635],[85,97,143,226,1020,1024,1091,1150,1187],[97,143,226,1199,1351,2004],[85,97,143,226,2003],[97,143,226,1199,1627,3215],[97,143,226,1073,1627,1636],[85,97,143,226,1019,1024,1073,1080],[97,143,226,1199,1351,3429],[85,97,143,226,1020,1024,1080,1321,1323,2700],[97,143,226,1199,1351,1635],[85,97,143,226,1024,1035,1634],[97,143,226,1034,1199,2013],[97,143,226,1034],[85,97,143,226,617,1019,1020,1024,1034,1095,1099,1156,1187],[85,97,143,226,1024,1034,2013],[85,97,143,226,1199,1351,1607,3629],[85,97,143,226,617,1020,1021,1024,1029,1030,1095,1187,1265,1313,1906,1908,2033,2798],[97,143,226,1091,1199,1351,3090],[85,97,143,226,1077,1091,1092,1094,1427,3085,3087,3089],[97,143,226,1091,1199,1351,1607,3087],[97,143,226,1091,1199,1351,3087],[85,97,143,226,617,1020,1021,1029,1035,1094,1095,1265,1420,1906,1961,2033,3086],[97,143,226,1199,1351,3085],[85,97,143,226,1023,1024,1035],[97,143,226,1091,1199,1351,1426,3089],[85,97,143,226,617,1020,1024,1028,1077,1094,1099,1300,1422,1424,1426,1427,1908,2760,3088],[97,143,226,1199,1961],[97,143,226,1091,1199,1351,1426,1607,3088],[97,143,226,1091,1199,1351,1426,3088],[85,97,143,226,617,1020,1021,1029,1035,1094,1095,1265,1426,1427,1906,1961,2033,3086],[85,97,143,226,1024,1321,1323],[85,97,143,226,1024,1076,1150,1384],[85,97,143,226,1177,1884],[85,97,143,226,1021,1022,1024,1030,1035,1069,1070,1071,1187],[97,143,226,1199,1351,2752],[97,143,226,1099],[97,143,226,1199,1607,2760,2790],[85,97,143,226,1020,1023,1024,1077,1095,1908],[97,143,226,1199,1351,3304],[85,97,143,226,1019,1023,1024,1414,1415],[97,143,226,1199,1351,1607,3305],[85,97,143,226,1019,1020,1024],[97,143,226,1199,1351,1607,3306],[85,97,143,226,1020,1024],[97,143,226,1199,1351,1884,3207],[85,97,143,226,1019],[97,143,226,1199,1351,1607,3208],[97,143,226,1035,1884,3207],[85,97,143,226,1069,1199,1607,1653,2790],[85,97,143,226,1021,1024,1028,1030,1035,1079,1156],[97,143,226,1199,1351,2753],[85,97,143,226,1019,1168,1952,2752],[97,143,226,1199,1351,2652],[97,143,226,1019,1313],[85,97,143,226,1020,1024,1035,1151,1177,1187,3208],[85,97,143,226,1199,1265,1351,1501,1607,2033,3231],[85,97,143,226,1020,1021,1024,1069,1150,1265,1501,1906],[97,143,155,164,226,1199,1351,1607,1886],[85,97,143,226,617,1020,1021,1077,1151,1884,1885],[97,143,226,1199,1351,1607,1885],[85,97,143,226,1021,1024,1026,1082,1414],[85,97,143,226,1069,1071,1199,1351,1607],[85,97,143,226,1029,1069],[97,143,226,1199,1351,1373,3893],[97,143,226,1199,1351,1607,1904],[85,97,143,226,1026,1187],[97,143,226,1187,1199,1351,1607,3272],[85,97,143,226,1020,1024,1029,1035,1077,1649,1908,2769],[85,97,143,226,1077,1079],[85,97,143,155,164,226,1199,1891,2790],[85,97,143,226,1099,1890],[85,97,143,226,1026,1469],[97,143,226,1199,1607,1892,2790],[85,97,143,226,1024,1030,1035],[85,97,143,226,1082,1091,1199,1351,1897,1902],[85,97,143,226,1082,1091,1187,1301,1414,1897,1899,1900,1901],[97,143,226,1199,2016],[97,143,226,1902],[97,143,226,1199,1351,2761],[97,143,226,1099,2016],[85,97,143,226,1091,1199,1351,1897,1899,1902,2016],[85,97,143,226,1151],[85,97,143,226,1032,1503,1648],[85,97,143,226,1199,1351,1503,1607,3638],[85,97,143,226,1026,1503,2973],[97,143,226,1187,1199,1351,1415,1607,3228],[85,97,143,226,1020,1024,1029,1030,1035,1069,1095,1187,1313,1648,1906,1908],[97,143,226,1187,1199,1351,1516,1607,3630],[85,97,143,226,1026,1187,1516,1648],[97,143,226,617,1091,1187,1199,1351,1367,1607,1911],[85,97,143,226,617,1020,1021,1022,1024,1029,1030,1035,1069,1076,1080,1081,1091,1095,1156,1187,1367,1903,1906,1908,1910],[97,143,226,1199,1351,2649],[97,143,226,620,1020,1086,1375,1958,2293,2639,2640,2641,2643,2644,2646,2647,2648],[97,143,226,1199,1442,2654,2790],[85,97,143,226,1024,1442,1908],[97,143,226,1199,1351,1445,1607,2790,3097],[85,97,143,226,1024,1094,1147,1149,1445,1908,3096],[97,143,226,1199,1351,1445,1607,2790,3096],[85,97,143,226,1024,1147,1149,1161,1445,3095],[97,143,226,1147,1149,1161,1177,1445],[97,143,226,1199,1351,1503,2790,3100],[97,143,226,1024,1094,1503,1908,3099],[97,143,226,1199,1351,1503,2790,3099],[85,97,143,226,1024,1147,1149,1161,1503,3098],[97,143,226,1147,1149,1161,1177,1503],[97,143,226,1199,1351,1607,2846],[85,97,143,226,508,1024],[97,143,226,1199,2037],[97,143,226,2037],[85,97,143,226,617,1020,1021,1024,1029,1035,1078,1082,1095,1100,1105,1106,1107,1108,1109,1110,1187,1265,1313,1619,1906,2033,2034,2035,2036],[85,97,143,226,1199,1351,1607,2040,2790],[85,97,143,226,614,617,1020,1028,1033,1077,1150,1156,1187],[97,143,226,1033,2040],[97,143,226,614],[85,97,143,226,1199,1351,1607,2790,3082],[85,97,143,226,617,1020,1023,1024,1077,1187,2041],[97,143,226,1199,1607,1970,1971,2790],[85,97,143,226,617,1020,1024,1095,1150,1503,1963,1965,1966,1967,1968,1970],[97,143,226,1199,1966,2790],[85,97,143,226,1030,1965],[97,143,226,1967,2790],[85,97,143,226,1964],[97,143,226,1199,1607,1968,2790],[85,97,143,226,1083,1965],[97,143,226,1965,1971,1972],[97,143,226,1032,1964],[97,143,226,1199,1607,1965,1972,2790],[85,97,143,226,1020,1024,1025,1032,1964,1965,1971],[97,143,226,1199,1964,1965,1969,1970],[97,143,226,1166,1964,1965,1969],[97,143,226,1187,1199,1351,2769],[85,97,143,226,1076,1187,2042],[97,143,226,1199,2790,3232],[85,97,143,226,1019,1024,1077,1099],[85,97,143,226,1020,1024,1091,1187,1313,1975,2242,2244,2282],[85,97,143,226,2790,3065],[85,97,143,226,1199,1320,1351,1607,2790],[97,143,226,1199,2233],[97,143,226,1170,1199],[97,143,226,1199,1351,1607,1912],[85,97,143,226,1020,1024,1026,1076],[85,97,143,226,1020,1023,1030],[97,143,226,1081,1199],[97,143,226,1199,2044],[97,143,226,1032,1187],[85,97,143,226,614,625,1031,1187],[97,143,226,1031,1199,2790],[97,143,226,1031,1199],[85,97,143,226,1020,1023,1024,1026,1029,1030],[97,143,226,1199,2046],[97,143,226,1031],[85,97,143,226,1199,1351,1607,1914],[85,97,143,226,1020,1021],[97,143,226,1199,2048],[97,143,226,1032],[97,143,226,1031,2046,2050],[85,97,143,226,1199,1351,1607,3268],[85,97,143,226,1020,1021,1024],[97,143,226,1087,1199,1351,1958,2790],[85,97,143,226,508,1019,1020,1024,1087,1094,1099,1178,1187,1366,1368,1381,1442,1503,1947,1948,1949,1950,1955,1957],[85,97,143,226,1187,1199,1351,2656],[85,97,143,226,1020,1024,1187,1451,1908,1956],[97,143,226,1199,1635,3319],[97,143,226,1073,1187,1634,1635,1636,2915,2961],[97,143,226,1082,1187,1199],[97,143,226,1081,1187],[97,143,226,1073,1199,2052],[97,143,226,1199,1635,1636,3414],[97,143,226,617,1073,1187,1634,1635,1636,1639,2961],[97,143,226,1199,1351,2762],[85,97,143,226,1099,1306,1884,1888],[97,143,226,1073,1075],[97,143,226,1075,1199,1351,1456,1460,1462,1607,1915,2790],[85,97,143,226,1075,1076,1456,1460,1462],[85,97,143,226,1187,1199,1351,1607,1918,2790],[85,97,143,226,1073,1083,1187,1313,1460,1916,1917],[97,143,226,617,1073,1185,1199,1351,1607,3149],[85,97,143,226,616,617,1024,1073,1079,1095,1293,2798],[85,97,143,226,1024,1156,1916],[85,97,143,226,1073,1199,1351,1607,3338],[85,97,143,226,1021,1022,1024,1029,1030,1035,1069,1073,1906],[97,143,226,1073,1199],[97,143,226,1199,1305,2055],[97,143,226,1091,1187,1199,1305,1351,3263],[85,97,143,226,1020,1021,1026,1035,1069,1071,1095,1187,1305,1306,2015,2055,3229,3259],[97,143,226,617,1091,1187,1199,1351,1607,3266],[85,97,143,226,617,1020,1024,1087,1094,1187,1435,2582,2760,3263,3265],[97,143,226,1187,1199,1351,1607,3265],[85,97,143,226,1024,1147,1149,1161,1187,3264],[97,143,226,1019,1020,1024,1147,1149,1161,1166,1177,1187,1305,1314],[97,143,226,1187,1199,1607,2790,3221],[85,97,143,226,1020,1021,1029,1035,1095,1187,1265,1906,2033],[85,97,143,226,1147,1149,1199,1351,1607,3281],[85,97,143,226,1020,1032,1095,1147,1149,1187,2314,3279,3280],[85,97,143,226,1147,1149,1199,1351,1607,3279,3280],[85,97,143,226,1024,1032,1147,1149,1161,3279],[97,143,226,1019,1024,1032,1147,1149,1161,1177],[97,143,226,617,1199,1351,1481,1496,1607,2768,2790,3242],[85,97,143,226,617,1020,1024,1029,1035,1069,1079,1095,1150,1481,1496,1906,2768],[85,97,143,226,1077],[85,97,143,226,617,1077,1151,1187,1884],[85,97,143,226,617,1091,1187,1199,1351,1607,2630,3227],[85,97,143,226,617,623,1020,1024,1035,1070,1077,1091,1095,1108,1162,1166,1187,1301,1306,1463,1503,1507,1619,1620,1625,1884,1995,2037,2582,2596,2760,3220,3221,3222,3225,3226],[85,97,143,226,623,1020,1021,1022,1024,1030,1035,1069,1079,1099,1187,1265,1313,1649,1889,1906,1920,2008,2032,2582,2593,2595,3223,3224],[97,143,226,1162,1187,1199,1351,1367,1503,1514,1607,2781,2790],[97,143,226,1025,1035,1150,1162,1187,1367,1503,1514,1976],[97,143,226,1199,1976],[97,143,226,1199,1351,3219],[85,97,143,226,1199,1305,1306,1351],[85,97,143,226,1304,1305],[85,97,143,226,1199,1305,1351,2962],[85,97,143,226,1306],[85,97,143,226,620,1199,1370,1607,2651,2790],[85,97,143,226,508,620,1019,1024,1086,1099,1178,1187,1372,1375,1380,1442,1947,1979,2293,2641,2643,2644,2646,2647,2648,2650],[97,143,226,1199,1607,2641,2790],[85,97,143,226,1020,1024,1314,1371,1391,1979],[97,143,226,1199,2643,2790],[85,97,143,226,1019,1020,1024,1035,1375,2642],[97,143,226,1199,1951],[85,97,143,226,1607,2644,2790],[85,97,143,226,1019,1020,1024,1099,1370,1377,1954],[97,143,226,1199,1370,1607,2650,2790],[85,97,143,226,1019,1024,1028,1035,1079,1094,1099,1370,1371,1372,1375,1951,1952,1953,1954],[97,143,226,1199,1351,2646],[85,97,143,226,518,1024,1178,1314,1506,2645],[97,143,226,1199,1351,1607,2648],[85,97,143,226,1023,1024,1025,2293],[97,143,226,617,620,1178,1187,1199],[97,143,226,616,617,620,622,623,625,1031,1032,1033,1034,1072,1073,1074,1075,1110,1182,1183,1184,1185,1186],[97,143,226,1091,1199,1442,2655,2790],[85,97,143,226,1024,1442],[85,97,143,226,625,2763,2764,2765],[97,143,226,1199,1910],[85,97,143,226,617,1020,1095,1909],[97,143,226,617,1032,1187,1199,1607,1923,2790],[97,143,226,1187,1199,1923],[85,97,143,226,617,1020,1021,1022,1024,1026,1029,1030,1031,1032,1035,1069,1071,1072,1075,1076,1079,1080,1081,1083,1087,1091,1094,1095,1099,1166,1187,1367,1369,1445,1469,1499,1506,1648,1649,1650,1651,1652,1653,1886,1887,1889,1891,1892,1902,1903,1904,1905,1911,1912,1913,1914,1915,1918,1919,1920,1921,1922],[97,143,226,1199,1921],[97,143,226,1031,1652,1888,1902,1913,1914],[97,143,226,1032,1199,1607,2767,2790],[97,143,226,617,1032,1199,1607,2767,2790],[85,97,143,226,617,1020,1021,1024,1029,1032,1035,1069,1094,1095,1187,1265,1906,1908,1909,2033,2059,2576],[97,143,226,1199,2059],[97,143,226,1199,1922],[97,143,226,1199,2063],[97,143,226,624,1265,2062],[85,97,143,226,1091,1199,1351,1607,3310],[85,97,143,226,617,1020,1021,1022,1029,1030,1091,1095,1293,1367,1906,1915,1920,2033,2062,2063,2781,3309],[97,143,226,1187,1199,2065],[97,143,226,624,1187,1265,2062],[85,97,143,226,1091,1187,1199,1351,1607,3309],[85,97,143,226,617,1020,1021,1022,1029,1030,1091,1187,1293,1367,1906,1915,1920,2033,2062,2065,2301,2781],[85,97,143,226,1199,1351,1367,1607,2790,3312],[85,97,143,226,617,1020,1024,1077,1091,1166,1177,1179,1187,1301,1367,1503,1952,1963,2292,2766,3228,3234,3238,3309,3311],[97,143,226,1087,1199,1946,1958,1959],[97,143,226,1087,1946,1958],[97,143,226,1187,1199,1607,2790,3274],[85,97,143,226,617,1020,1021,1022,1023,1024,1030,1069,1077,1079,1099,1187,1265,1301,1906,2033,3270,3271,3272],[97,143,226,1199,1351,1607,3276,3277],[85,97,143,226,1024,1161,3275,3277],[85,97,143,226,1019,1020,1024,1099,1147,1149,1177,1314,3277],[97,143,226,1187,1199,1351,1607,3276,3277],[85,97,143,226,617,1020,1187,3273,3274,3276],[97,143,226,1187,1199,1351,3632],[85,97,143,226,1020,1147,1149,1161,1187,1301,2222],[85,97,143,226,1035,1099,1187,1884],[97,143,226,1075,1187,1199,1351,1607,2764],[85,97,143,226,1035,1073,1075,1099,1187,1884],[85,97,143,226,1099,1187,1884],[97,143,226,1187,1199,1351,1641,2770,2790],[85,97,143,226,1076,1187,1369,1641],[97,143,226,617,1187,1199,1351,1607,3287],[85,97,143,226,617,1019,1020,1023,1024,1028,1035,1077,1095,1099,1187,1300],[97,143,226,1199,1305],[97,143,226,530,1304],[97,143,226,1147,1149,1187,1199,1351,3214,3216],[85,97,143,226,617,1024,1025,1034,1035,1076,1077,1095,1099,1147,1149,1161,1187,1301,1305,1627,1636,1884,1947,2651,3212,3214,3215],[97,143,226,1099,1147,1149,1161,1177,1305],[85,97,143,226,1199,1351,1607,3268,3269],[85,97,143,226,1020,1021,1024,3268],[97,143,226,1199,1351,3270],[85,97,143,226,1024,1077,1187],[97,143,226,617,1187,1199,1607,2790,2884],[85,97,143,226,617,1020,1187,1897],[97,143,226,1199,1351,1893],[97,143,226,1199,1351,1894],[97,143,226,1199,1351,1607,1897],[85,97,143,226,1893,1894,1895,1896],[97,143,226,1199,1351,1607,1895],[97,143,226,1199,1351,1607,1896],[85,97,143,226,1079],[97,143,226,617,1199,1351,1486,1487,1607,2969],[85,97,143,226,617,1020,1023,1024,1077,1094,1095,1162,1380,1483,1486,1487,2967,2968],[97,143,226,1199,1486,1607,2790,2968],[85,97,143,226,1020,1021,1022,1025,1029,1030,1069,1095,1265,1486,1906,2033,2067],[97,143,226,1199,1486,2067],[97,143,226,1486],[97,143,226,1199,1351,1486,1607,2967],[85,97,143,226,1024,1147,1149,1161,1486,2965,2966],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1314,1486,2069],[85,97,143,226,1024,1301,1324,1486,2069],[97,143,226,617,1187,1199,1351,1607,2790,2796],[85,97,143,226,617,1020,1021,1024,1028,1029,1077,1187,1265,1313,1906,1908,1909,2033,2768],[97,143,226,1187,1199,1607,2790,3233],[85,97,143,226,1019,1025,1187],[97,143,226,1069,1187,1199,1351,1607,3093],[85,97,143,226,617,1020,1021,1025,1029,1069,1077,1079,1095,1151,1187,1301,1306,1987,2760,2768,3082,3084,3090,3092],[97,143,226,1199,1351,1431,1433,1607,2790,2811],[85,97,143,226,617,1020,1021,1028,1029,1094,1095,1265,1313,1431,1433,1906,1980,2033,2798],[97,143,226,1199,1351,1607,2790,2813],[85,97,143,226,617,1020,1024,1077,1094,1150,1430,1431,1432,1433,1908,1980,2760,2811,2812],[97,143,226,1199,1351,1607,2812],[97,143,226,617,1199,1351,1481,1498,1607,2768,2790,2797],[85,97,143,226,617,1020,1021,1023,1024,1029,1035,1069,1077,1079,1150,1313,1481,1498,1906,2768],[85,97,143,226,1082,1199,1351,1454,1455,1607,3146],[85,97,143,226,617,1020,1021,1024,1026,1029,1035,1069,1077,1079,1082,1084,1150,1313,1454,1455,1906,1908,1981,3145],[85,97,143,226,1199,1351,1607,1981,3145],[97,143,226,1020,1022,1024,1077,1301,1885,1908,1981],[97,143,226,617,1187,1199,1981],[97,143,226,1199,1351,1607,2814],[85,97,143,226,1020,1021,1023,1024,1029,1077,1094,1095,1151,1187,1313,1906,1983,2033],[97,143,226,1199,1351,2790,2800],[85,97,143,226,617,1020,1095,1313,1492,1985,2768,2799],[97,143,226,1199,1351,1607,2790,2799],[85,97,143,226,1021,1022,1029,1030,1069,1156,1265,1306,1906,1984,2033,2798],[97,143,226,1091,1199,1351,2801],[85,97,143,226,617,1492,1494,1985,2760,2768],[97,143,226,1199,1351,1492,1494,1607,2790,2802],[97,143,226,617,1199,1351,1492,1494,1985,2768,2799,2802],[85,97,143,226,617,1020,1095,1313,1492,1494,1985,2768,2799],[97,143,226,1199,1351,1607,2790,2803],[97,143,226,1199,1351,1494,2790,2804],[97,143,226,1024,1028,1077,1147,1149,1161,1176,1494,1984],[97,143,226,1091,1199,1351,2807],[85,97,143,226,1020,1024,1077,1099,1166,1306,1494,1984,1985,2800,2801,2802,2803,2804,2805,2806],[97,143,226,1199,1351,2805],[97,143,226,1199,1351,2790,2806],[97,143,226,1024,1077,1150],[97,143,226,1199,1494,1985],[97,143,226,1494],[97,143,226,1199,1607,2790,2808],[85,97,143,226,1020,1024,1080,1099,1156,1959],[97,143,226,617,1199,1351,2809],[97,143,226,617,1028,1077,1079,1094,1150,1506,1510,1908,2808],[97,143,226,1187,1199,1351,1512,1513,2790,2810],[85,97,143,226,617,1020,1022,1027,1030,1077,1079,1094,1150,1187,1512,1513,1908,2744],[97,143,226,1199,1351,1607,3092],[85,97,143,226,1020,1024,1161,1987,3091],[97,143,226,1019,1020,1024,1147,1149,1177,1314,1987],[97,143,226,617,1082,1199,1351,1607,1901],[85,97,143,226,617,1020,1082,1313,1898,1899,1900],[97,143,226,1199,1351,1898],[85,97,143,226,1024,1095],[97,143,226,1082,1091,1199,1351,1607,2963],[85,97,143,226,617,1020,1024,1082,1091,1898,1899],[85,97,143,226,1024,1026,1076],[97,143,226,1082,1091,1187,1199,1351,1607,2964],[85,97,143,226,617,1024,1035,1087,1151,1187,1463,1901,2760,2961,2962,2963],[97,143,226,617,1199,1351,1607,1899,1900],[85,97,143,226,617,1020,1024,1301,1899],[97,143,226,1199,1351,1607,2755],[85,97,143,226,1019,1020,1024,1964,2244],[97,143,226,1199,1351,1908],[85,97,143,226,1019,1907],[97,143,226,1199,1351,1607,3311],[85,97,143,226,1019,1099,1168],[97,143,226,1199,1351,3631],[85,97,143,226,1199,1351,2217],[85,97,143,226,1019,2070,2214,2215,2216],[85,97,143,226,1199,1351,2218],[85,97,143,226,1199,1351,2219],[85,97,143,226,2070,2216],[85,97,143,226,1199,1351,2216],[85,97,143,226,2214],[85,97,143,226,1199,1351,2220],[97,143,226,2070,2216,2217,2218,2219,2220,2221],[85,97,143,226,1199,1351,2221],[97,143,226,1199,1607,1952,2790],[97,143,226,617,1199,1351,1607,1919],[85,97,143,226,617,1020,1909],[85,97,143,226,1147,1148,1149],[97,143,226,1147,1149,1153],[85,97,143,226,1147,1149,1153,1158,1160,1199,1351,1607],[85,97,143,226,1019,1024,1147,1148,1149,1150,1151,1152],[85,97,143,226,1147,1149,1153,1155,1159,1199,1351,1607],[85,97,143,226,1020,1027,1147,1149,1154],[97,143,226,1152,1199,1351,1607],[97,143,226,1019,1020,1024,1030],[85,97,143,226,1147,1149,1161,1199,1351,1607],[97,143,226,1147,1149,1156],[85,97,143,226,1147,1149,1160,1199,1351,1607],[85,97,143,226,794,1019,1024,1147,1149],[85,97,143,226,1147,1149,1153,1159,1199,1351,1607],[85,97,143,226,1019,1020,1021,1024,1099,1147,1149,1158],[97,143,226,794,1020,1024,1147,1149],[97,143,226,1148,1149,1152,1153,1155,1157,1158,1159,1160],[85,97,143,226,1147,1149],[97,143,226,1168,1199,1351,1607],[85,97,143,226,518,1019,1024],[85,97,143,226,1021,1069,1199,1265,1351,1607,1906,2032],[85,97,143,226,1024,1035],[85,97,143,226,1019,1021,2008,2592],[97,143,226,1173,1199,1351,1607],[97,143,226,1032,1035,1166,1187],[97,143,226,1174,1199,1351],[85,97,143,226,851,1018,1019],[97,143,226,1076,1199,1351,1607],[97,143,226,1199,2749,2790],[85,97,143,226,2640],[85,97,143,226,1026,1199,1351,1607,2973],[85,97,143,226,1024,1025,1026,1647],[85,97,143,226,1026,1199,1351,1607,1648],[97,143,226,1199,1351,2881],[97,143,226,1020,1024,1908],[85,97,143,226,1023,1024],[85,97,143,226,1166,1285,1286,2756],[97,143,226,1026,1199,1351,1607],[97,143,226,1199,1351,1949],[85,97,143,226,744,1018,1019],[85,97,143,226,1024,1077,1954],[97,143,226,1162,1163,1199,1351],[85,97,143,226,1019,1024,1099,1162],[85,97,143,226,1035],[97,143,226,1165,1199,1351],[97,143,226,1164],[97,143,226,1166,1167,1199,1351,1607],[85,97,143,226,1019,1024,1164,1166],[97,143,226,1169,1199,1351,1607],[85,97,143,226,1019,1024,1168],[97,143,226,1163,1164,1165,1167,1169,1171,1172,1175,1176],[97,143,226,1171,1199,1351,1607],[97,143,226,1081,1099,1164,1170],[97,143,226,1172,1199,1351],[97,143,226,1175,1199,1351],[97,143,226,1166,1173,1174],[97,143,226,1176,1199,1351,1607],[85,97,143,226,1019,1099,1164],[97,143,226,1199,1351,2640],[97,143,226,1019,1028],[85,97,143,226,1414,1415],[97,143,226,1199,1370,1607,1955,2790],[85,97,143,226,1019,1020,1024,1028,1079,1094,1099,1370,1371,1372,1373,1375,1442,1951,1952,1953,1954],[85,97,143,226,1091,1187,1199,1351,1451,1607,1957],[97,143,226,1020,1024,1080,1091,1174,1187,1451,1956],[97,143,226,617,1187,1199,1351,1607,2799,2815],[85,97,143,226,617,1020,1029,1069,1095,1187,2768,2799],[97,143,226,1199,1351,3339],[85,97,143,226,623,1076,1187],[97,143,226,1187,1199,1351,1607,2790,3606,3608],[85,97,143,226,617,1187,3606,3607],[85,97,143,226,1024,1147,1149,1161,3606],[97,143,226,1019,1020,1024,1147,1149,1161,1177,1314],[85,97,143,226,1890],[97,143,226,1199,1351,2790,3234],[85,97,143,226,1020,1021,1029,1030,1076,1095,1265,1313,1652,1889,1906,2033,2223],[85,97,143,226,1024,1025],[85,97,143,155,164,226,1199,1607,1890,2790],[85,97,143,226,1020,1021,1023,1024,1028,1030,1035,1077,1099,1306,1884,1888,1889],[97,143,226,1187,1199,1351,2790,3236],[85,97,143,226,617,1020,1024,1077,1151,1156,1187,3235],[97,143,226,1199,2223],[97,143,226,1199,2229,2790,3237],[85,97,143,226,1024,1035,1077,1099,1166,2229,2310],[97,143,226,1199,3235],[97,143,226,1199,2225],[97,143,226,1162,1187,1199,1351,1367,1445,1501,1503,1514,1607,2790,3241],[85,97,143,226,617,625,1020,1021,1022,1024,1026,1029,1035,1069,1076,1077,1079,1080,1081,1087,1091,1094,1099,1166,1176,1187,1265,1301,1313,1367,1369,1439,1501,1649,1650,1651,1652,1884,1886,1887,1889,1902,1906,1915,1918,1920,2033,2225,2227,2230,2292,2578,2760,2762,2766,2772,2781,3228,3229,3230,3231,3232,3233,3234,3236,3237,3239,3240],[97,143,226,1087,1094,1199,1351,1506,1607,2790,3239,3241],[85,97,143,226,1024,1035,1087,1094,1166,1177,1187,1506,3238,3241],[97,143,226,1199,2227],[97,143,226,1032,1187,1199,1445,1607,2790,3240],[85,97,143,226,1021,1024,1032,1035,1081,1099,1147,1149,1161,1170,1177,1187,1414,1415,1445,1952,2751,2752,2774],[85,97,143,226,617,1081,1091,1187,1199,1351,1501,1607,1613,3612],[85,97,143,226,616,617,1020,1021,1022,1024,1026,1029,1032,1035,1079,1080,1081,1087,1091,1095,1187,1265,1301,1367,1369,1501,1503,1615,1649,1650,1651,1652,1886,1887,1889,1891,1902,1906,1915,1918,1920,2033,2227,2749,2760,2781,3229,3231,3233,3241,3608,3609,3611],[97,143,226,1032,1199,1503,1988],[97,143,226,1032,1187,1503,1969],[85,97,143,226,1032,1199,1351,1503,1607,2790,3611],[85,97,143,226,1020,1021,1024,1026,1032,1094,1147,1149,1161,1367,1414,1415,1503,1988,3610],[97,143,226,1019,1020,1024,1032,1147,1149,1150,1161,1166,1177,1187,1314],[85,97,143,226,617,1187,1199,1607,2790,3609],[85,97,143,226,617,1020,1021,1023,1024,1025,1077,1081,1099,1187,1313,1367,1652,1904,2781],[97,143,226,1199,2230],[85,97,143,226,1031,1032,1187,1199,1351,1607,2773,2790],[85,97,143,226,617,623,1020,1021,1022,1029,1030,1031,1032,1035,1075,1076,1079,1081,1087,1187,1313,1366,1367,1469,1506,1649,1650,1651,1652,1653,1887,1888,1889,1892,1902,1904,1906,1912,1913,1914,1915,1918,1920,1923,2016,2033,2051,2230,2232,2234,2769,2770,2771,2772],[97,143,226,1032,1094,1187,1199,1351,1367,1607,2750,2774,2790],[97,143,226,1032,1091,1094,1187,1199,1351,1460,1462,1607,2750,2774,2790],[85,97,143,226,617,1020,1024,1032,1077,1087,1091,1094,1095,1099,1166,1168,1173,1179,1187,1301,1367,1445,1448,1449,1460,1462,1469,1506,1888,2016,2054,2233,2578,2750,2754,2758,2759,2760,2761,2762,2766,2767,2768,2773],[97,143,226,1199,2234],[97,143,226,1032,1265,1888,2230,2232,2233],[97,143,226,1199,1351,1607,2754],[85,97,143,226,1020,1024,1028,1035,1099,1168,1179,1314,1952,2751,2752,2753],[85,97,143,226,1087,1091,1199,1351,2774],[97,143,226,1199,1285,1295,1351,2758],[85,97,143,226,1077,1087,1286,1295,1301,2222,2755,2757],[97,143,226,1199,1322,1351,1607,2647],[85,97,143,226,1020,1024,1099,1314,1322],[97,143,226,1091,1187,1199,1351,1607,3615],[85,97,143,226,1020,1024,1025,1091,1099,1187,1313,1903,1975,3064,3614],[97,143,226,1199,2790,3614],[85,97,143,226,1019,1030],[85,97,143,226,617,1091,1187,1199,1351,1607,2790,3618],[85,97,143,226,617,1091,1187,1369,1990,3065,3617],[85,97,143,226,1187,1199,1351,1607,2790,3617],[85,97,143,226,1024,1030,1147,1149,1161,1187,3614,3616],[97,143,226,1147,1149,1187,1199,1351,1607,3616],[97,143,226,1035,1147,1149,1161,1177,1187,3614],[85,97,143,226,1199,1351,1607,2790,3619],[85,97,143,226,1369,3615,3618],[97,143,226,1020,1199,1300,1351,1607],[85,97,143,226,704,1019,1020],[85,97,143,226,1018,1019],[97,143,226,1199,1351,1953],[85,97,143,226,742,1019],[97,143,226,1099,1199,1351],[97,143,226,844,1013,1018,1019],[97,143,226,1199,1351,2639],[97,143,226,844,1013,1018,1019,1028],[85,97,143,226,1020,1199,1351],[97,143,226,744,1018,1019],[85,97,143,226,1199,1351,2215],[85,97,143,226,1019,2214],[97,143,226,748,1019,1024],[97,143,226,754],[85,97,143,226,1014,1019,1020,1023,1024],[85,97,143,226,803,1019,1020,1024],[85,97,143,226,794,1019,1024],[85,97,143,226,1029,1199,1351],[85,97,143,226,1018,1019,1027,1028],[97,143,226,910,1019],[85,97,143,226,1018,1019,1020,1021,1022],[85,97,143,226,897,1019],[97,143,226,921,923,1019],[85,97,143,226,1020,1021,1022,1027,1028,1077,1099,1150,1151,1199,1313,1351,2215],[85,97,143,226,931,1019],[97,143,226,1030,1199,1351,1607],[85,97,143,226,951,1019,1024],[97,143,226,765,1019],[97,143,226,1019],[97,143,226,961,1019],[97,143,226,615,1024,1322],[97,143,226,965,1019],[97,143,226,972,1018,1019],[97,143,226,1035,1199,1351,1607],[85,97,143,226,1011,1019,1024],[97,143,226,1199,1313,1351],[85,97,143,226,1019,1312],[97,143,226,1187,1199,1351,1607,2816],[85,97,143,226,617,1020,1021,1024,1029,1030,1035,1069,1187,1265,1313,1906,2033],[97,143,226,1187,1199],[97,143,226,617,1187,1199,1351,1607,3222],[85,97,143,226,617,1020,1024,1029,1095,1187,1265,1313,1906,1908,2033,2798],[97,143,226,1032,1094,1187,1199,1351,1607,2048,3300],[85,97,143,226,1027,1035,1083,1094,1161,1166,1177,1187,1285,1884,2048,2222,2774],[97,143,226,1199,1285,1351,1607,3627],[85,97,143,226,1077,1147,1149,1161,1166,1177,1285,2222],[97,143,226,1199,1991],[97,143,226,1187,1199,1351,3633],[85,97,143,226,1025,1035,1077,1187,1301,1964,2222,3631,3632],[85,97,143,226,1187,1199,1351,2777,2790],[85,97,143,226,620,621,1032,1087,1187,1923,2748,2776],[97,143,226,1187,1199,1351,1512,2744,2790],[85,97,143,226,1020,1024,1187,1512,1908,2700,2743],[97,143,226,1076,1111,1199,1351,1920],[85,97,143,226,1076,1111,1187],[97,143,226,1199,1305,3674],[97,143,226,530,1305],[97,143,226,1199,1351,1607,2244,3101,3103],[85,97,143,226,1020,1024,1154,1176,1952,2244,2752,3101],[85,97,143,226,1091,1147,1149,1187,1304,3101,3102,3103],[97,143,226,1147,1149,1199,1351,1607,3101,3102],[85,97,143,226,1021,1024,1030,1147,1149,1161,3101],[97,143,226,1147,1149,1177,2752],[97,143,226,1199,1351,2253],[85,97,143,226,1199,1607,2252,2790],[85,97,143,226,1024,1080,1166],[85,97,143,226,1024,1035,1077,1099,1151],[97,143,226,2239],[85,97,143,226,1199,2239,2240,2790],[85,97,143,226,1035,1187],[85,97,143,226,1199,1607,2240,2250,2790],[85,97,143,226,1035,1166,2239,2247,2248,2249],[85,97,143,226,1199,1607,2240,2247,2790],[97,143,226,1199,1351,1607,2790,3113],[85,97,143,226,1301,1313,1369,3097,3100,3104,3112],[85,97,143,226,1032,1091,1147,1149,1187,1199,1351,2044,2244,3105],[97,143,226,1032,1087,1091,1147,1149,1187,2044,2242,2244,2795],[97,143,226,1199,1351,2243],[97,143,226,1019,1099],[85,97,143,226,1199,1351,1607,2272],[85,97,143,226,1024,1080],[85,97,143,226,1020,1024,1035,1099,1177,1305,2241,2242,2243,2244],[85,97,143,226,1199,1351,1607,2269,2275],[85,97,143,226,1024,1080,2269,2274],[97,143,226,2280,2281],[85,97,143,226,1199,1351,1607,2269,2276],[85,97,143,226,617,2269,2271,2272,2274,2275],[97,143,226,1199,1351,2259],[97,143,226,526,2241,2258],[97,143,226,1199,1351,1607,2242,2280],[85,97,143,226,1020,1024,1035,1077,1080,1099,1166,1183,1301,1313,1634,2241,2242,2244,2250,2251,2252,2253,2254,2255,2256,2259,2260,2268,2279],[97,143,226,1091,1177,1187,1199,1351,2242,2281],[85,97,143,226,1020,1024,1091,1154,1166,1177,1187,1301,1452,2236,2238,2241,2242,2243,2245,2246,2260,2280],[85,97,143,226,1199,1351,1607,2269,2277],[85,97,143,226,617,2241,2269,2271,2274],[97,143,226,2269],[85,97,143,226,1199,1351,2279],[97,143,226,2270,2276,2277,2278],[85,97,143,226,1199,1351,1607,2278],[85,97,143,226,1024,1035,1099,2271],[85,97,143,226,1183,1199,1351],[97,143,226,1019,1024,1099],[97,143,226,1199,1351,1607,2271],[97,143,226,1019,1020,1024,1035],[85,97,143,226,1199,1351,2274],[97,143,226,1019,2269,2273],[85,97,143,226,1199,1351,2273],[97,143,226,1019,2269],[97,143,226,1199,1351,2256],[97,143,226,1199,1351,2255],[97,143,226,1035,1952,2241],[85,97,143,226,2241,2242],[97,143,226,1199,2260],[97,143,226,1199,2244,3106],[97,143,226,2244],[85,97,143,226,1020,1021,1024,1079,1954,2236,2244,3106],[85,97,143,226,1162,1199,1351,1443,1488,1490,1607,2236,2790,3105,3108],[85,97,143,226,1021,1025,1026,1030,1032,1161,1162,1443,1488,1490,1648,2236,3105],[97,143,226,1091,1187,1199,1351,1607,1613,2242,2244,2790,3112],[85,97,143,226,1032,1091,1147,1149,1177,1187,2236,2237,2242,2244,2282,2774,3105,3107,3111],[85,97,143,226,1024,1032,1147,1149,1161,2242,3105,3108,3110],[97,143,226,1161,1199,1351,1607,2242,3110],[97,143,226,1147,1149,1161,1166,1177,1305,2236,2242,3109],[97,143,226,1151,2261],[97,143,226,2261,2262,2267],[97,143,226,2261],[85,97,143,226,1301,2261,2263,2264],[85,97,143,226,1024,1099,2261,2265],[97,143,226,1199,1351,1607,2242,2262,2267],[85,97,143,226,1024,1080,2242,2262,2266],[97,143,226,1199,2242,2262],[97,143,226,2242,2261],[97,143,226,1199,1351,3109],[97,143,226,2236],[85,97,143,226,1024,1080,1305],[85,97,143,226,1094,1166,1187],[97,143,226,1024,1032,1147,1149,1150,1161,1173,1177,1187,2751,2752],[85,97,143,226,1032,1187,1199,1351,1445,1446,1607,1613,2750,2776,2790],[85,97,143,226,1021,1024,1026,1032,1147,1149,1161,1367,1414,1415,1445,1446,1503,1615,2749,2774,2775],[85,97,143,226,620,621,622,1087,1187],[85,97,143,226,518,2003,2004],[97,143,226,1199,1351,2645],[85,97,143,226,616,1187],[97,143,226,1091],[85,97,143,226,1187],[97,143,226,2289],[97,143,226,2285,2286,2287,2288,2290],[85,97,143,226,617,1091,1187,1199,1351,2294],[97,143,226,617,1091,1187],[97,143,226,618,1187,1199,1351,3137],[85,97,143,226,617,618,1187,2315,2589],[85,97,143,226,1199,1321,1322,1323,1351],[85,97,143,226,1321,1322],[85,97,143,226,1073,1187],[97,143,226,1187,1199,1351,3154],[85,97,143,226,617,618,619,1187,2291,2315,2589],[85,97,143,226,617,618,1187,2291,2315,2589],[85,97,143,226,1093,1187],[97,143,226,1199,1304],[97,143,226,1184,1186],[97,143,226,1104,1105,1199,2297],[97,143,226,1078,1104,1105,1106,1108,1109,1110,2296],[97,143,226,1019,1199],[97,143,226,1015,1016,1018],[97,143,226,1069,1199,1351,2301],[97,143,226,1069],[97,143,226,1199,2303],[85,97,143,226,1199,1265,1351,1607,2033],[97,143,226,1069,1255,2032],[97,143,226,1185,1199,1293],[97,143,226,616,624,1184,1185,1291,1292],[97,143,226,616,1199],[97,143,226,1184,1199],[97,143,226,1185,1199],[97,143,226,1184],[97,143,226,616,617,1199],[85,97,143,226,615,616],[97,143,226,2008],[97,143,226,1087,1199,1366],[97,143,226,1087],[97,143,226,619,620,1199],[97,143,226,619],[97,143,226,617,1166,1199],[97,143,226,617],[97,143,226,1178],[97,143,226,1199,2315],[97,143,226,621,622,1199],[97,143,226,621],[97,143,226,1199,2576],[97,143,226,2575],[97,143,226,1199,2578],[97,143,226,1199,1956],[97,143,226,1199,1370],[97,143,226,1199,2583],[97,143,226,619,1199],[97,143,226,618],[97,143,226,1199,1916],[97,143,226,1178,1199],[97,143,226,1187,1199,1620],[97,143,226,1087,1187],[97,143,226,1199,1634],[97,143,226,1187,1199,1379],[97,143,226,1199,2008,2593],[97,143,226,2008,2592],[97,143,226,1199,2008,2592,2596],[97,143,226,2008,2593,2595],[97,143,226,1199,2595],[97,143,226,1086],[97,143,226,1087,1187,1199],[97,143,226,1199,1382],[97,143,226,1032,1199,1963],[97,143,226,1070,1199],[85,97,143,226,1086,1091,1199,1351,2635,2779],[97,143,226,2607,2618],[97,143,226,2607,2620],[97,143,226,2607,2622],[97,143,226,2607,2624],[97,143,226,2607,2626],[97,143,226,2607,2628],[97,143,226,1199,2607],[97,143,226,2609],[97,143,226,1199,2611],[97,143,226,1199],[97,143,226,1199,1351],[85,97,143,226,1091,1199,1351,1607,1613],[97,143,226,1094,1199,1285,2790,3300],[97,143,164,226,612]],"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":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","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":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"66be1299a7a3129ceb488b340c291cf575bebb0e337f92e169dec38231472e34","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","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":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","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":"48dcc919f76c040a999c0d46d2bf25ab089645ca21b837f120b222f56a86cd76","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":"2467b00d963828f540f4acd7910f4c04cfe4b489550e6bb682212f65583bca5b","impliedFormat":1},{"version":"854e50b93090b3f8fd6e355b074e1d24dce1ae0240f1ce46563e35fea210a6d5","impliedFormat":99},{"version":"5a16e93d5d53d987dddda1ec606c9821f6bd31d1bdf0635e05e3841312cefa8b","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":"c30436b130b6218b7714314dc41d3f459590db4bdf099eecd51cb1bda32109a8","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","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":"5757b78830c681b3124af568b94c269259ea5e8171a4316508ef67310c2ed1ed","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":"db2d933d8101f90deeec6698e70f1e14729495c5daab3199f4cdf0ac78a87bdf","affectsGlobalScope":true},"7b550dda9686c16f36a17bf9051d5dbf31e98555b30d114ac49fc49a1e712651",{"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":"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":"3cc8a3d123b6b232d48d34b51b785f9da8d193f5b5817fa521fcd2f3b9315c55","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","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":"061446b67af18b541c723104f25aa94667dd438c050fc873f3c02a7b5a9a3ef0","signature":"b8ee70929b7bfa2ced6aded5f38945440e9ff6809c61d2972b59aaecf88c254c"},{"version":"8ef0b457802d1883c0c185f90610a8aaf33283250633a31853de01bb45565b7b","signature":"b730dbc27807d6a94494d69e0154827379b8ed4606f3dd3a4584a1e2242b1e53"},{"version":"64bc7684d633c835220935b80701168771e6ddc8c3d9145af8bb3a3ac7d0c59a","impliedFormat":99},{"version":"121fc7776751821e405243a0c188554d2749dd334482a1d311af61373072a89a","signature":"1c508f6403621b58f8d59e7eb61eb61788714be526c91dc3cad739330b6923b1"},{"version":"598c32af38ceddfaf9699b9013ecf2e0b2df7b5d76795c9de010d5ff92c52ad5","signature":"e064b7ccad9850f3a78ba58a45e43e4b3eaf126cd2bd2979896b5885dea07f57"},{"version":"618bb47ac74b0ab39b90743cfe920a6e670b2d3bfa24f37d106b535c1c18ae37","signature":"4c8a45f845c330caf5dc2bc33da6e8e4f317035d08a20fd5f1da986bea511d60"},{"version":"9f50731b7a6739ad4d5d0e00b5d0be3650535cd74d92bf86ba3b81cf57000269","signature":"64be38d2ab0fa005245ad20baf0fc7899f1db575a219b4428e0fc3e550d02410"},{"version":"1d0628911b56f83b654ca0ba826d503b3605926e73b985e754513832eeab5592","signature":"45b43c38bc20ca8c0edcee887ed49ed8b771dbe78e1cb5b4e3ba794e853fa887"},{"version":"6d575d93896c413b308c3726eed99ddd17e821a00bdd2cc5929510b46fe64de4","impliedFormat":99},{"version":"1beebd50610b0c9701d2de263e0183ec22aad6c051d0e15ce9e6cce295c6a40b","signature":"3c49b34b1c62e5d74c637b63276c9acacb605689334f5450cc3f67f560ac0ecf"},{"version":"310c820b803950d18c0ed9376df2cd73def2f56cfcc993f9012008403cdd4843","signature":"6fb95390f4022e0327e4a170917a06de5caad8c8c563c8b00be3cd40a71c759e"},"b7c5583a7e76b44bf7f987827c2793272b11c74d9f0a5c6b196f1035f8dfcf6a","47f5078d810ecb6e57eea5f0382dbfb9db641a35460fdb723e920c4898852e0b",{"version":"ddc62c8eb6b7fb8e8fd0f0f19809530b1e5ba5a131471a6eef65028d0d3b5a6e","impliedFormat":99},{"version":"802cbde8e06732ca0356927b4c9fbc39f5961df58a18b74fdc4e131269293a5d","impliedFormat":99},{"version":"480713ff75c24f445e3f159da28444406e1334730375c94d0aa24523c5e52e1c","impliedFormat":99},{"version":"3ac6eb2cafcb89a552a4923213c705f0fc3c2b50e466eae9dc1a540e3af18bc0","impliedFormat":99},{"version":"073f96a1cfddfedf8695401f8328a8e84a4d98fa5b08b4d894c3885069083cd6","impliedFormat":99},{"version":"66ba40fa928c2fada9a280a61c2b426dfbbfa69085f99913650ff72ddec75b1a","impliedFormat":99},{"version":"4d857105510df8011cfb5b3769dec55624a1df92e85d399cd03bc82bb89d090c","impliedFormat":99},{"version":"19a22f3446387435f13445a31e3d4eb65f132d8e6b7b060d249f0fb138cec698","impliedFormat":99},{"version":"ecc46b24349caeab20d889baf0a6f9d3beafa739a0f2c36afc107dceb15e7b2b","impliedFormat":99},{"version":"b887624859a2f03e78ae6018e96bd269b5318685f42adec4e93256ed7579c125","impliedFormat":99},{"version":"14191b461a91229ff4b388d15b9e15392a8a3af9bf11fa7d0d4cd31405178e75","impliedFormat":99},{"version":"23a564e852dc91b6e6f050584994b35156f6ee8a2d08c493dad04309046a8397","impliedFormat":99},{"version":"daf66c9de89f11011ef703af894970bb15985fd5a4156b8038e895ad4e4616a7","impliedFormat":99},{"version":"d59c3d0c3283c1878913fc2bc88d84160dbcdc69cf06f822ca7ffb39eefef13b","impliedFormat":99},{"version":"05128b72488ad970c2e30ae6b82c7ee232be49ce6def3b4dd56f62d8b7f7704c","impliedFormat":99},{"version":"9eb8e1320fc0ecbfba15c0f3452dfc1957543dfbd466aaf8b67ddb0f2ad0f217","impliedFormat":1},{"version":"4faca872dbd194a17b3ee267bd8ddc3daf3d16df96f4e43a02c7d9a862022c4f","impliedFormat":99},{"version":"87654de60b5cd8d91d59632ec576fa7e313b41c2540073d52814b6cf5bb739e6","impliedFormat":99},{"version":"144a4e5780b800c0553949169f50be285eccbdb0298afd83ef2ae03fef77e2d2","impliedFormat":99},{"version":"66aeb47bf8638d6767f7b4ff684c2d794391c981590073025e98f98e1afed499","impliedFormat":99},{"version":"cd5b0672c9699fe169d69efd65472a874de9d1e25fa8669a934f5f326bf0f025","impliedFormat":99},{"version":"4577621880c696b0aacec6ebd2dbf97ac178ee2e2bfaa0aa3a5260a798220ab4","impliedFormat":99},{"version":"26731910f98a56ed001d25d5167d85b1320def4ffbb76e1cc4b0c6484482a5e2","impliedFormat":99},{"version":"cbaadb95dcc68691900ffa857b3bd7eaa99eeb6c351afca15103560bc87f0d15","impliedFormat":99},{"version":"97b02501eb45f487174d5a0ff89b6a95690d50e9eae242e2162118edd5f2705c","impliedFormat":99},{"version":"bbea0619511648a92fe83d5c8eed6149106d7fbf3065310a1986d18598b83bbf","impliedFormat":99},{"version":"963ece6abb58542445eda863960cf053a98da8f4e8634b7a8826aa04f6f85a56","impliedFormat":99},{"version":"5ecea63968444d55f7c3cf677cbec9525db9229953b34f06be0386a24b0fffd2","impliedFormat":99},{"version":"1d226c1e6786584e97efede708d49f2dbd6f887905f16c785d5f09b300bc098d","impliedFormat":99},{"version":"07ff7d4360fbc945963d7a4a8105a5520d1681a00745c20a962fb36bf04452de","impliedFormat":99},{"version":"1b1c48c4d7cbe6f40616594c2a3f6f95bb1dcefd200a7e4167e47b67725b631a","impliedFormat":99},{"version":"62076be1e1e8b668a8ddcb803402f1aec725a31d592e8722ff39ad368d9cd472","impliedFormat":99},{"version":"2806e4d2a88e0461c3b0c8cd9e7bc8e927034690e33345aa0853439d67f801b4","impliedFormat":99},{"version":"b1d72bde8f54695b85883613af13295a615034b2829dde3a31bd3d2a40eb6bc4","impliedFormat":99},{"version":"26cfaec143443411bc7d5363f274f885ced430b8f4bee25a81f7827248848d7b","impliedFormat":99},{"version":"6870f32dc76ff6f8f6a419ce55add0a011909e8895252d7cca813835f431783f","impliedFormat":99},{"version":"29fcff21ff0ecbe700c7db7f719af2fb4822a08d6d703b6687822535e8bd3126","impliedFormat":99},{"version":"5597cbcd19e16f5c9148c76c914e158680de55849b625c2f6b69723f01f1007c","impliedFormat":99},{"version":"7864233f21a3bd04eb6dfa79103a6c1d0648cf17eb4c47cc7aef19d274dd639c","impliedFormat":99},{"version":"b27f7733758db8f462dadf0ee250056e370413028c99fc723c4a93baa54a7c1c","impliedFormat":99},{"version":"5bd7f6f573ac89ec20aaf326e79394da8a89fbff8a297aa864de9137ca045678","impliedFormat":99},{"version":"81b2bebeae6ec1e73b491fe22a82c7e2d3a8369271e622ed74b6e94ba108a475","impliedFormat":99},{"version":"d0e4a184f48eba140f30e8f770853b884e01694f6aff59b38d0be55b0410d397","impliedFormat":99},{"version":"1d34b3ef8e5926334d86d305477d3592d648adb41fa0110970a68059e13d45c0","impliedFormat":99},{"version":"791e26804cd328b19fc37f7903813e8e41892e70d5241dbe2c39fdb52fdd0c9a","impliedFormat":99},{"version":"02c2773eb8536a50f6e647483e78e8c2991fea8ac32ab69a37f9a24255401530","impliedFormat":99},{"version":"4ac6d584eada1621a7eaa4bfb3dd54e81c2c8a82c7ffdf421ae58d84c3a3490e","impliedFormat":99},{"version":"a522abad9b9b959a9c4bdb4e6bdad96e65d97da9385be13ffc8affc3669fd786","impliedFormat":99},{"version":"ae733b8a8fc9659e24821aa3797d25cfdc205bd31674227b49411ca4d54e510c","impliedFormat":99},{"version":"0aa9c5135c3a086d7c01d8d18409da6b01fd32f09a4a261048f8cb4653f22be1","impliedFormat":99},{"version":"1e185a3af4b4f3bd6fd52fde968f14dcf9a8cbbc4924237270e290e25d81fe40","impliedFormat":99},{"version":"ae42c6173cc8ad49d6ae21187d0bb7c7c65da10204f9e2614eeb83b29c58f4f7","impliedFormat":99},{"version":"050240464b97ffce2e353ccd5251660f5d3dcf9dc834f88504732ded7cfe926c","impliedFormat":99},{"version":"3f896952650454552b2584ef1e3dd072e97f8498908cd2ab25e6b0217e8bfeb2","impliedFormat":99},{"version":"3b3d0685f081f6a02cda029e4d1e1ba5f10690870c971e6697e0c2539501e835","impliedFormat":99},{"version":"5998b174ccb38a61393170f40448f80152ca5518f9d2048f5b5d3cbe0a9fbac2","impliedFormat":99},{"version":"6707d39d8afa069222d0674016d48c4772067eb671f9b62528a6cc8218fd5b40","impliedFormat":99},{"version":"481aab62f04afa6eab4e439fb4f39af392c5c51519f548897ff71e6bad0b6771","impliedFormat":99},{"version":"51de9d738596fcc085d13bdf86c0014f15d9b4e6986631c7be3df9d2f61590d8","impliedFormat":99},{"version":"8eca47167dadd486582ecd4e41f7fba6ae66cc4a4c5202f1f7acf34129a0dadf","impliedFormat":99},{"version":"29cc3322fd17fd1b55ea2150ad6f7cb37f0b587efca5696819cc5b6e95331bd4","impliedFormat":99},{"version":"d09b7414a64adc7cae660ecd6e8a222ad9fa58585dd2390eb0aaabfee812b354","impliedFormat":99},{"version":"769b6f9f1cd9471261d137513abc391a744a3c3a62f492491bcde520219fab53","impliedFormat":99},{"version":"49fdbd971a9b57df943498b37cf11c40fe09b2675493039a0b7841671f385108","impliedFormat":99},{"version":"faba6f3b673c89279d3b41a47e8ea2c850665eadfa1e2a56be4f50a6bf4356c6","impliedFormat":99},{"version":"5cbc3c3c6475704af132c35b095da392a03815baf2e9f2853178ff9b370b64d2","impliedFormat":99},{"version":"074209bc8fc6979cfc363d392a8babe62685adc61c62a8742ecdb86fb9b62ad0","impliedFormat":99},{"version":"6826e70645f65e77bcceb9230962687109301a4ad9d6dbb71a7785167d4a4b9e","impliedFormat":99},{"version":"3c8b637a833f97a085417e7d0024ac82f7fafc0834a4c61d5e48f8edd8da6c10","impliedFormat":99},{"version":"a3a4132d6c64f431b6d0cc890557c392f57eb43371bb73979ea38d27c86a1c4c","impliedFormat":99},{"version":"ec03be0777b98df75dcd97657ebfac0eb7a9153867aab050a591b6caadf1c2a2","impliedFormat":99},{"version":"c788aaea8be5712b40c3bd9cf589c9510930af7b2aa3d986125df0dedc569290","impliedFormat":99},{"version":"e7983072a038e512514c146b25e7e97a8a070ca3507950658ab1e96f6598957c","impliedFormat":99},{"version":"f2333bb4a221631fe506a0354fffb808507d4e5f6fe2c85b69890618226f7d9b","impliedFormat":99},{"version":"6c9114366ff07ee8f5c3cd4ba94ad189a098ce8368040909d605fb38e636d026","impliedFormat":99},{"version":"13e930c27d68ecfa906c24d599b10927b152030d07da0fa0889fd4fddc5b4115","impliedFormat":99},{"version":"b4e61f4f522304f7fce1038590ca1f6d091d58aff84833861848f8157732d8db","impliedFormat":99},{"version":"8c86f563e8bcefb0b5b1ac62e5a27ee6a2a9b775e72dea5793823edeb24d36e9","impliedFormat":99},{"version":"d9134c8daef2565f20b72171f634800efb204eba63b03142d5dae5f36088e95a","impliedFormat":99},{"version":"faf9a217d8d237b02ab6d95508d8736ae431bbeb38d98885eb5b8fb6dbe48cec","impliedFormat":99},{"version":"e702ed1fd1dcb24ec2634901441fd156449f75458359c771074cbe7675e86614","impliedFormat":99},{"version":"bbb044421875fc84b7d2f2aac4fb14499687cae5a5063da51bfa28c58239bcfb","impliedFormat":99},{"version":"74b564cd3da8f83d5e472a5b0cc53bf7e276b25576097cb89e6f67caf95b12dc","impliedFormat":99},{"version":"3705ba677801103461ff0a06d34b6b2149072952365e55d8266969978dd33154","impliedFormat":99},{"version":"cca68a7703ec3717b6d4c287884fc79ba811f894c472718126010418cd306aa7","impliedFormat":99},{"version":"590708a598f58b156518493c563df1d03040d3b2b7f75fe614e1ada06dbb44dc","impliedFormat":99},{"version":"dc2c32ad9c49a7c3e56a18f3f42933e91474bc26ecc2ea47cf533818a54e6471","impliedFormat":99},{"version":"e061e898ffe9970c067278f5a7462665e2706e7cc6ce2362276eca1c92c128f7","impliedFormat":99},{"version":"e5ee49966285e5afa0dd2db7f66acf1e8a9e1d0bc5724b03b67be92ea7819bfc","impliedFormat":99},{"version":"4db2be160aa80fecd367876f8cf1aa197cd1f296e5f82ed8d8b961d9ececb204","impliedFormat":99},{"version":"5a8e4a5e571755e265bd6a840d8ab48eeb1ca2e35487d96bbd601ed296e2d1b3","impliedFormat":99},{"version":"d93cac0bbb7e1fe241f4b0493cd47466df00d9f1c51a53b69e5442456cb4d102","impliedFormat":99},{"version":"9de94cfccea0da314e8554d6b2f1f01a1b63fa4c79dc24b54277e86b918e9d6f","impliedFormat":99},{"version":"b4f7f4e2e4d0e668ab7cfd94ae5b72b6c690eafeac0e7a6d2218b16afdf7432f","impliedFormat":99},{"version":"5e8d925c0b8f6f91ac0af131a83f72683f88d80a61f5eea37d8883afbe8f74fa","impliedFormat":99},{"version":"056e9235afb474b7b2ffb6df16ff331f5238027b185367ca745103eb228fe57b","impliedFormat":99},{"version":"2eb77a708b1d812a8b0a57a6a12cbdb659bf43acf839b21b8996dbbd511d6e53","impliedFormat":99},{"version":"9a2d7fe034d084982a18ed744a3e0748f4768fdc5b9f2cdbf5f190e5226b54a4","impliedFormat":99},{"version":"1cbf7a0290d370c2843e79344bd494a10d267b3e0323bb77cf1b34a36ecf4200","impliedFormat":99},{"version":"2dd580520217749fd86cd77b8e48075a6c2ff32339e2334aef676bd3800f345f","impliedFormat":99},{"version":"939fdf70427033c0a05d112c2b03e8e31f037b8f2ac4617df680107162ffb423","impliedFormat":99},{"version":"9bc7a3d724ff20d2429d94e087c276b9256946b2cd66c9f9bce79ab54ec9c115","impliedFormat":99},{"version":"aa813b5adf5ecf364ddcab7bc6652db73d5c4e43ee5f6ccfdc7737f6d3184667","impliedFormat":99},{"version":"e5fcc46e6fc608a77c7efea569e56e3cb02491a9fc0d74f49e784d0a4a6aee14","impliedFormat":99},{"version":"fd413d87e8bf7a8e523c70d194b2c3279016d1ec733a9db43640cc1e0cabde6f","impliedFormat":99},{"version":"126cab464ce86f9c155c0b79f9c38fa906c422ac02c856ff9874051ca35ceb14","impliedFormat":99},{"version":"4d84f055621f07107b6e882b0cb79848106d08899bde344eb6ad0c9bc3539eae","impliedFormat":99},{"version":"25ada8b073df8f9b669aa007ee66298095904b83236652ee940d827c2ed5fe9b","impliedFormat":99},{"version":"24bb5860d0b4310843a2ce164c113315db19861f3fae4f2a56727ca9b98dc4b4","impliedFormat":99},{"version":"b62d96002ec0c8710d0e99aa3175434e1df0f22f5a09291b19e5ec05e8a877e6","impliedFormat":99},{"version":"221c86478853bcca59d83ce0eb2832e575779f2244a9a0176971de55c45b9690","impliedFormat":99},{"version":"7b8940dddb145146d5e62f9d817d5cb9f54345cd17bb91a363c293dd5216a377","impliedFormat":99},{"version":"7d331ed732ddb23a5e04eb12716cff50491ba01b712f4810df496c174547403f","impliedFormat":99},{"version":"a444b1d18b18c90477babf60511e8348ab9d591698205ed1bf12f3a0bf5862e0","impliedFormat":99},{"version":"08ca4dca79ba1cc23d4610ddec493102d3fdef6bb57f025d99b1cba9759f71b3","impliedFormat":99},{"version":"39c2d0f3d8d82809c02668743fb19a50e66f05d4336d48765946e4a051d0579f","impliedFormat":99},{"version":"495e122ec7cd8b18150ec1191e48edda4e23b2587022e59805571ddf8a3b516a","impliedFormat":99},{"version":"9277cadea8fcd4c10616d7667f521274c5fc6cef385861f6962ef880db3e612d","impliedFormat":99},{"version":"20b20c535eb79b2a4a62229abf83f0fcdd3dc1f041fc3c588dbe01e3a7666ef9","impliedFormat":99},{"version":"e98970286b6514c67e3b0f916f23f8bb81ad6fbe3b5ef1f2bb013272e9ccb00a","impliedFormat":99},{"version":"09c1c46f10e01ae7399f2fb391178be7ddd42d70dc6a3abc41d80ccf73badad9","impliedFormat":99},{"version":"31c9882e1d08811f5821ea24554c0bd8a0d97fb7efc661ef76393a28d9a8eb26","impliedFormat":99},{"version":"7c9aecf4946da6395949f23bacaa6d7e9ce287f5aa65e50e69332ee5f4d1960f","impliedFormat":99},{"version":"6701adb65ce407ccabefbaf20862daf55d52dbdb2a663c899d163cc6cbb59192","impliedFormat":99},{"version":"f73df64d28c41e3bc777eca2fb49cb5cda69b52c3786b32d4dc473f855fca42b","impliedFormat":99},{"version":"b342ffdc48ee317927f88cb38871b984b7edf94634428fcad875c7a9fe5515ae","impliedFormat":99},{"version":"247fa787c809e9036079d3f4bf429f5c6e4d76a31647d5547e668fa25c46477e","impliedFormat":99},{"version":"f015d64096dbfde32ec9117706e6e1376e9ed0ea8534d17d1c4035262fb82ebe","impliedFormat":99},{"version":"0af38d2d00fc29764aead613ae52e263e235289ec9e2f365e909226e8b2df2a5","impliedFormat":99},{"version":"c610c569ccfdbcb03d9e531ac1be3ed944586e099bf4f756885fce2d5e1a680c","impliedFormat":99},{"version":"2062be175b1e4f6a0b6b21b4ad08c1e241833349fd82aae558000acb2a9c905b","impliedFormat":99},{"version":"32c98d5e98a05f108f4e405c853db481f83c5a1a9cd6c53870501d8248f9afad","impliedFormat":99},{"version":"c717d81d125641e3d95b30cb00d3c0179fdcb30c9e716c360aeb23c699e51321","impliedFormat":99},{"version":"9a96c65bc8d115c4cd1f6d61305013640593f0c0f869a2e6cebb7bbcdcd7313c","impliedFormat":99},{"version":"d5e101bf2eaafcf94b79c0a80a8b86e26ea0b24234f8f5b2c88b58cac0842a74","impliedFormat":99},{"version":"27caf95cace62037352d836d1c547a73363248289ba8b05205cb9eef146768ba","impliedFormat":99},{"version":"8ac1275f4eef836ace2b3779aa240cece0a7094cee65e3a56fc730a270695b0b","impliedFormat":99},{"version":"4dae97da440251bfb634edda1739b3cf39e66b56076e05d7b06bd3181a6fc500","impliedFormat":99},{"version":"66a183f89f492290d10baa4bc6840fac3a0212cd3e32f2230c1506eb6b1f84db","impliedFormat":99},{"version":"6576f83f333348274a02f3a9a048dfd9c0fbcc3515ec4e654def0ec5491a6261","impliedFormat":99},{"version":"f657e9bb81b35be0d298f305f4a6924c4b652692f9d48512038015e7eb79c7fa","impliedFormat":99},{"version":"5ea4c5fd9091e33b07825015ed1cce784854121cf42d337f0762f1d707ffacfa","impliedFormat":99},{"version":"0a6af3e7a2a63fec578ef9940ace9987eaa91450112efa665dd94cf26555463f","impliedFormat":99},{"version":"172423ba720956a2999c4e44a640d6b141c1c8646d96e8a88a333181eddb1eea","impliedFormat":99},{"version":"8d1ee20c4ca7a97ffb6c9b19049a1a9ebb34bfff32379261bc6295e82cb77abb","impliedFormat":99},{"version":"258436bc14be16b94eefec3da57b4eca7a3c1df633c79d4ccc35f18eaa9d8107","impliedFormat":99},{"version":"0ad0d843d93b5bf3fdaf79de4e159e28d6f9367a945970413205f345e9797cbc","impliedFormat":99},{"version":"f9a161a77ec523402d8d7dbaf9a04e9fc3d32d0b304dca4d7a86412bfdd1b1c9","impliedFormat":99},{"version":"ecaa337dd6eaa40a78934bc53a46455c969a9e2ec75e07da806552e5e1f5f575","impliedFormat":99},{"version":"747703dab2b5bfcb0f4372616373cbbe85a8a9e246bf4f2002252c54f79750a6","impliedFormat":99},{"version":"c09f4c7ec02ad3b5be269a3e220d69d3f16d43fe3843e2e75263344d3ce7981c","impliedFormat":99},{"version":"d351678cfdd7d86b5dbc0c75eaf66ada923f7ff1c76102508ac22f703cb9b927","impliedFormat":99},{"version":"b3d820765aa7672d9276e319e9a2b4d7a928b5dbfe34169e287bc2c0a03be70b","impliedFormat":99},{"version":"96ce9dcfef17a1945dbb4ec0ff2256f3847e813671bcba46381fa6673cf8b202","impliedFormat":99},{"version":"0d852b4958e9b9dee49676e33381e33280a0345bec8fde3f902b479bd0f69e37","impliedFormat":99},{"version":"1ee834bd1a5b21ee9f0f8e683ed8f46410f2548f5b81ae090c14fa41ebe3173e","impliedFormat":99},{"version":"fd875069349f1541cdbf2859ca8b0acdb81acaffeeb579f74dc08b332e8a2fc4","impliedFormat":99},{"version":"fb6994ae9a491ff440c5a78667f4d5783fb6c5827050db94f9ca7fb14f8ff260","impliedFormat":99},{"version":"3318f774e0fa8cd7decde2830e561c401e53057ea505c031f687966a16f4b32c","impliedFormat":99},{"version":"c130a5e599c565b49b02dfdaef22c5dc68bf648a9678339b44e8913d3d27ce71","impliedFormat":99},{"version":"06cb5fd4ff2e5cf532dfc6bbeae7b47ad7c2879909e6727ffdacc558115ebf0f","impliedFormat":99},{"version":"5cf2e81f262bee804fa9d50112c5288ed4224243b1837c653c9eec5a621a9b13","impliedFormat":99},{"version":"aeea67ef93786c8625e6c2840c5be41e6f6679f9890bc75628ac0a3cf8ea0c04","impliedFormat":99},{"version":"a37b86cc490287c9723338ed95965a938886313f0f912ba12f789462d8bad89c","impliedFormat":99},{"version":"41a073e65cbf693b4ca1f61f6847e16227d023cacfa75a84fb989efc3545cb19","impliedFormat":99},{"version":"e726badbad2c619272fe4fe528dd07cd5ef87bda456dc3656e4fd1bcc11976d0","impliedFormat":99},{"version":"60b0f3b27eed4652b4cf70ff359eecf92d1dadce962239812474436b4d608da6","impliedFormat":99},{"version":"4d7002dcc54793296ab4c4b1e28c00e99cdda63ef31b83ef616c58f8773c25bd","impliedFormat":99},{"version":"040dbae8a47533338afa394e6974e753b4bfc1895c322a3a715eb1be21eab5bf","impliedFormat":99},{"version":"a9d4d662f3494ab31e98c8193f20b0725a9488225df92bb4df2d9f96b5b7a166","impliedFormat":99},{"version":"6170c6827bcca40ead01d9a8e92e73049b82a0e595f1c11ef39bb98282781f7d","impliedFormat":99},{"version":"5dd074521b20eeb26c76fb3e1d0f85fb4bf26cd247c7dffbed08bd888a6d29d3","impliedFormat":99},{"version":"f3a8d4b406af14afba34488fec9b89859900a8df10510a23d9f1c2e8a116d3fd","impliedFormat":99},{"version":"b03aa91aef645f9856216a2223a47001a84954caf37b7ffb1d63d1327b4231fe","impliedFormat":99},{"version":"01b6435dae2508e231ded5ca79334075da7d6ca12d909765cb335211a90ba86e","impliedFormat":99},{"version":"75fc3992422a1d3b15788ee84656da98a10ce15ce5ba257a0df623a024a0d845","impliedFormat":99},{"version":"b6c5cede83853964b2f753d7e202613e1d461857cc3780a57a2a3d346c5afc0a","impliedFormat":99},{"version":"ffc4846043b7f71f310692e4bd38f349373981b832f907963e4bbdd4288f130e","impliedFormat":99},{"version":"54a730e06094b37f96436ccc8e736bb65b74d256439bf1663344e3fab16d2246","impliedFormat":99},{"version":"1389cb1ca8557f7380f983f00c337969542d6c932b1ba294b48f97f6fd1cb69e","impliedFormat":99},{"version":"85cfc4f1cd043b1df65ba7714d292ba7c6c79c9e288db0d4a9ea6a7b567a675b","impliedFormat":99},{"version":"74cc10ca21f4fc15188d7e7aafd66de5c34c82d011f0c9e02b05b9739e0fa31c","impliedFormat":99},{"version":"abe83f442f76121715241d0fc207d2c325510c6a4dfa6b07662f550c95c6a2a2","impliedFormat":99},{"version":"4b6de64797fd57745c2856f26b4c7de6be543f9335dfde7870a186c3541ff183","impliedFormat":99},{"version":"765122fafa15af14742c91619b7e30b36e5c38f01e6ad079d2c5ecd38a4fc45d","impliedFormat":99},{"version":"1194d3241ea56738d7d8e2b4908572a350cfe7a85b82ef89828ea32e20ab1803","impliedFormat":99},{"version":"6449789627c9555d2914c88498ab494cdc4f18e28a7426a1e74dcef3401f181d","impliedFormat":99},{"version":"17326f1b693cd3a0e89fdc1248097f0135adacbc072b0ed62cab9eecb1c21743","impliedFormat":99},{"version":"1b322be99b786ec951d3d14283aeddd32c3ab25033c4cb984b5224630317b232","impliedFormat":99},{"version":"c7523c0ac422da80b521031667dc06ca66d817ce5ac47f69db6fa98531febb26","impliedFormat":99},{"version":"19203771ab06e45e1524b7f608b332cad7143ba3ff473e302827a835ecd99dbc","impliedFormat":99},{"version":"3b7b9365174c24792ba2c762637b0bd5cbb8d88a72153e3f7f82d34e115d5647","impliedFormat":99},{"version":"7e3a2715195f927935488d7565bc30e7f540797776e1de208c64720d4ef87f77","impliedFormat":99},{"version":"5804ffbc65b78751fd510218b90827a7ca677ca34a45b4709a00783b658cbaba","impliedFormat":99},{"version":"0945a03ba41861ce8f75468e2bd1bfd424185418921fc2f55cac5eeeb5049c3d","impliedFormat":99},{"version":"246dc85745d220f0a1041d67bef89de1e02fabf49e6ce896bc1a345eab1fc507","impliedFormat":99},{"version":"616b74da95e0f9bca845458de4a8b25f12142b4a7b02e89882da05b4cc115802","impliedFormat":99},{"version":"b894722e4b4205a60154ee3d6fa8ecc3ffdfb92a7bd38936f666d3f00be6649c","impliedFormat":99},{"version":"1f7f05258c0992bd696cf00984e640011ae5477d7aac3b80fcf61bf27f42fe88","impliedFormat":99},{"version":"9129342b97e39ef2c9df4848dfe011329cef9b27e719c7913fd3859be5fc0cca","impliedFormat":99},{"version":"351edaf90b54a559e1759f7ceb54b7881079cba5f4d6dcf15bdb26f1877dd2c6","impliedFormat":99},{"version":"3f79205d951373afec1ca713cbda4be9816d97daa795a9e0a37fa3ae5429afbe","impliedFormat":99},{"version":"bcc4c8b5a39356915b8d366e3499a28adc89e2e0bffc02a108eaec1c4797a58e","impliedFormat":99},{"version":"5b2287eec9804a7fc7c6021ae0a7a92b0160750eb21604b77203589e2ad905f8","impliedFormat":99},{"version":"03870a19c7cbbad803b0ee2d69b777e12be7734e087ccfb0c862529a41cb493b","impliedFormat":99},{"version":"46a52d6ee42784826515dd6ab9f5afaab3a05dfb49ddd8298a2026b6c756b944","impliedFormat":99},{"version":"995f334b04df585cb2a77b74533441293ff1e1d4549c86dd5495494c1fc3969f","impliedFormat":99},{"version":"e83b7824f3d983e9b8c2785541579cd8d8c153e96959e71ab4f69bd83c71f953","impliedFormat":99},{"version":"7b0030262f3d2cc74ae1dd79f4990a7131c34935b2c177e6cfa17a88a6ea56ee","impliedFormat":99},{"version":"a923cde26c2e5431e455844ac5f31126d45976c85f347c7dfd2b9eba3e8ef63c","impliedFormat":99},{"version":"3b2738cfacb777ea1f53acdb26b4f4306fa3dbac7fc5d0f1c4750350d3f5741d","impliedFormat":99},{"version":"b95d11a17e57f0cd0ab04aa8148c8f0ca3a68f56c9a44ac9179cea8a6cccb546","impliedFormat":99},{"version":"7688f3196338007600eba7158240aaa15ad524ca42c204fdb3888446fd690086","impliedFormat":99},{"version":"514f33cfc8bf4a00d0603f6df438959657ce42f94e93e29df29fa9b58e7d54f9","impliedFormat":99},{"version":"7568cf2d6e505847c539e63406ddbde2ccc0f96f2e6c5f115a4b9774d0b55aad","impliedFormat":99},{"version":"d05bd9004c654c2583de473d77f047f03719e3e7bdbe62861371755208e36d59","impliedFormat":99},{"version":"74371225d6032ec7f73b46e736d9ff6ea3626be6fc7959e8b71fedff0bb75cf4","impliedFormat":99},{"version":"06dd247275efd44b3f91270763246700353f1add0945380bdbca8c90a517f9f1","impliedFormat":99},{"version":"0833e55be9920ff787cedb7ea623e97ac9bab28961e0e11aa4a56d36d6074dd2","impliedFormat":99},{"version":"92fbb2b6566fdefc6ba3f151299b2618bd1780cf26c2d0078dcd7f1bdc1c551e","impliedFormat":99},{"version":"3b5317db0574b276c1ecf6ebad9faa974f4e416786b682ed1f854cc85837c3df","impliedFormat":99},{"version":"5f27b1f1b03636451e90fc414bd8426a1db25ad438782354bea60f47d7efb9d6","impliedFormat":99},{"version":"53eb12cfe4c56afff32a3b8adec4fefefa12685c84202c8207351004d30c3b3a","impliedFormat":99},{"version":"05bc3698de467024d02654162f1eeb4edcb0ed9d855a96133572969a6f3675c4","impliedFormat":99},{"version":"a5ba4a306d8bc21ac2fef4e40e9076708dded0176aa21484f1f6da23a4d400e2","impliedFormat":99},{"version":"1c825d1f1bd9e70c306f6c16a0a6b76ccfe4be9350857831eba93e59b95fbb5b","impliedFormat":99},{"version":"b27224caf8db7ed9edf9b12368cedb963bbba3a9b5143c68dff53f5fb2351c96","impliedFormat":99},{"version":"eb3bfb8488f260946c5bbf5d9e730a6e23e0c4a568fbbbe782f3c365e0595dde","impliedFormat":99},{"version":"15de6ee96c8e0f6a78fed11e60c3a0f9b4535c1e6a802c55d65028d500e91e75","impliedFormat":99},{"version":"052f62cd94d56a5ca9d8ce7e68a2201fe8f399a12d7803be2619fd03dd36f1d9","impliedFormat":99},{"version":"06e98ec1e0428de740d985f3480b2e699826d5cd2fe2457f1265b32ff4797ae4","impliedFormat":99},{"version":"01b8daaa0be6124a730b7170c1bb1375f7ed6acf1b4b49c1389199b5ffb600e7","impliedFormat":99},{"version":"7b0d3cca9104d4d9f484ca0a64bf731ff1aea842c8a4bf93618814b1a8281992","impliedFormat":99},{"version":"e40aa12df628390fb3819a883c52c51ef94fd3998e74965fce6a38917a0530f4","impliedFormat":99},{"version":"0df397db19a2db183105dfe900d75798622677a5db73038608bd325f86a556ed","impliedFormat":99},{"version":"ab505b9c7ee7649920023b14384c71e3c542bc7535f51028dff27d70d2b1d6fd","impliedFormat":99},{"version":"29a9fb009bcc76c847dcf73d820d276d6353e5c6c4c016c847d51e42796f68f5","impliedFormat":99},{"version":"743751f2d8819fd7ac9d3ef6378614b6675d3101e42ddc18767901693621cb2f","impliedFormat":99},{"version":"e4a995fd487783122df0848df4c871dbb536e1636e8e7b6f6186d2993e9761e8","impliedFormat":99},{"version":"45da721f9a605485a439778c248dfbc6351341d87de448ec266b74185e090631","impliedFormat":99},{"version":"ec47a4e180f0cf61787ada2d4691a1cf4f7fd65482f6fa9e01444adff3cbd6eb","impliedFormat":99},{"version":"3209d42dcb86b35a13c127fc39981a644b61a1fb0e59524038d0f3bd7fe25768","impliedFormat":99},{"version":"c8c16f7fdc34f8bda36cd9827b11c065e94ae25473465b2b35aa71df336ecf63","impliedFormat":99},{"version":"db07a4e9f69cc9b58930c2d3a4ad1fd9f882794b92208d55ab057443081f649a","impliedFormat":99},{"version":"735a572ced293fa984b3675cce56091902a0529cef028fe016d9670e3a94dc8b","impliedFormat":99},{"version":"dcf0056dec8dc80fe76eac1e8c6fa778a2e4c094fe2d4b120e6f5bcabd820be8","impliedFormat":99},{"version":"6268a89f0ce2f857f6f7ada0045bf8dc990b449f648b51522c0a7d84d016fe85","impliedFormat":99},{"version":"efd3f26f59c3291a0998435ad54c67191b39b4cd0d451ac807afd8da86bc1996","impliedFormat":99},{"version":"19b70aecc85035f5faef7f3da8dcbf199af4ceccbce15a670950377b388c1d9c","impliedFormat":99},{"version":"d621382b4ad80cc27b2f670e44e0bb11a7e85cb0f6a0b043aa0c9b6b21b16a15","impliedFormat":99},{"version":"5c1f5f0c20f5171a182440cd0347dbb94e5c84f5976184f2f36dec92afbd9b9c","impliedFormat":99},{"version":"1a7e2345e3b20202800bc92adbf628d22a74902b8a5c87a6ce3c361d3ba314a9","impliedFormat":99},{"version":"eea9dc67b1bd75f72aad8483567241f5fdbe46436f018df7f0719e7ee5aa85da","impliedFormat":99},{"version":"5bf7ec4d84bfa8c29f32b7cde878e8ef4e11b1bbf0f4edbb9e851efbfdccbd2b","impliedFormat":99},{"version":"3a49927f72440d36c50e1b62f5cbc2f296253d151ea4e5484ecebc8bc461ad4f","impliedFormat":99},{"version":"ce293a2b914083388ff1de83875cc6e82792c5dc1e99c3be4b787f6b150516bf","impliedFormat":99},{"version":"f8d6e2784bb518d523898f614b8c0ae55341968c982d4617f08867b5d11cf354","impliedFormat":99},{"version":"1413f593b860e74f717f40bbf5c934fd77ee6cbbc630216954bc1a364d5d58a6","impliedFormat":99},{"version":"f7e358590496240e80dc08cd1b71ca492e4d27664bb403d3efbb9acef5075b40","impliedFormat":99},{"version":"656e30f229e3a05096b21a8d0b4a37cadda6201d74631fdfd6a6f52f0c158831","impliedFormat":99},{"version":"03206d1ab6b7f08b118786a903cf849768c8c927a21022df88fe63910ddc3433","impliedFormat":99},{"version":"702cb19c1b38ca1b2d158d765869b667b2c1e5ca0e62862b7792285055cb86d2","impliedFormat":99},{"version":"396f903b4d3bcc1d5a72580bb0a8d9f90c7dac5e481b81c2b58df80c968b64e5","impliedFormat":99},{"version":"0e8707f15586d91f92a120b4751048061e04fdee756246667158d4df0105dbe8","impliedFormat":99},{"version":"c37e7b3d6c0b5da08a46d028e980becdd8d48d7b32b7644209695d75d43f653c","impliedFormat":99},{"version":"fecc5365f9a1dd29cc8c582bc0427a7bf06a52c2a42cdb4b25012976628faa6e","impliedFormat":99},{"version":"0c073335c77c5ad0240a0303cae56c8be8da93e206591c5a5a8bd6a613d78d18","impliedFormat":99},{"version":"827c1178e5058f0aaa9047725b845d598f0f52871792441412059173f895597b","impliedFormat":99},{"version":"9e8ed20b5058a6f5f773f420c0efce5c8eb802c0af94cdb96b782cf2acf1b00b","impliedFormat":99},{"version":"8b0336c60458945b1fee185149fa4b5a512917aa171d4232b1f0805c3c12e31b","impliedFormat":99},{"version":"b15377bca02bd4d77f5d089fa0c7a13dc251b104de3b43f62dde6955cc8ef7e8","impliedFormat":99},{"version":"020409dbb29a4396e3c1c0732a0f8afa939e47c935182f6fd0603e21d5a6a8f2","impliedFormat":99},{"version":"bb259ffb75be8a11b1be05d135a561391f7123110d75074eeb5207be382ceb70","impliedFormat":99},{"version":"1f52c9be8dfb11cc31d9e2aa4f950ef56aa8eaef1b78949431882f70e10487de","impliedFormat":99},{"version":"1e685ffce849148fe9e9649189957078d9495608e9df42cfeab20367d2d70c75","impliedFormat":99},{"version":"97e30735672fbe25393231a53ab5e3b63d34e74d0697c59ffc034f9119c23d31","impliedFormat":99},{"version":"84ffde0a761e4b6cbf3cf90c97c4c01608962e8b55082f3705d29465a194f449","impliedFormat":99},{"version":"d874bdb89c1172b0eb109873d39175a5f210f5d853439e7eb250102622edb0d1","impliedFormat":99},{"version":"2b7e61a49cb27bbfc53fd5b888705290beb2d1fe78a8b433bac1ce7544113904","impliedFormat":99},{"version":"8b28a7039c2ccb5108bb3a3b771ca430db73c4ec9e47031303b8e87732a859a0","impliedFormat":99},{"version":"156eb4c6ef17eb61507364b320e2812cfc5afd862cb1baa251b2ab412384a2a9","impliedFormat":99},{"version":"9efc47a0e98346bfd4b386050634b4e150e6c41dd6d9b2bc1288e80a0f345390","impliedFormat":99},{"version":"2bd0a3ea02475382ae8e87d78e3be763dba251ddf9629664ce73c706b400dc94","impliedFormat":99},{"version":"20008d2327e19c4fd051a2c0ee88ea696d704bc6d7ad39988fc509d81c27a485","impliedFormat":99},{"version":"6cee28d40bc224e61f12e867140ab6d677a03a1defc9ade08b1bd60ab1c06524","impliedFormat":99},{"version":"3a0bb28315b2084f25a012275ef45e180ea80d9ca4bbc37665b9c67e912e998c","impliedFormat":99},{"version":"17662ae9763596c2ddaa833f9e326b3de9289098a71457ee18d2db9407cc681b","impliedFormat":99},{"version":"9442dcf95088615dd8ea58077ebed1f7d5dd662caca210b245a6b19f38984038","impliedFormat":99},{"version":"baf0ad4aa9df446c5b08370689dc08e23e112fdd1a022293676254fbb7897a47","impliedFormat":99},{"version":"e3a929f769e33c3001244a06d6a3e025083be64599c1e961aee31145d623e824","impliedFormat":99},{"version":"083493311f28114ab250a8f379798214e91f264dce121fa2140ae58376fc48c6","impliedFormat":99},{"version":"fd03b3ac929f2bcec6710176bbcdb34969d7f9810b01f65d19cbdac143a2c7d9","impliedFormat":99},{"version":"f3e2f84bdacbe962c856add41824ccfd66fba7b320753f6e9c6871cd6fd5133c","impliedFormat":99},{"version":"d70ae743099d2615ffab06760a3571a2beb01fcb27366cce4025544603a6081a","impliedFormat":99},{"version":"530fcba9474606ca2eca0b85f91b26d5e24c31431c27d20403928d51f9c1931f","impliedFormat":99},{"version":"c483babd94cb2effd09a918f5cacae5fbc8cdcb8b65b1a28cf07c2a9381f2a0d","impliedFormat":99},{"version":"c808470b50113d547da502f2380c6674fd41908d641663e5944a6070113469cd","impliedFormat":99},{"version":"f0568ac6f1c90cb01c4a2b3d14c0c6e734cfbfa34eebc57d789db55e7d0d34f1","impliedFormat":99},{"version":"0ae4ff7dd81505058a06f617152c94802f16fc7a8d2f768c8794f53f8be57178","impliedFormat":99},{"version":"3f61a28c42e990b337e084e92d7fa7df04f8a6b6699da3754dc59611d189b40e","impliedFormat":99},{"version":"73fbbf32113d791d019c474cf474344bb36d4c375f9622728163ad5640492a39","impliedFormat":99},{"version":"91b6fbc14c8a81bc1751cc033f55e0cb6f3b346653d51e30efb7995ecf969ed2","impliedFormat":99},{"version":"77e2fd9131fc81ffaffdc85a8ab553f869f2a67b236ceb95b85b9a1bd72b8823","impliedFormat":99},{"version":"330213ff23c7adbbb6f1b5ead11fb8dfb731c5c24f8c4a18586acaaa47e74077","impliedFormat":99},{"version":"a9f07992ccd51ff2a089628480d51364e19be7e5b22e04edd7e18a519c50e2fc","impliedFormat":99},{"version":"26f62f6b63fff6ad7abd3fc5d89d36f8c74f6ddb32d64795556d0ad3ac6b2d29","impliedFormat":99},{"version":"30c55932c3859c15cfb16c4cf3cda9c303588f3216f8b1ca205e2c41bf801402","impliedFormat":99},{"version":"b727fb19b28fdd8abf41b989f9ec0a6aae52cf07f3918386ad068b33d20c3468","impliedFormat":99},{"version":"d7fad08d42a437ea163bec1c3d08e5e4714a27636d89809602f04328a54a3fa4","impliedFormat":99},{"version":"22469dbd699381a169d6e02d5c080ba9d94b9d6567b7a5c41cb17f505e6a4ad7","impliedFormat":99},{"version":"44412e7238512522c472296f100c52c0accc20d3ee75db7aa503ad4d92b80754","impliedFormat":99},{"version":"27c4c4f9114b51cd89d2ba83e9fa60bacc6c29a1279f2f3b91d19c2f7b2c68ac","impliedFormat":99},{"version":"6acb809bb284648297faaefcb03e0e4500de5f78194a08b75512e13e5887829b","impliedFormat":99},{"version":"c31062874243eeb47ba70f53686f860d4c238bed5587af12ea4f73389ce2333c","impliedFormat":99},{"version":"b4f0992a1069bd5af311d02a49dae7aceb5e0400856449bd766b994267e2adba","impliedFormat":99},{"version":"adf2b0d2362e1b4c99336c56293ac3da8aa0d3ebbda67f963d4a0f3d3ef2a021","impliedFormat":99},{"version":"5fc3d9350eb34ad3cbcb1b69249161a33ffe19d7c0e72e6087c947046de6f756","impliedFormat":99},{"version":"b0418e08aab8aa9e4e406428964d2adf8187dd29f6cdaea32ede28fc36e86f56","impliedFormat":99},{"version":"be4147ddded6518b57942a23f89b50b772d841a97e22c93b70eddf901c7581d2","impliedFormat":99},{"version":"6b952ce628d71b1e1644cf8aea26a4de997596197158dc7b6e71ec356a8cf992","impliedFormat":99},{"version":"2f4dad0e02e51c0d630d46dd18b3a99a1d8c9f184af3e9d109027d8d11735f9f","impliedFormat":99},{"version":"41c5600e8662d67b2a149c2eebb422c80cc2337945f5b79dde92d41427499496","impliedFormat":99},{"version":"9c1e78acaead99ab9c612e54f5e16c0675cb6863627ec2dffa0c3d5651d53659","impliedFormat":99},{"version":"6df75e65602bbd54c977312ed62988e0c64423b046ed74ca126b529970233a2e","impliedFormat":99},{"version":"3ffc0815b3b1da65f6fc42a2a10aece2bda56d024cbcf7477b6380d4249ff8a1","impliedFormat":99},{"version":"7cb50c74ced03d93407f80f61840b52540cdc0ff7189ca603e6995306c2b25c2","impliedFormat":99},{"version":"a44dd85a5c1ba838eea01fd555504229ee74d97b1d237741598e7d97c0e857ca","impliedFormat":99},{"version":"aa7f2b3a9f4bb8a225b3a5e5c611b1a034ad76c3d870a1062e241485e3968e23","impliedFormat":99},{"version":"eb41d07bb7e2d527ac33c71146a3a4802a24d39defb6b8e4d707e5510074d076","impliedFormat":99},{"version":"405bf967f547561f6810f2903df5c5b3c7528d55917fcea0cf251951bedd879b","impliedFormat":99},{"version":"0060d5fcac50ed959be8765d1f5343eda5641109a62eba69696577e004b891d0","impliedFormat":99},{"version":"def4730fa85f358f1257bf2116242bec72080b1a0c70046d0c05ff7f90164707","impliedFormat":99},{"version":"5336f4657e6ffcc8bae26bd762b09b80ae6e3b67dce0a4b4aa99f5baab00c65a","impliedFormat":99},{"version":"8e728eefe8c7160465492dafb86f25085ede8c6b05e360dd2c7129955a155da8","impliedFormat":99},{"version":"c665cdd809976f388c82e21c47a040e5e19ba6cb953d0e0c1c38e1ce61f40922","impliedFormat":99},{"version":"c3bdb6cc2b1abe32815c4894c4d011d4ea80c79d0934d264b467cc6ec0051bcc","impliedFormat":99},{"version":"d40c02d227da200dd6be4e7d56ec2c560c08b9e24a4688a071f392b37953143a","impliedFormat":99},{"version":"01b9b0a56a739482aadb7da55886fa724bc2b557e9814ef4841d2262efb9846b","impliedFormat":99},{"version":"891117d566ab7e1a7798d83c58a20957c1703e92d5a351802081c643cf58faf0","impliedFormat":99},{"version":"6f925dbb5e83ba81d632287af1706945f435bfaec89258540eaae87817804c84","impliedFormat":99},{"version":"0fadf459265643344979f57c02e7ae5fdb5c70244fc9ccece5a1a977fe0b1fb8","impliedFormat":99},{"version":"0e77a1ed700a09eae143529750cc2eef65b8e28d76cf8a6eaf78b7f1afa24c63","impliedFormat":99},{"version":"5408800bf96b2cdd0d8d77e3d52f6848514efbf1590d96d9f8aa86c8ee95bbdf","impliedFormat":99},{"version":"b6e0ad0ba28715ae23a61b1192cdb24c06a909aa58b2048e64e56574aa4da7a8","impliedFormat":99},{"version":"c20b3e5d792dae26f5bbf8d1b73ddd16d9ddc336e32a301b9dd99c68a779f61e","impliedFormat":99},{"version":"d71713801d5419399f8edaaf0471dea5e578dd8b71eefde7abf387fb372feb1b","impliedFormat":99},{"version":"89b56bb82308d69d9ea109de95fff39ef64bcabd250688da972fcea05f50dad3","impliedFormat":99},{"version":"214f90578d41d0f5bf61b4d3de16b4671dc75fe893b803a483a4e7b96e80a1e7","impliedFormat":99},{"version":"32b6a2a6fc20f85513ffb0f35e455dfbaf058f65f063a10dda07ffe9592cd98f","impliedFormat":99},{"version":"8b2bdf89d903b856b52e4d416930701068a3522e9e8c2705602c6e7e2394e86d","impliedFormat":99},{"version":"c988c702e73a0ae03ff6d7868ebc2dd0497e921c3b7ea4fbde42aa781831b8a5","impliedFormat":99},{"version":"8409e2185704c03d12e1522dc4c7b137b6b7524e2fc1f9baee7581ee28fc3d86","impliedFormat":99},{"version":"95195cfacab74280a41490ca2c731fe499a37d7ffcaeac7dd2d9056cdc694623","impliedFormat":99},{"version":"9c8746b57866938dccd94775ccc3abe27e41d182b6f6d32ce82a1044084d3778","impliedFormat":99},{"version":"be71dce0024b565b17433b79dfb73c200bd087568e24e796d712cbd42eebf8cd","impliedFormat":99},{"version":"4d9081308548bde06c710ad7bc3af5e6d7e24538378a4c10eff2e769dec31bd5","impliedFormat":99},{"version":"09e693240afe609150a21882e64d8f34b664eea485d16ae78ac86cb3a47de3f9","impliedFormat":99},{"version":"89502a94ed72858e0018b65766f8deea38577994b7df9d406afc47224fc259c9","impliedFormat":99},{"version":"2c19973a0dad8e650d42349838ecc7bec9e181c28f7aacfc045eb3c0b8c7db19","impliedFormat":99},{"version":"851bba631a33a4413ce53ca3586b8a2d5799d0450207e8f7f9b594340e8d0af6","impliedFormat":99},{"version":"9772a1a4b6a4a8c16e2564c0d83848bc92c5410378710f9da8fb2d912ac32b57","impliedFormat":99},{"version":"ae66b8a49700f9b0e1e857eb7989a033392b92b5a19690c9ed7f8f403a1e219c","impliedFormat":99},{"version":"a095cd74b349b5c587c52343a00871d3a522d5d00614275a608e5c3ff690468f","impliedFormat":99},{"version":"ce2b17e7bb13676b9cfe8b9d71db509625851486b845475bf336e2c6f58a2cf7","impliedFormat":99},{"version":"68ff0025b0ff8a90165ae54d417191c8dddf93c794fd54fbfef6d4ea75f6ca82","impliedFormat":99},{"version":"6ff5a35137457c0c733501de9300f1801ae9abb33aea7bc9c6bf5e9d6d98cfc5","impliedFormat":99},{"version":"71128c986c2bd2554d203c724e897471277d96efae9d67721835e0174bb19a97","impliedFormat":99},{"version":"5118e5ed493b74299ca53eca1e5a422fb8f3207337285fe9206dd5d1f88785ad","impliedFormat":99},{"version":"aacbc0d9b6f47db9784a2193fcc7f4bfb1fc6cc711587a4bbac43e45432332ea","impliedFormat":99},{"version":"5be892a93003f44bc4420408ec0726322928020fe22f9a68264a176dc4eb8b96","impliedFormat":99},{"version":"9cf47cb5d151b9a09d0d2fed8b5858d726cbde497560ccb136557aa203364208","impliedFormat":99},{"version":"981feaf9d706617834eb318674966a8741ea35c93ce33e0ce155498e665d2593","impliedFormat":99},{"version":"8e661b24aed6caeef42e16eba111174c13ed178a660b41fd8f82401dfe129515","impliedFormat":99},{"version":"74606837f50a3a16d02993364c004db527b47cdb828edbd770595d5e4ee8dbde","impliedFormat":99},{"version":"d7e7588481cd78747b1d6a9439feede87c2e497df8448acc74d9803867cfdcc9","impliedFormat":99},{"version":"a46d60895edd2436d8927e02798c82975267d0b6fe3af28d7596177f23da3639","impliedFormat":99},{"version":"f93b561633fc4bf5005f34f0c2f96f48c3e548d1593136cfaa9331d7294ca417","impliedFormat":99},{"version":"d64b9ad5dc93f6dc86e1c13f5e483583597b35fa0ad3170c928e436253b1a252","impliedFormat":99},{"version":"23bbc076a14d01df086f77870c735b053cb1c9dc07c2c8b160f6a04db80c469e","impliedFormat":99},{"version":"7f1f69fdcac775d124fe626182219327b833a962de2c9751073d2643695ce2e0","impliedFormat":99},{"version":"ad774bd48cdebf2909e354cb24ed9ade7763306edda185b7692890a2aa96be4b","impliedFormat":99},{"version":"f53c345523d49bc3e6a11a5f6540ba145b441af3618efaf58d25b58db03d2922","impliedFormat":99},{"version":"164af37e5cde8d2d830b5a5f2aaa6be547b8004e4e98b33fd6977581f8be4d4a","impliedFormat":99},{"version":"2aa08243d9c596b3e993b558033dd391f39ba4d6525ccba992b11bb5be54c04e","impliedFormat":99},{"version":"208371c97acf811ef41ba4b217816aedb802a570129042463b198c7d72d1cca1","impliedFormat":99},{"version":"6311ecffa1680ff0f9587217df76d9556d4c8c623f12464b8beb44461d1d22af","impliedFormat":99},{"version":"16e2700613d061c8a3c21fd26bdff099948396954d5935ce913424d93c97815c","impliedFormat":99},{"version":"0890d6e6870d35b625590a98abc2bd3fa880fa46d0dc3de22dbf01628cfd34b7","impliedFormat":99},{"version":"193814fef68f60058efb9c02cffd20bcbf70eec1d32ea0fce4b5887aef746157","impliedFormat":99},{"version":"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d","impliedFormat":99},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d","impliedFormat":1},{"version":"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88","impliedFormat":1},{"version":"a9ff5614fec6e47cd306851cd39e2bb0bd1b939a9776cad032bc06753a24b105","signature":"2641cc270e66b5b412cf0f887ef90e12173ac7773390a8e0008f653358f66841"},{"version":"709504c4a347b021a9984ee3e65359992e9f0f172d22e63030207d0c604296d6","signature":"b0a30a6f3075e34a6a108ff4fb8c54e7714f964c0690db0b6e82bed93ef6568e"},{"version":"c91f4f63d6c2c84d2e0f7368a97dfb126ac74804e775e5aa5bcefd853917e69e","signature":"96d032d99c255b941936f513419610586f7e642f2abb57d1b8d2581f7d442eb8"},{"version":"a313760e9f66c6f819c3426e038acb9aa8f47a59be74062f51321caa88a688ea","signature":"439593d167651f2e1c0c439482dc3d5d5eb248ea221ecd8feb5c62cd0d60cd86"},{"version":"2c82ac3566fa4072c5cc6320a0a786afb9d27c061d41316411483f61353560eb","signature":"e0d9f1fd5544f50032be81792d9409f65c8ea46853ed0450b9934372d4255930"},{"version":"829b5cb87df9dfb327efb8a4e55644d809f3e03de209067122b99ffebf284f00","impliedFormat":1},{"version":"415510d38ea33f28cb571ef11ebd6ee777a377e0d1886b6771dcb15fdde7a02f","signature":"5a4e0d921d1c64c046a46838efd87367a659f8debed6c7f7801b8440576657de"},{"version":"6e9445b11a3d075d64853d8b32efd159b4a45f37b481bbbb7d3bd57f5a5d5f35","signature":"589cdbba6bdaf20ddef1fe78e3bdedfd4e7f6b6e08179a9d8197ded860ebaed0"},{"version":"80d23e36921e787529d3ccae753675b91180dc2326b4c1a3d8f270205b85af79","signature":"845a9728a8fd9284d40c63aaea7b11076866271659517e0ab1a1cbd041bf8588"},{"version":"1aadf3c39d08e4aeea1b9950040079b0fa8baa1d5f9644667cbbb6b9c8c0837a","signature":"730f18e9a86d7032845d6a326f8c5ec9469490304565e2a637f4dbdd8db08977"},{"version":"03a87f22d5567ad70a9761d76f0d16ca6ae32b6201d79c4946e751f2c4cb4e8a","signature":"daaa96af8feb9c538eac60042eb231ecb684bd361d5d7d5fccb0a614a41c365b"},{"version":"07e1a6c1c01468d20ce760a06f39c9e59c56103e63556c7240bbc9e8c2b8d24b","signature":"e6ec95dc819ab75e36c9e4492ba3e6bcf21507403a6afb5bbe8cdea76fd77fc7"},{"version":"e7205096e87497cb983cffe2ea271035dc0f7bae9db702859e5a2d0941d99597","signature":"1a85b0cd6837d60863844ad43f065863cd13b3cb956c369d493761bb603f4b63"},{"version":"18ca2e6d4cb671ed4530429e6b4886aa0792c79c4e5e74078dd290668b540599","signature":"c606b46f5784bed24dcbe2bec2d9ee535c29050ba160ea11fd41b6a173bbf25c"},{"version":"4ed96213860296593b569b425eec8dfac37cb5bdaffbce2206c000dc673007c7","signature":"0fbe920fa2bb3439dfa680647a4ea264b7a8ea9bfa75e4cdd9ff2507d69df783"},"4720053f6a578540743ee8f3c02278636ada05dfc7184b43433262c4aebbbff5",{"version":"21e365e7414b00e1dda3cb0e8c1ffe7eaf8f4cee8665857e7a4ab0051c694811","signature":"d36c6cc5adf1dd3c897e4bfe96cfc0506c9352c7413cd83da0d3032f820781b8"},{"version":"800de8bb8ea525980e16dd155bb6e6847e7fdeccaf816e5c2674e1a24c5bfc9a","impliedFormat":1},{"version":"88efe27bebddb62da9655a9f093e0c27719647e96747f16650489dc9671075d6","impliedFormat":1},{"version":"e348f128032c4807ad9359a1fff29fcbc5f551c81be807bfa86db5a45649b7ba","impliedFormat":1},{"version":"8ee6b07974528da39b7835556e12dd3198c0a13e4a9de321217cd2044f3de22e","impliedFormat":1},{"version":"deefd8c43b40f9797c3921d78d3f9243959621a17b817be7f5d95c149f23a9dd","impliedFormat":1},{"version":"5f12132800d430adbe59b49c2c0354d85a71ada7d756e34250a655baa8ad4ae5","impliedFormat":1},{"version":"ec27c0cee1436f58e785f621703d19d588ebbd489eca245e5198b4d6b715790d","impliedFormat":1},{"version":"b16e757e4c35434065120a2b3bf13a518fc9e621dc9c2ed668f91635a9dc4e75","impliedFormat":1},{"version":"efe2821496a760b9128309bb69ad43f1a99feb49d3fd004673c5e406de523da6","impliedFormat":1},{"version":"ea0e3c7d1347a549ac7ec32d3c61a30e473dbbbc901d458064db03f673128145","impliedFormat":1},{"version":"4374cefdde5c6e9bad52b0436e887b8325b8f407c12035194ad02c28f1553a3a","impliedFormat":1},{"version":"5f1ba0898eb0a54a644cb9c95c2240beaa961d87fd080cbb90807a6cc03daeb3","impliedFormat":1},{"version":"8e92ee8710ba85b158c5d91b0bbc9d0d033f5e062b6e70178063f01b20f63a14","impliedFormat":1},{"version":"ee933420aacba1f60aa70fb8ba47c5e69001b005073b71973114587089a13c7f","impliedFormat":1},{"version":"0a0714999d0a5bdfacd15c7b34cffbcc6f263f6cb0ccb42076cdc541c6987797","impliedFormat":1},{"version":"56584bfc655f9df64afc0f22f7d1122c29e5b74b342c203b891e19de9fa37de8","impliedFormat":1},{"version":"40ec58f0fadd0b3981b3d383e1c12fa0680115ae9f018387fc2cfc0bbcf23204","impliedFormat":1},{"version":"59709e26e08d4fd4c6a133552ad8f94c5b31463f295c4bf75fae1907738b8441","impliedFormat":1},{"version":"849b9e7283b7309a4556c9b90bb8e2dfc27751f157798065bbc513dcddb09a8c","impliedFormat":1},{"version":"76bba0c97594248c1be19af32d5799f7eff51cec2926d8e4dd59267d7636a0b4","impliedFormat":1},{"version":"10e109212c7be8a9f66e988e5d6c2a8900c9d14bf6beadf5fa70d32ada3425cf","impliedFormat":1},{"version":"f4558bcdc26690cc593cd59217cd17d8e00af0f5fbd0c4f1c0d71ba75029c42e","impliedFormat":1},{"version":"51d621c4e724720dd1b7ba6374d8a5b988beeda22d620ac84634a13691b631d9","impliedFormat":1},{"version":"f57a588d8f6b3ce5c8b494f2dc759a8885eaee18e80a4952df47de45403fedbe","impliedFormat":1},{"version":"34735727b3fe7a0ed0651a0f88d06449163d1989a2b2de7f047473adc7c1c383","impliedFormat":1},{"version":"a5b13abc88ab3186e713c445e59e2f6eee20c6167943517bc2f56985d89b8c55","impliedFormat":1},{"version":"8b29e3ed0c90b2ebc40b2bce5a518a0e86c0c417f7fe99a5e7658a61166bd9cd","impliedFormat":1},{"version":"7ae65fe95b18205e241e6695cb2c61c0828d660aca7d08f68781b439a800e6b8","impliedFormat":1},{"version":"c2c8c166199d3a7bd093152437d1f6399d05e458a9ca9364456feecba920cda4","impliedFormat":1},{"version":"369b7270eeeb37982203b2cb18c7302947b89bf5818c1d3d2e95a0418f02b74e","impliedFormat":1},{"version":"94f95d223e2783b0aef4d15d7f6990a6a550fe17d099c501395f690337f7105e","impliedFormat":1},{"version":"945be5a9505194381cfd4a8551a5f0ae48090847e454fecf834e054207c5a57b","impliedFormat":1},{"version":"d1e8b78a5ce49cee9ef4cd2565d4645d269c6fd0650e3592f85ba481f13da3a3","impliedFormat":1},{"version":"61be8f1d5345cf5750aed87af2869888ca1b675ffa481f1d4d80554e10084b4a","impliedFormat":1},{"version":"eeb6c806376b9c3464f29b6058aecf113328f9ce290af0375e520f1a844529cf","signature":"61f11ef9f7b473f14c872a139f0a329251738f177edfdcaefb3745adf8967036"},{"version":"216c830de5b7e1ff7336a1bf11dfe9c98ae2de2da56f616e7e4b4405aa14050d","signature":"f9653d5c0a8d7199894c3721eae87d898c8ce6668c3c28461dde2236367c94e6"},"ebfca49b6f505f572648960feb0bc5e131c9a6bea97f3f5883dfa9374ed4028d",{"version":"1ceb93a23603a978c37604ac8c0f3a5adb8a7bbd76a5769b950db644b972f0aa","signature":"0895d90edbc5d40218c073393554c18fa39a891461bfc44da8be225be27a6a37"},{"version":"b16d890b0ea02f67586f064f87af862c601884fd40ec000b3ec8dacdf1c4c7cf","signature":"c4bf08d84391225b229f7d67fe8f7b3ff511782f27e0d6f5f4680aab2cf451af"},{"version":"ed0300836934be9970c950c0d485e8276fac87cf1fec92f07e5f13fb7203604d","signature":"e3d48af43b4af0455edee6944467120f4272a8306e90d504935da490b053cafd"},{"version":"172445546b246f00923ce61b907837020174c84335bfa24cddc78b6a5d28d0a3","signature":"b34528c74b3ff693ae3d27488992d045d0da79151d70e3240ea701f4a8910b5e"},{"version":"a207d5278346c5ef6ea5ce0b34dcb377bf4cccbd7153ab83953cee72c59ab34a","signature":"1dd308df0c17f9580459e35f573f15a40609c032465913c8d86a10883edcda1a"},"f299ec29ad652a02319d39bcb58adf0803a2bb2387a025aec1a0a16f50519176",{"version":"b53aedd97ce9ebdcaf94f60bb120b172149f79045edf37b22858ff10bb61e09e","signature":"ec13261703b24c5ffb56fe30e3d7b64fb29d7ea5fbf548dbb3440646b65e1316"},{"version":"1251b05bdf0f9d8118139c3ed7bb0ea60466ee664c2d58e710be9b39032c6f6c","signature":"f2c6a00624f44434d49aef27eac8b74b150c4ad7ea531992cd5ec7b61cff698a"},"a7e4a0f02427c3e07643a6d5bb9bf0cc09f2ebe28b37a42189f923133c43c186","fe3c71661dd6c6d74c4bb196af4247d019f9308057fa1347f35877c4511c460d",{"version":"5bcca0c2e2f15929cfa8e0d91bad9f61ab85e7f256377ccc56d6b0f9f8552960","signature":"d77db17aa371d965761001b744dba64792f22b53c0a9ddd4d80d8c8b359c482b"},{"version":"641984c05f82a6e0b8dac973196b8ba146f1644b3706d318427096d844ac4f0d","signature":"eb5c97b219f68b8629c278d916c59c82b514b848ff10eb0db5d4196d69654147"},"064945c8a414c7a78b237a277403afd2b7ba4bb433d8cdc41fde3cddf09880f4",{"version":"5323f2f109370900f8d4f85c82ff47df76a7d63dbef322abf601217e4e677086","signature":"f59baba97905164ae2797a2a2869308ff3435aa1c66fd33034c0237abeababe1"},"1beb3dd4e06334a36673fbdf6df977bb28d28134285a21da8584cef98b0e7c46",{"version":"5a2cdf6adeec348bbc876221be4367e8adff0bb78a5680ebd7d71e5c3bad6cc0","impliedFormat":99},{"version":"e004826eac62081f867c66dabd92d3ef7d126d93a70430a2c88429228c3ecc50","impliedFormat":99},{"version":"38d6857b58d2ac42442e396311c542062d4f0dad40f2adb496dd5fd0756ee400","impliedFormat":99},{"version":"34b7d1e2d15845cf08bcf5e3c01adbb92cea1ec27564ee249ba486cdfb28526c","impliedFormat":99},{"version":"f13cc653015347688208b6a2817d6a024cae82cea1b2be422061ae1fb40e86a3","signature":"9d34eaf37fb26f7e3b5d52527e1cb097956ecec3deece2590b99f360ab4428a7"},"cc319ff8f06a331d4b359aa39f43dc18f8d4402f1f3c446b22a197389c4067a6","3c27fb3f66fa5c3798c663843ad30a16957d6cf39d4c4ee8c154dc03b777bd80",{"version":"0c50f7da7e287df66e69485e4e5b56c4a0fb9f8730571541873377e7ed45a2c8","signature":"987de9b3dd9352f138928040bd0776e179cdf67c235a18bd54580bc4163a2999"},{"version":"ae5f21d33e9cece1850a7223c30edff9bd2b842b05492b4d1f5c74891683186a","signature":"1bc767c3ecaad8c2a205ad502ee7c4f20cffc11447fadbd4ab3f573481073582"},"f45fce4f354b6059351eaea503203fe2661457390b5c443d299c90a690122d9e","721652119aa07fa7df69fec15bb05e7818c69f4798e424b2a444889f482d5118",{"version":"31f74c987ac1c8dd1bd2a84a270623b054c1fc4ce81a30eb788ce3d579d95e40","signature":"58a5ec371db12fd72d7b69a8c237fc87c5a131763b45d262a3d191c5d1356d6d"},{"version":"7efed9d38ce35662483150baaecb0eb98e400391ada29a436626063a3cd09be5","signature":"56e3f4727284e65c0f755411270bbf10da22e3fe5529baed216b93557b41276a"},"b3247c06acbd296275f69ae7aaa4572cfc9228e70de48b19ceb4584247fe05c8",{"version":"ced3338f3895b632082c71280b90c9425e3545064b1b595a2245930e353501e5","signature":"2abf126b8a0429351ec7cb3bd61efd7f4966a31641a2bef1339b78de215479ef"},"2cb5bcfcddafa73663cc7a0b9d07913ff00864af96cdf56ce809d55f80a1753b",{"version":"8605ab3907c8332a03b0fb2bb8ecb8259321c15adf6ec70b4032b85d771cf2f3","signature":"06ae795b9ca99a2466c46639c2ab809198e6c67d400165f05424a012b1bb817f"},"0b596ac641129a560bec8f495f52adda3c82d92b1a115434a46e9c48080c9157","393217dd0d9559eaec6303131eacb34df82e55bb8138da849896a108dc85151a","6515c88b44047c95ee046a13f74332deb2e8568f97aa6854d5d4a785ad05b84e","2b25605d3b717aec5daafbf2032723fda8ab4359aed0cb6e1585028b60b3f708","921a3cebbf89a24feeca9c194e89aab4fe3d19308ce4c13dad9efe3182df4459",{"version":"9e51643ad596e80ce30204003fbee1fb98f8006a5e383575efbdb0907951a0ee","signature":"fe75269529e2b297082168d9d65d1cc4a5f4b9d2649fdf474ea6ef7928b19729"},{"version":"bb23c7b441db38d447145cda42a252dd88d0ac4113dc27e43a3a7db35524bda9","signature":"c4e6581c0c2bf8d017173140969f491108dcd5784f12ddf140da8b0daf20ac83"},{"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":"02b6d443cd64d2a7e8dba0f1d59944e55e91a16b21a7d7d4fb5a81724c832dc4","signature":"e66fec1c73ea068e8541b003c79af072b1b18910017d07c47ad151a438c709c1"},{"version":"a1cc7006ac0ac2dd2748f5b4a07092a330d175b16adbcd49c0eed365e9b4575f","signature":"6521410466cd5930d8f9db814cebdf1094def90f68293a3b330296a35ff2c1f6"},{"version":"d3984cd8c4d6cdf73a81ea0891dae87ae6a01c1895fd68df0b6d740006acb9d6","signature":"0ac76f72a94a13f3081c41c43b58679492c219ccada653f40801a89fcd5e9d04"},{"version":"64a1df79fbba93c3a1642f66057608a3d7e2fd24ba015b260f7014ddde542908","signature":"feb053fdd4dce7ad7c1ba7791bb6f65fb66d38bb9c1f0543012dab8f663e88b4"},{"version":"9b47bc8dd5a4d6b7f03a22a9ff4f46883813ae93554718511b93888e39ee58d2","signature":"32937206cdaee2551a23ce603292dd67e9606a27fc71a984eab852fbea3b9ad2"},{"version":"f748a5c971c789d58810273b596542811e7e49eea7a42b7fa3c42829dbf62a58","signature":"4dd7e1bfc2c138b564a1ff5bddcae96f4cefd39724115166bdbe071cd00b3cb6"},{"version":"c4528c70ebf1acf226f198422561ad4348ac9e35a8990b1fd15e17ad9268d60b","signature":"71f762a4ed63ccdd8a60c9930b445ab8e81bdf4b9919c5b94761511cd866f447"},{"version":"d6d3f9395cfd6f2ed3c9eaf572f882a03a1fecfc1e13acc4519df67833342bd5","signature":"5c597452991cbc579454bf8e1c5f549816d79f80ddd3514b52fbb26cc1cdeced"},{"version":"7c6ce84284a608e8ca9b7636cb5da89481d8c945d03ba511da6a2fd56bcdf78c","signature":"c169279b909f77b0c7b26ce990b20c6719869fd76be6f95f4eadf4f3befda363"},{"version":"83e1bfa7986a958fd6e069fc5df9dec6aa1e63f3dd81ddae889c19edf3a6c450","signature":"6efc188b6e1596f593cdcb356be53ede31fa87f972e5d2adc9377fa511e2685e"},{"version":"c463facc7d18f4c36823714a285903d1123cc38a9dc91a5d099c64145432f75c","signature":"2150afbdeb24336371088cf931c6081d224326f5c57580ee0b36925d1569ad5c"},{"version":"dc916450a7fe9f02ea4f2b015b836fb7d3e6291e59c93b47f711623ec4c62fe4","signature":"1609615e284b1a86bbaebd997d03c23cbe145012ba3b3d4376aa8a43a701e4e3"},{"version":"02a313eaacd1d0d97e7e1605737ac03e732648ba6d92fbf2c24716c1349c30bb","signature":"e42b8c3731c42dd2bdacdcbd0b7639df957c3f9b5fdc1edabac4a5e63772a4b2"},{"version":"af2d7b90a50168850a399d83b4e9afdc302a1025148194e2e94e1a31060b93c6","signature":"9836be02a489f0fb61392d0e3fe4127c72f079fcde9e9fed4c282bb070832fb6"},"9e06917a1e0918bc34f5e3cfc014c05c7cfdad0c98997d7047b4e7542aee1861","086d9066a9edc176d4baeb61d0075de9353ee4695c94ecfae51f293be8fefab9",{"version":"94fe52c96742b25429d30bc54d7ab2a2324f37025cbea99f819a77ec87bb1772","signature":"d570651c0a2c5e78e74c52a792b94ccc2cc9b2b927bfb3a5419acc0150942695"},{"version":"1897adbce3874a07180bb47daf0e8ebedd6d1793819143c63cbce290ca2ec80e","signature":"aaf435d6dc58d0a18a54421b3a622efedf9a7a996d8f75a06354219d91707650"},{"version":"68fe3c692ad2824bc811643cd5e239d872cba48006000dfe185146ad106066b3","signature":"0342e61cdf2eadde061c53e1c6fc7907ad69390beddb2aa50e656dc2a45a632b"},{"version":"b15c4ee8a756cf303d0efc482e861876a2e90b194c7cc393a8acbe7080fb186f","signature":"4a1ee69e5477f0d725306d7c9281d127f43ec0b40a23689a1a27b9430f030177"},{"version":"f6a08a8d8fa7acf45c3ba85e864da549befc88abeca247da5b6732a82685bf45","signature":"afc9b47eb28f4775396aacf528a98d207e4714ed7600c47bd33d01d4d6d3852b"},{"version":"bb0365d741d36b7f82832dbcf1b2e0025b6638516fda9ba3061d7d41f7f073c2","signature":"1e5e485956159fcc1eee2c73dcba5186c0a66f780ad21ec3760cd35a723930ca"},{"version":"431206d65e5858f0534c8be80eb4081627924a9d1cdd853982a3a4d811999681","signature":"e4b7681fdfe65ce81bcf251c1bcdd71b93740fde81479a2e3531a23fd347d951"},"7b5bae142db908800ba59fb353d7274e1f1ef0eb46074f1675af3ee77c4d789d",{"version":"058aa6a9383796a202fdc9eb0c5eac4cce8a19ba60bd8b551091beee197fe25d","signature":"b067ed3257c3d808d867d834eb5c1688ce5984ce6377ce6213f7fbea90bd6b58"},"c837dc2de1fef03dadb1fbe3ae46565ab80cbeea60c057acd5bf1e1d8df1b509",{"version":"e6c4a6a15416b28fac47309cf33fe8040115a2849ccd012bd098efa4ec4ce9ef","signature":"f3afc6db2c2172dfa631f271d2bc28e8dfddcb2807285ebd2ff547fb786e49bb"},"283ec3ae2b171cd28e6778d2bdba3f8b055e818dac238b8a2d27403c551974d2",{"version":"a76cce81c55f02fa760f7b994c9aca6f3878e566f2bc8dfc8ecb79950d04f354","signature":"26b17216456cfb72ed066ba09342f94a61d6ea42ad25b2f2f00285c728be628a"},"e0340f2e710b3caf03d7435335ed6441df684f5f416b3008077280a53bc0d195","92c285578eeb816b54f7042a5447e57b676d60becce977c9d4105b6565b1977b","04780775bbde0064d8134ab5c1f40f2a0cc6e8fb4d3bc8e8e2ac961c05bda871","c32feab5e5456978529c9eb1c2d8b56a04d9074f2f43e757edf680e132d37d00","be7bd88676ebb10c83d7fe1378c26122200f68085ea06524a4f0f8c66831b348","e111d7709868c64a5ec40c93a0831eff084f5f3747bb50300878504738e28c19",{"version":"be072d8f770e47c11f6ae1b77999dd40b0c32d7f710b8c2685a7725daeea9d19","signature":"e367993516c9f05fa87238bc5b53220f06b7f84b72629958930e2a7a37436c24"},{"version":"4d6792c606bdd2a9b2cddc4d24923ccc18f7f438cafa31e0e21285e97c58421f","signature":"2d8f81759b547e64f1b0e290fd4b0ac7316dc9c3e96f5ca93db1a1c790ec6038"},{"version":"b8b666a3d41df3b7cf4066283f67e72bc5e8e04ff4414695eb972ba7561ce133","signature":"171b8eafff7d0d126a6df4cb220dfdf7ae67c7c6687fbdc02bf4b791bca40091"},{"version":"d4e9258acb020b007d2a76f0d9870d5d10f0a2d6bc8d24bcaa0f65931892b736","signature":"345eb0a009f9b07377ff2e8bcbd390da1648e549914b3bd027bd6b4987f92481"},{"version":"7813cd7f20cdeb108fa5e7ca8c809d95738aef3d8aa514cf516fc6e5651e6357","signature":"b9210c8fdabb61d8f6f3aff9f643e99b8fd4ace26e523d81264ec5e5d23583ac"},"93c88804801702c2ebf4d7e282ff71d90f118253ee206e7f0ba03305cc581546",{"version":"447809300abe6967b66fe4226b18bcd8513258b0139a8fd1ac516767bc510372","signature":"6d31cb09b5e87e0588c937c73aff7673a15865e355903fe16ac3bdcb3d2894d6"},{"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":"309ebd217636d68cf8784cbc3272c16fb94fb8e969e18b6fe88c35200340aef1","impliedFormat":1},{"version":"91cf9887208be8641244827c18e620166edf7e1c53114930b54eaeaab588a5be","impliedFormat":1},{"version":"ef9b6279acc69002a779d0172916ef22e8be5de2d2469ff2f4bb019a21e89de2","impliedFormat":1},{"version":"71623b889c23a332292c85f9bf41469c3f2efa47f81f12c73e14edbcffa270d3","affectsGlobalScope":true,"impliedFormat":1},{"version":"88863d76039cc550f8b7688a213dd051ae80d94a883eb99389d6bc4ce21c8688","impliedFormat":1},{"version":"e9ce511dae7201b833936d13618dff01815a9db2e6c2cc28646e21520c452d6c","impliedFormat":1},{"version":"243649afb10d950e7e83ee4d53bd2fbd615bb579a74cf6c1ce10e64402cdf9bb","impliedFormat":1},{"version":"35575179030368798cbcd50da928a275234445c9a0df32d4a2c694b2b3d20439","impliedFormat":1},{"version":"c939cb12cb000b4ec9c3eca3fe7dee1fe373ccb801237631d9252bad10206d61","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"03268b4d02371bdf514f513797ed3c9eb0840b0724ff6778bda0ef74c35273be","impliedFormat":1},{"version":"3511847babb822e10715a18348d1cbb0dae73c4e4c0a1bcf7cbc12771b310d45","impliedFormat":1},{"version":"80e653fbbec818eecfe95d182dc65a1d107b343d970159a71922ac4491caa0af","impliedFormat":1},{"version":"53f00dc83ccceb8fad22eb3aade64e4bcdb082115f230c8ba3d40f79c835c30e","impliedFormat":1},{"version":"35475931e8b55c4d33bfe3abc79f5673924a0bd4224c7c6108a4e08f3521643c","impliedFormat":1},{"version":"9078205849121a5d37a642949d687565498da922508eacb0e5a0c3de427f0ae5","impliedFormat":1},{"version":"e8f8f095f137e96dc64b56e59556c02f3c31db4b354801d6ae3b90dceae60240","impliedFormat":1},{"version":"451abef2a26cebb6f54236e68de3c33691e3b47b548fd4c8fa05fd84ab2238ff","impliedFormat":1},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"41f185713d78f7af0253a339927dc04b485f46210d6bc0691cf908e3e8ded2a1","impliedFormat":1},{"version":"23ee410c645f68bd99717527de1586e3eb826f166d654b74250ad92b27311fde","impliedFormat":1},{"version":"ffc3e1064146c1cafda1b0686ae9679ba1fb706b2f415e057be01614bf918dba","impliedFormat":1},{"version":"995869b1ddf66bbcfdb417f7446f610198dcce3280a0ae5c8b332ed985c01855","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"dca963a986285211cfa75b9bb57914538de29585d34217d03b538e6473ac4c44","impliedFormat":1},{"version":"d8bc0c5487582c6d887c32c92d8b4ffb23310146fcb1d82adf4b15c77f57c4ac","impliedFormat":1},{"version":"8cb31102790372bebfd78dd56d6752913b0f3e2cefbeb08375acd9f5ba737155","impliedFormat":1},{"version":"bb9b5a18147a0f927e0fffe91515a39610e2477b0d8a0d0b391c283013e0bfac","signature":"d373335450e0c74b3455541e03c0ff8fef26b51201c49ef145a0afb217a9f026"},"4bc5159b0bb1e303f1b662d485b7f9dcfaf785a29f8cd101ea85817fdb3a518e","25bb698c825c728521550bae3d4d8777520fea078d96529db79d3901278e084f",{"version":"d7382f620923bf13382b5aa1ed1d439617c8f6c916c1d7a645a6f5005dcddf8e","signature":"32159b615fba8ba0c76d071b40e35822660f8317b107f16b5d40ce3a8d6a5bbd"},"6fbedb59be020e7d349de8a1ffe8aaa52d16c78f9aea437249f14782b290aee9","1318f5ed0496352fb48c4c03ed01567db651e1794260df1b2da1e146a1aefab7","fcbc73a398e35777c583049d7a6315455a1c340d06a7ba06fd65a08a998576a3","ea963ab39dbed68f0cbfe8f7bebb09e3b9a98badb38164903aeda102ca62fe84",{"version":"70ac7fbe8555de02f7cb0fe42f479173ddb89a737908c560014d733348422046","signature":"d9b4f0fd652a60e8727bf295164c2d0a652cb6d79ac90e8b13c48d4230a47039"},{"version":"656ebe6a1e35fb1e45ace5b3d8975099fa82a7a42542c09ee1e1e975b4951722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c4f3d9c6228f744351b3f3d6ac2593edba9e7cf0a965cc6ccb1805595f44f275","signature":"96dacaf48c43f86fe63578c47b008b14f31ea3b26ba6604869be23386548a0af"},{"version":"ce80305706eb0c25efc5028968e9b4c6118a68c8987532ec9df246a8e7ecf993","signature":"b95e4b7b3523b6989a5d11cfd8722d821d0dae59e5016cc2fa69c6c3e7507a9d"},{"version":"256ac94c8da7010cbaacfb3e0f55cab2ce49beb7f21309659ab1e5c44b66cba3","signature":"4932a57ec8dc885c99967df2c08c4be4dcde303de1727465afc901bb526c9dce"},{"version":"054c188a756ddb383e1ccb176c09ab7f0894d89fdb9ed00f102af2a9f7ac0e3c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"45d8c8b72f837a46ca63ef01ea3f4244112587c0abac142367982f443e31ea7d","signature":"eb8463d6df0ca2c38399823f1e38ff66180aaefb12e5b403155c2abe1eda8b5a"},{"version":"964e363030719b2e66f7eb64663b22f039bc64985dd1e75eae362e378608ad32","signature":"52b37759b4c21b0266e113f72e72db24ca11859fca9beaae88ac286fa508c5eb"},{"version":"2aca0bc14bc6a0e2ce70f410e002eb4aec77e7622afd0e40200a2d6c36542db3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d21bdd834776d159085df8f067883d3437dacf6eab3d356f7c3bb2bce9a9c98d","e541c3824271bf8af94ce64854e33b2434f4f619a75bbf7c9051b746d2c5b2a2",{"version":"c7fe6433c779a7bce07b3c90d85dbd397326047eb839680cb426b97d15b1af91","signature":"8ffce94f2622151e417ce42edf509f0890eaf9268f878c698e05d0bbe3df3159"},"4edd723b64a3e617fd8ffc3bbc1fcf757ea9e1eb9132d9d77525807656426e4b","fcf8bb50230d3b1973034c5f3d43b32ae889757e96c8f1bc574e4e229cac3855",{"version":"2ade88925e99f1feff0c33e971814e734e05f6f9e32c2fb7c6260247635417ac","signature":"06ca53e7c778e43262f44194db43a44dae84e02e9d9ae674f74a4039f043a39a"},{"version":"a5110f54ba5e9c7c7fdc029cd20e65f35a9ddab6830394949279d03f4baaa112","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e08859a0433654484c23ea7d2447e7da43768e228643cde336290e80359be015","impliedFormat":99},{"version":"427d5d08714a73f909d965ace2642ea9819e49245620483d47acb73b4eb922cf","impliedFormat":99},{"version":"242e53307a5705e9235ca1be47168a4c3155ea674e80f5a13f94d7938c23bb5d","impliedFormat":99},"2e0b9c5b9659b03cf5a40b73ebfe3b0c8de950f06308a61502a2722e2f418c18",{"version":"8ffa57b994af8cee7411cd7bfec0409118909a2a897b417d5ba378025b9b8eb3","signature":"66d21fb03c05d9e19a9e6328311f0e929ca450163fe3ad5a1f19b3e0563710df"},"50fdd772b1313709b583dd32561b52331a43b53d7aae0d6f3630a85d4871ad13","179f0303099722db250eb13fcd19349ee2fb24f33bf524e43d88f94a8a82aa95","80efb9a44eed9b0287c7811fa3b4418dd9a75a3c8c9d55bcd30ffbe3d72d8211","72d3fd192ffa0901a97fa17655ae18a1a4af3479f66348b17bfcafc42678e06a","849d186951b6fe08777eb595e7b5423a933404a59b255b15b3ef91eaa9e03e2b",{"version":"398322573c9e0ba2826eaea162b97f4987dd00caaa4dc29df93d7dccaff40c2f","signature":"dc1df284b2ecb2adb8124f0411490cbb6adc27d6b3f783cb98e4de022894c67c"},{"version":"0e614492dab5ee5f4418895293386b642203c8f1a3a9d14a8eca94a906c91c04","signature":"1ce004dab6fc4c13fe2a946bf541afc29f77e4b6d197bd7e078c216bf331c288"},{"version":"f22a38020548019be436f7d33fae243aecf68c9e068dd7fb5d88ec31fcfce2c5","signature":"990a86a4c51ffd7c3c146bc5b5e4f2a6eb31f8a08186da00d699ec62be38d14c"},{"version":"07a57ea6e42f784f7664053d917baf68d010a79f0df1fdb8fba87a6af92ddd7b","signature":"e57424bb8ca9fcb02c7b73c295bff56d7889e92610490ef4dbcf83dcf5006809"},{"version":"ba3f0e6512b7afdac714ac775b22777273fe0dd98096e1d1a7fa2f9aae83cec2","signature":"7546aea101d084a3039eec017b4629ef261e36c012a024daeb0f8170d86d192f"},{"version":"18f204ccd73154b6afaeb5c1672609aeaac9592c85183e75cf590a4bd70575e1","signature":"4b23394a9dda4737bec117daa9748cb9868e5be402b010881029c58b639c48b6"},{"version":"ed9e84f54b39f81bdc4e0520812489f40ea453de7a51d380a15b14d7bf02e683","signature":"e6cceaf655d91958114f0707a4d6c800cfd0d72ea8673f4f0face6b049c90ec3"},{"version":"9f2145479716604449381a636127459790f9e428a5c526cd0795223bb66dd9b3","signature":"d86c8b7a6c6edbeb6a14c73aad61eec9d13cec0040264ef13c78a8c2f7ecbf43"},{"version":"fa32219cc14042734452368d122b82bae2849be88d8941c5b363e3c47c9b651a","signature":"7c56fc0ecedef369430a6cc78f797e1f72ac6aa30cf3861bea222fc9efe93d49"},{"version":"f52646c7394ab792adfca993338d590f7d9030ae3269526d5dede9b131247717","signature":"d1d2efd128275df07279bf887018192c1b38c0cc2aea96243de78a8e92bc30ad"},{"version":"b1ffda5abe3874eaf7ee7e57cefd4c4ed1e85e00932b5da6847cbe0e22c7eedc","signature":"29afa7f4d2f64a222d590227109f01361ddb9c6588355096f6aaa036b9d67d05"},{"version":"0cdab5351929ad0e2d94ecea2b8d60d11fcdb153ff2355ab9c5109e84a697ddb","signature":"91537516c066b5bda3446b1dbd01a6b3ff342925cde014d5acb7b6f8b99ed12c"},{"version":"0172224d148517f7cf90527aa73f03cb436b3fe7f06ac70c039d6886b8f9dfdf","signature":"d01168279f1f9a417a578d3768c58eb5170a5b3445e7b2dedf880dbf0d1fe873"},{"version":"a6d5aa9d1ace7ec2a992729d02341737fc267a9d2d7f1467313bd170e4b26b15","signature":"a65ecfa05330aaeae23d23b899f0bd37c34e42fa5083d180b4a0bff3dc3ae25e"},{"version":"831b967c1911010eb3adbfe96d76340dce858803d80310236352a7b52de799c2","signature":"76d26c617c0a9f48d4e21938e684ae22166d2d3604d00cafab5212b0e15b57fc"},{"version":"d09b5dbb314f199a0d7ae3c2c2a9c7258b611f3f28d1cc83c2685d3c738aec3e","signature":"068d0597a17af822c2ec3af9b1c2a9b9a26c0a4387eb66f655a0f1d26e36ba84"},{"version":"35164c8ef06e7c366f6f45f993da6e0df0f7c2cc93e78198c199bec111da8fa4","signature":"942546eaf5ae2d0c5948c6d25a748fba25b6f4d760911ed595b40f043fbae102"},{"version":"ea484c456f3f9236d0b324d2c6563f6e77571c9414768590248a016b2e248a3a","signature":"65bb767048368601ad35597c54f6b112e3147dc84fc338199931af5d57b8fe95"},"1ac040d4f47be4c1b44b4a521c7bc1264c97371b3c51bc2170326827fc8a5406","f3d7aece0ac20c6911c50aa54b50c1ae6768a8793a72412d346aab2b66b4a7f7",{"version":"6a282a4b745d9ac9d04d759b34b9e51124a950ba33d83a1408f76742cab5d8a7","signature":"519c584866e4d804355422ce52d8088fda0124969a9668779f10b0d50e114153"},{"version":"3cef134032da5e1bfabba59a03a58d91ed59f302235034279bb25a5a5b65ca62","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c05d0fcee91437571513c404e62396ee798ff37a2d8bef2104accdc79deb9c0","impliedFormat":1},{"version":"373cf226ee7ddf9535231d4ea2c24d47e4262372e1c075aee7b48e0d2d38e759","signature":"3a700951382c62ca71c0a4fb951071e1a2692a3ddfc899ba0145c275ff12a006"},{"version":"e91484fa999daf133fc988973a12652f1f59f4e1e4e440e5f5e7aba9dc419e54","signature":"7cf5ac50b3def9f8df750c1e7ea9a102216484b4bba94f9e0bf68458bc77eacd"},{"version":"2d4b53789aab997f99121021686c05f5f54aae58fbb0525243fdd322c80d612d","signature":"5c975df906b720e560dc80cf99f12cb2763329a0d5c42ffe3039256137dc3a70"},{"version":"e43472b89b27f89f28fcb57260e48230b95baff6fa6c489c5710115cfa6c9506","signature":"2f9e549adb20bf7d44ab18efcdb5e7dab6bdf423d310f3df05e5ac78e3828990"},"91d7a64938101c27f0f5493074dd0ebc4f82ed6d58c42c8d148235de3f8978ed","d563b38c81c713a23b730e0e385c44442992d3b1dfad2424fd9c635e3eacf593","6d4b59d8a599531b5bd5cef904c5f8832f062b79eac5298805b9aade268d66b8","fee8eb73b4397c9d3fc50904fb4d93947f32879251345c687761a5ac20a76314","0d922edfe50c6ffb2c16d49dcdd160dc468b0f93f69fcb021d6464db95664a21",{"version":"df3a3dc2616be7db489fe6a853faa1e52a83dfde06d2f3214994ee7ef81f18e4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"3d3208d0f061e4836dd5f144425781c172987c430f7eaee483fadaa3c5780f9f","impliedFormat":1},{"version":"34a8a5b4c21e7a6d07d3b6bce72371da300ec1aed58961067e13f1f4dc849712","impliedFormat":1},"6a534c594838029f9096b88db91c054e612ff951a57ed9d9efd92f19643a2753","6223f56cb79eac77e1211e76830da993ddcd9baea0dfe2d10a61a131d39f427a","20d8184cc9bf496dfd9415be762d5233809d005d149417d3c30a16084b0c3842",{"version":"7f72a954c349bccf89e393e243763fb141257a54d6647e369c79beda371378f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c6b47f0ea0108d741abf731f728b357a8370c238ffe73fcee007619484c24356",{"version":"882e8d0ba2abbb1b69de1964aa644932be0278f7ed640ddc904541ffda281fa8","signature":"f592c7e333a33b4e5dba58516b31a9ba2c3f5639c989fce88351556ba49606fc"},{"version":"316e4731cf6b5fa0f7200e020cc7264355bce4cd1c0a2556296dd7f4ba015b5c","signature":"e38a144b393c547e8f484fd4ee07f6790d350a3f1f1148211ba866434cda2648"},{"version":"214244e86df9709da19e41c83203eb228ab74388a8899c0cacdb856bcb9b2091","signature":"571d3448b7e5dbb700ec919745d70b84c0859909793935026a8331b7666d91ed"},{"version":"73f615ff0e9ff74f51982f4b09e85f2474c1e05a50a4c75f099061a3057094ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa4db26266d6f651c711350ddf671278179e6f59b28d3c390ae50a9b20a3aae4","signature":"921c81a312317ce376b3db64ec158a40d264b56c798653f7985b9361289d951a"},{"version":"973c5f132d06d0706025fb3d9a12e2aa50d9bda13557ffc7498cdbda3098fd58","signature":"03da542307af2869e7b2c1de8d1237d05b584cf0acf36c9d40f8e994f21adb2e"},"5f1ab4340b3a3f3d2c88167d0b98d1d8ae6c6d4b1ed845f25c3069d7d1b902d1","62fdb9ea1d1284dc72bae3338d2a20c737814b30d30c9d0ce40aec4fcbd51746","cab9185002bd5ebd154b6106ff93ae480bb26be2bc14bbf19180ae690449af28","b6ce8cc18189aa155b9d4386c03e3547f48121a7e3f37b66ed9ad43190b20dd3","81aa2ba3522bc10222837dca4556efb07a6628de274c94545a536c1955684ef4","fa1702a90530bc09b078bbfc9e98010c20333706f9d225c18558a146ac9e2219","b980df9c1d9398fb15cda202074eeb45eca1b733888708d0fb43c021b5411991",{"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":"6f815019b8b763503cecf9ac86f9de6bd8593180a0db3a624f98acf88dad162f","signature":"b5e89db47e4299930bf6020c3ac33fe228d590042b7dd4c5a3dc245027bd9a83"},"8f6aa64ab08524e8ee85ed63f8dffa377a7f4017680001a3669a963162f9ddef","c7aa0820877b8341f78e794fde60d464746eec82b0e2624982000ebb19fb8f8c","7866820b43b8c2b53cf5a1d2f4de019e059e681e9ec7cff7b5972800a8d78732","f38eb0a2421beba20ee66d42353732cf2ce6f343c1b1c322252842e0f22b8308","88d59e42faf36bf3fa832f1e69ed374efa2092ef1128f016701503413b9c44bc","9382ac249f4efbc0256803deafe838b51123955ca8b68c68a4be2b2c4a94027b","7f8e8b3d1d986e5be4c13b7a642a6f4899f1af03978448c01183a00e828c12bb","e1ae660c622a39b34c87f6dd244d59846a5d110d2c39bcf4316a499b166b6e7a","7606ef9eeff41c0616d32c7f6fc2086c38b34c3d7221598ed9291aaf126eb178","695e9a27d875aae3f404cbd6ff901fedf0d77c193cfd9552edba781658ed0e85","d0608ad5086ad006c7c2a10e4fce58cbdac946d73cda4c270717bbc11751afcb","42b18867a7543fec221e4f0321e077538e596cbacfec0595872df4876635dccb","c07f3037b31e0bd7e1384c41a6fd6524ac141e8f56b93a4a12ec63d75a704edf","6e925ea850d5c27ac93c1d0a26203779a347ded9a658643ac71febb557086d09",{"version":"25b749d6ada24514fd767c7212f8710ed80c3f54499a13642246913e678553a1","signature":"27bb3ddf3da26f0251f6fa1f7b1d888720e20fcb54f8513e691c7276c730e0c0"},{"version":"f77ebf90d0877e84d5f546d128be5e362554f93395f46ab6fe1fbf060b962765","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8efd52cc22298b4d6f0540b8c96ad97a563fd1a0effd9c245b9da300b5eef04","impliedFormat":99},{"version":"0d8cb9539485655600b329ddfcdb91d1b4b20f5d1b40a9e40c8017938fb68d5a","impliedFormat":99},{"version":"51aae950c97b61105064619e52f8b4e702ed9d88f6a38d8a6d461389be28dd0b","impliedFormat":99},{"version":"08a8feab6868367d5112474f9015e5b00c101012a639309e88fb105f94ced534","impliedFormat":99},{"version":"a15a870f6ab5a26a7eb91ddd8c47ff4e00bc23ece96ab48ff8aaf42450478a50","impliedFormat":99},{"version":"73390a82cbd5ea87d8bcdf183d66853207a111de00a8512c68ca17b47a11e65d","impliedFormat":99},{"version":"53755d0e8037d36720dc68e2e8c77512698befd0c0d48ac5d20c41986df91bc2","impliedFormat":99},{"version":"e0f44fe626dcd0026670f01dc0af34c40332729e8a1ce2dccc67e2ad5c96e3a5","impliedFormat":99},{"version":"2919501096a871a68fa6bda28480d7c237b232928215d6edec67e75dafc820c3","impliedFormat":99},{"version":"7f7f3dabb63cde6344d767f66379aa90b17c87362d999507724758247445d005","impliedFormat":99},{"version":"1d6f7095a9b7bfd7d035a4b07f23378ba7c44993d881b75593a252f186671e51","impliedFormat":99},{"version":"952f574aeb762b9927559c9d128dccf663352aa92736ebb856d2cec80fabceda","impliedFormat":99},{"version":"c06c1379c3f1bcf21007bd9a92e8c7a8e63611387392411bbb2399c0a4c5ae04","impliedFormat":99},{"version":"0fab16fa249312e20d9a96e0464c7ae63c841b17c02401a59ccd0bcdfa67bfcc","impliedFormat":99},{"version":"027464bfd5f5d3110b7b5303ee3a09d3bd74e630393c5caef2cbfe1bb6ca59d7","impliedFormat":99},{"version":"3e9aad7dd39dc61c41d0c249427d39b3548f7ac02f2fa2e4a813c38a8e1a2e01","impliedFormat":99},{"version":"3f28c2bdd8d3da9487f032bf85ea09bf9f24f6f02ae2336cb65e6988aa92de5b","impliedFormat":99},{"version":"6a437b4b58f8b3b220f3ae8af2230bf3bdd0fa4c17db62a9a2a03fd224a68a70","impliedFormat":99},{"version":"e16749a9377888735e5edcc765da4ac2f5a552de2ef46d930039b2d54f199fb6","impliedFormat":99},{"version":"bf973f547b27688728916b64a98fcfa836772e7382211a9692684947220ad550","impliedFormat":99},{"version":"507b0e93358d09b74a0caef2370175290a47c790dbf71fb63d1b4593b7e070ff","impliedFormat":99},{"version":"eb7e05259b0603e91365983fffb6e6dc1e574f1cbcf09c51bdad4f3717869a82","signature":"5679163e510a4314da81e928dfe7e72c6671b0377ebfa606c80e18db43ad402f"},"8269474f9aca3f56fe5ff007900aed4be90d6271a628d561d20cc29de0d5576e","88b5d609cf1c008e5d7926489df81bd606581dd083772e8ca735c1c0bc103093","dd0a4bfc93ee858cf6af173c428400652c01288761e7dc00b513652d005cd91f","f35ccdbcb49becc34f1c71a68ad0d843bf02fea572cb852884d0a96ac7169830","3d1ac90c29f450b8b90705d05264fd29f1034b5ebb6c2d2e9807489969e0a33f","6cdf25b7999cac1327ad91005d4aba65743aee71a2972eba11f5b1164e39fa4d","0d96088dc89a49f328fc47dd75713536556aca01e187da7dce124fbd2f395f09","ef05bfe2ed3c6fc7575cca3a8ee25f4a4aa28878b83c5d986ee47f4483f73b3f","dcdb66da2fdcdf0e80f084c8347e2114f573d97619a5e37f680a9ad5656f614b","d322344da57346fad32f22627e0028bdfe12501d7de9eb4a103d47c9a075a85b",{"version":"2d5bb04855694db19d13174de99509d4d4799d6bf3468318bb74b54ffe994129","signature":"9a6473983696c0401765d2ba2558ef9b0670592e8d74239b4d5623c42d686600"},"dbbdee1f403eb2a952f5e8ea724cb1a20a2b4dd63d8232e375a750d4929baa88","28c7612edce38076988a57695e970654fd09467b806a8e37b38a26946afafabc",{"version":"2a40fc7affa68ec88b30b2d2d19639d8fa8dadee567da499d460257372e04ab3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"7f13d1469822b3ce57bd440274f4a8d9c2d9925fa644d284fdeeb520f0ab43fa","0ed05108ce0f4538b5351bcf9d523912d200abe5b804951cf18b2e12feff6b70","fe55ccd61eb15dd0480443215f5556cd56d9a68075a79a1294e2d9cae200d70d","7fd1557a212fb2a671abe96de2e1b6a3e5110a07c49076705cdaf9c3e0f81f7c","b9eb4fbe039e06b65ba30bb786e50fa9b48e25d7eb26c4cc1cceba3a6c81615a","7ad085c66c15e665b43f1055b09ddc1d9c11a3d8f21174c9d3d9205d7dbc03c8","68dbb99a0ef2ffb046b1385fca8116795a7b4bf5700193b7bf2fe9cfb62e72e6","ded33ba85bd4f5491a76b8972dfb3104e8b7b4e1b256c44313d7ea9d21647d2c","1f260100362d7309e0cbae29fc09c4c36be2e4512013a3f6cd4706ada09c6675","4a96c42ed30bf77b8c127453fda697a212495a055f6dcafda400e42eb233d029","f911a52096fa219660557bc3c9ccb5745889d8a357674e8ae67585678891e106","3a2cadf5506cf6c268f65051dd63ddd5cdc82f5effcf658778af2b12e497726b","d5ebf3405d09e5eb9e3316e8b6a7329bba4fa306433222f109b9af077ec77525","2e1f498ba2e37492111a97c6048e07af342ff8df126eeaac6cb99f94372f8ca7","759a24229c02dafd2cd73ad6e3325c043d16ad85081d2e76296664bd96226ae2","2fb9eaa3ddcb8952e256d1537d6edc1593dd761fa12777b9ccb88370016463fd","9b7287bd51e848b323551afe464c4a91ef2b74bf1ed703dc7c7c5e35cd9073f4","26b692cceb67ab44563761e4c5701f66b58f7ee354393088e3b338aae9918ee3","89f0ba8c9a02ad55e23f296ff35f0d1d6361a190811401c3f0d767a1aeeefec1","5aa6936f80aaf206b952e46cb830f50e49e37862c6cc4fdca99000c797995a54","1f607599e3d2f94f8bc20f8f46a594132cd1b1b1004f0a4619dcfe84f792c774","827a3da73904af54ac6cad259cbde0bf0b811d253a3fc370664cf80cb65ad6ed","ca3da68186289d0f9bd93dc9a7e5c3eb30dae2526ca55be4b106af239b63d0fd","fe17ac85f4e0b305bf44f3a4d81cb75397d9d7776aa2649ead1caf2291f0d416","22c8ef3ac26c1ded1d40c6e1c48b6382fdf0d56a5a3e77f8bcb3bb198a1f769c","c7f6c31638211891b8f6ea106d4d9ca0cfe249adba1528b2d1ebd0f267fa324e","5d513a6a908bdec9f0c3a72c4fce232063a7365983847c1ade848a9970e97aa9","bcf829509dc2bdb8f3851efb4451010e917abcdd8a52e2ae302886045c0e38f1","10ebed90f6cf7c3e637d5595d4dedc3377575aad097fd0b28e15312572c09622","0ba02535f72f0cc1712b1a073897c522f3460eb86620046f1f3b250ea79fa567","2b4b00a99c93370ce060e954393ebac6d299d87a0400b9a69307c97e8021abcc","1f1d8292770ec1d17820eda3e3ca60550d3c356d4f4832e211e9ab2ae5d5c9f7","07e108a85e9c41db3bfca18812565985fe9c4185c09e44cbfd365364daf67e80","7236d482e2ca1a6307e2182466f18dc8ee373209e5588f156b019f949cd9ece0","da35015d12dac52832201a4900b07e4b0f4fc08f282eee6e7131d895db1930c5","25e39087798255a9189bdd829787ab8bd7854afeb8f8572586e73d47b3874412","24ac88c5cd809281eca45ae09b8b16dec4318221ea50d1b47888254798a046f9","63bb37a1d958427795e2e6ca7fb9451aff711e665b45bd4878ba693da9a140cc","d8f785b87f0a4f596648fb2e5fb351d34d4bffa5288980a8eb52ee82ed27180b","b581577a6affe98a790ea707971843ad284ae4cbd4bd1dc17642196e3c0de158","3b8a02a9dda0124bf30a030727c96e3d34272b55369bc55cf55102cc90ff4a41","0eee1242c13bce68990b788037aaecdb865d63943bc7b5681b8688cbc6d64e60","a3be9843036cf2c658821504c4931c7e1055db71db2a607b9bc5d8aa7be679a2","ae3566ec3d167f05176112f609460a77863016fec3216a87a15c36bef01d370d","c914982336f64f67076a86d5569fd1b878a42f415962a07d1d151a4d65d187d2","4d5caf53ba49b41ff378943f6b1dad1ea8e042426fa713b57c9524219bbbf573","bd703e7c27cc5dd4780eba552a51a698b459c9f12dbddfeafa9e0d216a4e66b2","c09c42c93bcd3ed631ea6902d069b5798db25862e47fdf4ba5f47ff0d36b2a51","bf82447aeb19b4df2e40900f920c15695a8557392588397ce359c51b133c00df","1e4dd6a5a91d2798fa4dd70d7e6f682475942bedb36b9f870cbb15e5c1f1a54b","0a5fe133b4ef41f0f2443b3cc82c4b99be11738a93d613fd12014e9d632c2fcd","cbf3758a6cc16ff397b8a2a27221d1f6d5f053265e353af0f37b356f0384b85b","e9dc912bf8a7678feb40d7d996c9263bad3b7fdab9ad9ac95d4e4ed023403d29","4b3f5e2463bca0844de6802fe4ee8e1e29a3c11d14fdd3fa7c96ae69fba8d74e","d878f9fb504fbde395cd7c61e48f2fc7bbc7d0cf14828004f95e5f9fe64f238c","12564f17d21c31e77240293e7434c0c933945a3a3a142130eaa166d0042f5529",{"version":"3e8a4a82fa1baa0281a0ccf309c22c954488a949c13a7ab6fd7c171b1bafb361","signature":"13428a7125f291070d1c531f233c9e8719c80160dd3587d51adf86a3e41bb62f"},"b51fb5ae439f311990f5a5c2fc4914fda89e5ebd585b33d22d0251afe382d391","deb873b1dff75e59633350db7fdd9e3c125d248ac7bf2c193a81e9665bbad9a1","d9628bca2f50c1a70ef77c452fe293c91380dccd76c285dd3aa988c0f93fed8b","59c0ddf46c0d1d17e34ceb4c253a3f3bc7654c450002d8f8080476a3baaf5755","7adf0dcdc081964a00a2235aa42fd757563b15038955013b98097c5731705a2f","d9df7df9ddd168648cb6e27223b325763738bccb1b57e965ba0d8443cd166fe4","99f67ae9774e4bb88839948649b26a55dfbe8b99ec80ecab4c548102d2ddcaf3","aae8eb9b4f313c457b2f82fde10a63f117333645b818b48d2ed26fb2333ca42c","53ed7ccce63fb30e129e73dd0abff74d68929d92fbe3b20f8ade965780b2353c","3abcb2afd6c10eadcd9e5cfcffe3f93172b891ac80b079ad1421105d1574b983","ade0a7d50f110afaed90ffd5f24f1617bf9b8a0d2f118530892d139ad67f29fe","4bc4b9b2e5a66597bed3af39f456ab78cc11601400c5adb4ad46a173bd03da41","a3311c2d4225d8eacfa5a9e662811aed57fdb4de78b824f1a702311b3f99b09a","d7e17a1b90344a6d9c26f1462f77d6350a6882706064a36e5d640f5726ce49a6","ea2d922ef7dd2904b092b91fbaa35be0af427504b9fc7e14ab5fbb6cd7c40846","5456720ba13d5a5037b07c10816207ca9a81cd79a370af608115c578d61146fb","ced670d0bf8913a58d8515d3b7ffc0e9215721c717efb9349c84d4710bfa7ce7","8d419ae38254b6ecf56946523964d6561fa3f8a677ed3c21b4b5d1176a3b5a51","6a073d10bc03e721d0b1ce2620253ae9b69daa32fbebb2f7a8c67e7e1579c6f2","a80fa136e8559ddb40afdfe957e7c625614d02ad4a72476a1dd758fc31ed2e54","e79d387b4470cc2ef4df34e09b4113eb85200a7c8c6508e4a2f418c63e29ae5a","56396e7c37789adc6f28a7c461ae904c01688c2836ac6278a9f9ee864078c7b6","4025a1efab8877af2ed8d8edda349736055a800404134c6b24ee95cdfb0c0ba4","0a46d4afc023db18a3c9d7816e3f90d960122319e2765c5d686f76da661864bb","4f649bd1b169333de666f079f7989d184c27109ce26544354957f5be00abd232","f84acbbf9c1536d22e69d354fae1dc2d43430ca0e524721df713722f9e26890f","b5da1cdeaf5fc3b53aab62bbdd5da7d9385fdb2839a18fad0e3b2c31c5d888da","0e30e16f547666851c4fe51505a9feeff6508b2d720b7b8c60691e5158db10d6","1c03c99ee1b5f5bcf0704dac9398e922805929cd23a7ad246ad0d67cfc3826db","a8adc0df2bc9038f9423e9947d03f490375a9f615cc8119055af8a695bb830a5","6096174ef99bb11f2656cd3f15a2fb649e504782c6ee27090448b681e33c2b40",{"version":"987a3bc405a132b704d415e99a6708c6ea54d0a70766ecf1ae59bd13034d848d","signature":"2d44dcdbe1d297af2ef6785176a9165f4feb886490712c82ab8578ca96ee0d10"},{"version":"187610881a6b1f7370788848d0a2af5a17e94b9b437727556ef3d2fe018a98f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0709632ce350e4970bc7fdb88e656ecea4b7579875a6f20ad20cdaaad26f369","signature":"f9a530c655221c9f5a24fc3421f341b21bd38da824f7612da7c87804306eca36"},{"version":"2b3793a5342b5d7ef5498271aa50c1fd31ce56b70f70d0dc5f9da4174eb1e5cc","signature":"9ae9233a7cd435509757e52ca1503b31fc923e1bf163d9bfba847b1a7dd89e51"},{"version":"1391eb93befc7b56fcc8fc9d4c37affcb37252ce6e91400da018023fac32c807","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"17b39df68c9417376ab9d3f845ad45905499eec1ee9798fcdb980cbcacbf2f44","signature":"f8c081ad7f58588db5940385eaf280c202e6ce42c0117ad62f774ffc421712ad"},{"version":"ff7b4ff43ed91708cb770527c71b00da17728615a98d59f62ffc6760381a1987","signature":"883833dec7bf0238bfbbb33db50c709cc7ca3a1f6714d17992c0d7e82a964d00"},{"version":"f56d21cb2be8cc1ca29dc2ec7c48ab92fe41d38bac18bcad6eb20b33c07c1b8b","signature":"3d655def48973efb420a82a2e05119da3a2c45672bdfc7a695f6e569edaa417c"},{"version":"3f072b168376dc71baf99d36fea4aba49a269f5852826888a3c6c95e5c9cb202","signature":"7fb20dbe5a83b73a18118cafefc659b66c75e285f8f6100023eed6218035191e"},{"version":"98ba07f2f211272213e4201fa31bbe0de1f95049cb411f0c7dec9e9de1fc8232","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8d9a8784de549deea41f12c4241eb87f77fe7b7f8222ddd7a6ea05085980d5c9","signature":"88af6abc2bcc060a798687a7bc8f8bc23f47f5bb2ea736e89666093f4e682a0c"},{"version":"7bc3f411c39c03e6ef2f245fabfa4bf821920e52e5c1083759cfb2c2dc264296","signature":"e18de9a7b62fac87db7bdfab03946f00b49de5cfc11b37f39d95c1f6d05b7dc4"},{"version":"b8747ecda57b04b458af6aa127d1e438878a6695def6c91ccb0820723f71bfb1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"baab78e9401b9a82e8fb3634de0d3750cbf4d3d6afb59ba28472ab15afd3a749","signature":"3d01773dca02fddc18139d243b8adbbe1c6c6447b8235bdc3cbbd4b493e7ffbd"},{"version":"1cce0ed0784dfa68a0572c20ceb1a173664dbb3ac59eead22d55be246fdf17d9","signature":"5de2fd3f978ef1724ed1d72271f8d9bd911d19d80a709137225e173127e3c615"},{"version":"57e3b4916970da260c692cda82bc670552fa93563710da8485ef3f1a40fc0cd8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"24772aec0e0f59dd17a2a1c4a924fa4fc228f24c64bee4fee8c5c08965f05925","impliedFormat":99},{"version":"e636cb7d61143bd3901daa91d1c2c3d8a53b677f6bfe51fafbd2d14a51efdfd0","affectsGlobalScope":true,"impliedFormat":99},{"version":"d33b19c5e9b2f8b26b1875c7aa12229cdd1e3ff0e809c89189805a05c626dc3f","impliedFormat":99},{"version":"bdd14f07b4eca0b4b5203b85b8dbc4d084c749fa590bee5ea613e1641dcd3b29","impliedFormat":99},{"version":"077cd7acbb4a3b50b4a01690d6a7d2583ebb39335f612763442a4d33dde01c36","impliedFormat":99},{"version":"8b9ab1d118cd0092e03b36d26b83192c6374c30e16abb7cbd0ad33979fa0c2a7","signature":"a3a467223e1b0d6dafe7ba2a535de44efc5aa9438c3b277566336031e5cd3f4a"},{"version":"db1d1a51416710f03d5b33f8ba166c677f7a372d7236d0d75857abcf2c46d869","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"cf0f4d9e5edafe3f777e151b9719afb37ca6abaa904fb8289367fe99913f0ad1",{"version":"03493681f3175378f73cc1994441b55eb2f178585c613377853cdf5dfb39ecc2","signature":"4540e50e72fce7b0cbb91e773d9c3ace94268cad237d1a032f294b9786a348b1"},"59e8ed7fe97a22a7e83c915d37eb2494f0eb416d7a52d0050824d718d0ed8cdd",{"version":"360a32be2efd3b572bbe4f092987fc0c27de1edbcba3a5a99b9eeb67efa91d62","signature":"bb97d8ce68b8061ea36f60acf43e82f3560c4e53522b05949721c6b7d014d89a"},"755907e327ad953500fb7ae52e0dc7dedceb54626942f3af04eaf1cbf20526b5",{"version":"8d096c0b73874d64e56e5fe99fb94f14b5a6fc0824a4a1cc0251147a823c25a3","signature":"06980b548bb6ff2b15c92032296a46d7f80d3e8ad9af172f5c2ccdefa86b3fb9"},{"version":"d330961532fa59192f0330dd430076475d3a3f5cbbb60c2ba196351c069243ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34ef539c1384da9fe858fb9c16368ddf1624a4914dc3f0fabc7a39811bcd2668","signature":"ed6cfc1cf330cbbc602b7a0305aebcef220219fcd5cd3e4493e5afe79058fcee"},{"version":"f7409e1093e57b3f7be327a71c71087e1f7a767333fc10cff7c2504af7222f88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b203b12c30dae8bb84ff0f770a857445942d8c9ec8d6ee472c3ca6b0b5b261d0","signature":"9fc50c5741ded49943ed4b81fc428d0aaa18cefc596400fdb71fdd11a21d8d8c"},{"version":"24803211766a60a0fca7ca541d6a158d376c65f7ae4827a8661063c2e70c06e5","signature":"6e2681371e356de1c4fa982616bd1d26b7c525b5ca9f75e1f688b83332e8a81f"},{"version":"1453393d564bcb47dd35ada6b469c661ef5d6c98f9dcbd7bc0f9eee3470ac944","signature":"22462cc125563699336669ccb959793d6c462626957a1da4ec4a639d4341fb3c"},{"version":"7c56faad4a628f9671b73a1227c941f930b55649699ac62931e360389775edff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"579a472a70260bf6b40d68aeffa65bf00fa5c59a44bf214f2385dce399f5898c","3fa0c656d89d31fa67f25c8b3eb9974b1aab6fd8fe5ae0fafd521e7d6808f71c","ad5ab4bc064063efa662328e47c8eb39c80888a25467fefd231a32e874162d6f",{"version":"f0e29314c7c3239de9961d460d5347081cc6e49bf065dd0a6cca6e7132a99ee9","signature":"ccb2b3ebb1fa7bd3fa3e02c7a23ecfb2ebad06df9c3c8a9e685113d81026d0bb"},{"version":"614e5cdc3c5f89f035510a0c61652fdeba39a62c9904acde7de79fce2d60bfe3","signature":"bfe40cd3dd4d0d35754643dbf07ecb96362953f4fdb490803e122593e679db64"},{"version":"36e5bb11081348bd0869d683fadc9a4115fb28720594bdf185a13ff19faac88d","signature":"d356e9c1bad769f9e8d358a35c420cc37a0ad01ea4f865d968f3b7fe10c9c3de"},{"version":"4d46dcb6027f62db92924103d77c199455ed38d1dc1c6c29bb65a707c25f847e","signature":"14084429a03e8974f38aaa1890d6a80ea62d5c8f0b627e0f824afb0ac621a65c"},{"version":"1d5a2531ab660f7a4f8b4572b7a19c23fc5a431299ecb8c1846cf8e279b97851","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6a819db1ca73bc3e3b29597b18ca5c36e2dafa02b171e8bb92208b2d50b353","signature":"1571b1b7546d0267d42d0c0b3e1e4593b2ef990541b260a9652427dd82758bb7"},{"version":"a62a7e45347ca1dc80dd4df8d2094790870c03b1c890960cce9628934f695295","signature":"af0b9205baeb5b5d2e4470da33f26673b5c363db0ccfe761276f8af34cfe3e9b"},{"version":"4217680981bab6d62ee8fe0cbac591599bf18f30cf2be39170d344eca5f7885f","signature":"5d3ccf27d7ce9e5f390fa882da69e253103b64bcc4e1af716d4e385d1f7dea5f"},{"version":"0571fa29dd502778997d9453169040a83607ae311f6ab6a7ce90fdaa83f86a72","signature":"6abb8469a763dfe1299c79302eb5559ecc978df41c92c0444a30c1b55710860b"},{"version":"afaea598c95b1ad2bd0ed30bfb6ad867d78bf021b73a048b8fdaeb90cff22d88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1bddf61aee12dc67fa70b5e40a9124f1f71f960b6bfe006fe4078273a7f77e5","signature":"77307295274cc402aca163afe863f0ae8a1d2e94588f2acd35ec24d77af97b75"},{"version":"d579bdba0db63d615c541195af19b783527c13bbcc870e83ea61d198e3be56c6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"10412b70545a4b21b51229be0a35ddb2bdff35e164c35e214f5f56baf863f12a","signature":"8aa83e11d68ccfd7360e5de9cb82c18ce6dd67f9c7dd89c81dc67e55dab60864"},{"version":"ae6af43f3746a31699410d070400739e19ef2281ccaba80b5e4b1c4dd4ce644b","signature":"1fd318722bb7bd17560dd18e11b824febc7904ec355636c7e042efcf18859c12"},{"version":"4e92e1739b0c23ae5b37b5dd14b72caf2adf6d0abc18d73596b627d63efd0f3e","signature":"69db250a4e6aaf0f4ed86907855c93327ae8e663b3b1fdb429fa0405ee768215"},{"version":"7aa764a1146707f1b9e18292969e24f394ef3c347d4f396cc6f90d39c3f3b6da","signature":"7c52c6c55104753b3519528829004136bfbe6e76535ec0a19430668fabd41269"},"688c5e58ff9137a2c5d6eb1a79475ec4c9d61c34bb10080e21d09babaa30ae1c","c51f1961b6b22a86183d4e8e166a4b08df4cf3537533f9249a79d3b460efe6ab",{"version":"13cc0e63b3212f43a760f9618ac9a5a26a3123954baa408ada44dd9744d060f4","signature":"6d2de774f7f1930f5a1a0061d45b779777be9dc7e6125661388a11a43f386636"},{"version":"693212d0a67ee305c09bfdd670455ad335e448c9fd52fb8c69ecbcda23eb2b93","signature":"225d95d4c8f9caffa003ab70fa3ac2d8b66e4bca291dc775b2d1ad4b676b660d"},{"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},"6bc9832d675edd15ca0c8e096cc4008e2791d822cddbe218e7fe65d33de8fa2e","baad5518e27c0ff3bc6192606a3c70d64e52338ecf1a1492a3582c9e8827a7bf","a418d3e5729d2bc1f21789a3926a6e5db364e9f80410207f4eb28b55a5c70cff",{"version":"71dde8ef5faa2b2f5f4a8f56944429ff768600489ba021017b68473c93660eab","signature":"b3a61d1bb2c4eff882c25e5284189e1934aeb4af535fdb36694fc461cf4b7068"},{"version":"0b791c213954a91e7d80daceb4b7d7b53600a731e2227d3541d88a09fcea1621","signature":"b6e882b417c55fc40bb0b42ad061d8f97bd0b2fdbd2aec5aa2aa257420c7c2ec"},{"version":"c041ad3802a420609f6fbb3200a946b897838cb21b76e176e78b0cafa83698bf","signature":"63b2f936da9faea8c52723d6b78ca09ee0bb769833160bbcb000be8b7cb456ac"},{"version":"9d0c46e2b8776a71972db76904d933f54d190601cbd57b82438254c275808ccf","signature":"95bee50322d4d787b4a886030c691a2317aca49f557c115e52f95938343f65cc"},{"version":"df804257d254a2e00d640a55eefb2ae628da95dba0085ca824a04ea3ff69ac99","signature":"2302a6d37e153539b259b1f3bda1c10d344984b15efa30ea39ff5c83b5825977"},{"version":"74226e280a2991fdeba3808665dcce17f87736137ca79404c5d8d7c668eec8df","signature":"b4e98fe21b2b7cca7ccabb5169df346479a0e3bb6bf46c25946174977917d316"},{"version":"bb520dd5abb511ac234e88f420dfbfba03a6ef74a9c783850bddd833b8235b23","signature":"80923ea73f37baf4c06eb870a02060c68149fc8a8e47daddf6b55af1047d2b0c"},{"version":"372db055c8310930dcb90ffa00df06b44ac8e725c75e0c172786676ea6a11794","signature":"6179a86622a28cdafe5d99fb99e1ab06b1a06011bb9a8ae9d65d4697e61d5316"},{"version":"0d7213f8b71376061118e4f91a6faac51b38b372ad171b4df50bf3559ac2b3c0","signature":"37a718acb4d240ce0d45b7082a821b9bc8d9c523df47980aed0547887beafcb0"},{"version":"b858c7849e256828563264a2345354ac829be7d7afc77e2c04f7683b81ccc79d","signature":"e724140889de1a68b6fac45652942a37dbe94d0951da3249196817e9004181c8"},{"version":"a4c991c3fa2bc9437a6d84cd1b2557b904adf57f6e7098943d65a01b8c57acc7","signature":"4fa89a082213215027fc85892fd3a42bf898e652eded33469c2c31a75cc7db12"},{"version":"9da149c4fd78a4ccab4e68a54c0cec7c3bbe48163c7e4cb550569bbca603ddd4","signature":"f50138c9b21bb7d4b52c5bcb99ff08cab9112b3fa3eb67ba583c1c033f5658fc"},{"version":"baa1b838cd0e200f302fa49ee523d8f74fdf7a16c6d14a121621aec564cc92a1","signature":"fe0fcdbbfc40a17d638651589c6fdae7c4d56ed10a0bf9e04dc47fa42b94ead4"},"ea99af3c9a22cee8ea6b5754cb9d29c7076588361543519e0a95675747e2c17b","9bd02ddf990e7c7a97c5b70d4357ed3d8ca8f7bc5061615ea7b6d36f2e469ab4","0bdaf3b9ac7dea3986c57c39de9ded3d5d4508776b840dc0764ae0dae7fec9cf","36817a296ae92afafd90b250316bb568a39791e1fbcc47b0ceda39b7c19cf358","eb3f998132c1ee368d9196be6771f374f6b809b6693f1f6a75be7118cca56145",{"version":"5a11dbf49dfc0bacac057085c4c818507b8613630a6080846a330caa09f40a1e","signature":"887a929e952df6c08de135d3c73360dd80e833b99706ce3aef0c8b64b26ce68b"},{"version":"a128283ceced70086ed7a99436e55575c7d385f95ec1937b86e2d7c725c6e532","signature":"bb7d350c5b0c764dc29222248163f61f8540997db099b636c993ce1ec6981018"},{"version":"954eafdd8e119ae7fd13c652d092ac62f95a3c450127f9bf2c4235b9a5550f9a","signature":"7515a48dc017014e10b59b93449b24053cfe2f6cbec7424292ff04fb29f14569"},{"version":"6d9e1b7a1fa967fb8505a5fa33073efb38aec5e7b75f2dc6383c9f84f3b5c0ba","impliedFormat":1},{"version":"0462956c97fcc2f9a0f7a498600008751aae2b004f8ab4da34af41eb2fb5317d","signature":"8c23d09073975011bf5b8adde26ee58c4c5c27b5c4cc656a32313963f3388846"},"8e03d5a09d01f13dd41c86a4930ad9562cc270cfff625eebc6a261b27cfbeed2",{"version":"40c9ed5a63b54bd64ea351b5d853e67c373d730a8243e2fad4757eab3ec5ab8f","signature":"26ab3593d88f84d8250fde332b61ef8e6c9331bf4da6e89698ed83e95c57f7ee"},{"version":"42858b5e9f40b8a0b2f860a6304d779419ecf0c8773f6cf498c882bcd9aae1fb","signature":"2f55fd6804783792ef44c4afb78fd8a5d6a2810a4c02007e53ded6f01e24b521"},{"version":"819a9152da954b548e16204dfbcd75208938e5e1a21464998d2d155c14f08f64","signature":"b6ea2388d7e17effc8c7a702bd5e736213f77468d04bda4d8871a07ff6b191a0"},"7d57f62963f7f76d3e4604f86fa9e7fd005e3e11bc81490b32193dd9b3f019e4",{"version":"d6f4b9a418add129da3898008b6036a68dab54ec3997b04dd0bee2534d59a2fc","signature":"2bde30c5f8e10d5820a401c68e63e2b81a23077352c01977a10cc10a15f266f9"},{"version":"fc381b272ffe38fb6844f6885fb858ac719c2ef6e7bdd79f0b18d6fa4b708850","signature":"4b3f4c9228ab5427308c651bd192f9f627d6e7465d7e3eaf63422d09e1a2e187"},"1d4e5a6bac0b4de345e9261395c7dde4f5788f2bf2a96734b0fcd153d83284bc",{"version":"a67823a8d4a16991b3653dda2eb722a15efb2762dec299662a23322bf2394e43","signature":"419996c73365008124de6ed63224ae81323de45079174bcac6b0dbdfc0108b44"},"2076d2cf1cdaeaeb896e27ec77082c91b5e485d297935597e76c8fec1c08e39b","a84795af5152dc3fc5782eedd4031079b9753301847f158ca3c979e551a4ad34",{"version":"92dc4e8b3d0e8dea1f5abbe30adfd3910a7be441c12ed6fadc738adb59f9bb2c","signature":"322baceb1c9f45aedb9e5100ebbbedd07a164fc03ab9e98c462e32b41cbdc90a"},{"version":"7da7e22f14ac9b7b4eb1f6628c33c7131d508ab0483dc146032aacbd35670b02","signature":"fe0cea76d1c6a718447bcd594059ac0a4c7f60b9452227e9bd6af7d564519f45"},"673b4a9dc7a23138c3dcb75f1a77cccf8d2a11167df9587286d6140b2c60499c","4ccc411eab7ab26ee65e6796dc137a43eb6d3145e6c616cc3fba32bd3901c240","c37155416601c041802206333c2537b309d8031770da2717d9ebbc0fbc0f1527",{"version":"6d870a6ebc4af392ccd2875f99d1dd4c90db167636e4b267de2e1d3ff14a6104","signature":"d02c70b928a2b77ba435d387595ed3b27e4466e3115d7b71a79c36c592238798"},{"version":"2f79a63626adba271a461cbaa34c976c26be8e474095b1bc8a80ce6e4fc68536","signature":"019c10e3a4d1413779d87a055ffea70d5dfd127b4b65a51cb2e20fca9e8f1f66"},{"version":"ef68d70baf9635137535142e9df63e515a9b8bdcf9906d6edaaf0e93313ac3aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1d17705658519c7d57ce6c5cf95f0c894c2dd754ad1fcaeb1e2c4207d54e6e5","signature":"0bdd8b0d06cc0910bf6a36f2bdf87794634ab7014e178c7a7eb928437c6e5b76"},"688842a137a6cf51df9153af0452d1099fa559ac47504178827954a0957f4d1b",{"version":"bc23a9eba2c69e497917dca9118a1a1169c27b9c527693802899955e9874789c","signature":"077309ec211d24c291b6f2483550990121454d5ee75109b3802b3c82d966557f"},{"version":"3fd930ef5d29ec40a3b52a43571be8356a95cdc215ea8da402feb0e67daf57c3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"697256913297b09bb0b83b40de56ad875baa198a0d1c03b8bd9a8f8f77c2e500","signature":"bce98e573080ea97bd3c360011d50db0affee86bc74443866897c0061708072b"},{"version":"3b455aef3a8f1084aec20cf655ea99e1f68620df4c3f6071e8eed404a1c379f7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"54dc720078a1e4b9dee72d9535dfc939609e8bd23bc5d58201980f5a302cd7e6","signature":"76964f1fd067c7ecd79d2dc18affd81fb2f0148dce268546b64bb6cb0cad859b"},{"version":"2c17c6e842123c5c921ba98cee5bd3886f3eeffd42eb3011819cf99cb5b02ebb","signature":"fad4e252103942053bbd84c183603d06e19da7332de7d295294f368a69af0752"},{"version":"47762a84ce21afc46f46c000e87fbf6b3035b5944da16ca8ac62de576d877fdb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"937a3521bff8fc032a57777777feb260c9ab218d266ac3d7723f7de32a48a430","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3858cf95d68efd835700eb41b1fdf881906eaebd35b07596bbd5b7c1c6fec6c","signature":"1671fc31a114078bc9cb71989c1919c504f1af4e0690995b055181a1932bc74a"},"339fbca5cf5752f3fa77eeef5ec37c42010f1549655b3796eff5f4747e419488","357afcfd45b1bbdf4029dc5107fbf70fbfb519eb1f7cce5c9d9e5dfceed98efb",{"version":"c9d2066069488cecf420d111d0201193958022a2905ac6c66689f50ccecda6b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5452448d44362f60de4ad50c0c5eff76066ef5b1b9f2b4921e83fb50a0c568d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1027c4c5e64d6a6f29226a6d46ba61ca97f539f7b78822e3f8da3937b867fa5b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5a7b42900c17657e4fecb9034c6bbd87a02fc402ee49415ae9cafdbe6f9d1dc","signature":"12ee1ad5a651c4484cbbdc6ea7d594fe1f8adc9684006988275bd671f510f581"},"f7409ccc7875d9cebabc5e27d9df8f3aca19ba959f30fa1b486418ae9c3058e9",{"version":"f51d0b8cfa092a592caf543d772238f191037278e2004d58e7354447d830863f","signature":"986bf1e9bc3d1b0b157927aafcfbf9e94478b28eda319c209ed8e9e613e14827"},{"version":"3b9d0ef4847a6525e297172e340c0dc383c8ab6c58a27aee0a27b2df991ecef1","signature":"57069ea736148610272f87e767f23439015d900f230c3060afa193d6b9029cf2"},{"version":"812fbe241e51f1fb745bfdb0cf447cff8a9802beeac16df1980f14499990900f","signature":"a4c0f47a1176dc8ca692834c31a2f1c95994955eb191e76cbf3e58dbd16ec08c"},{"version":"3cdbef5d6bb0c6201d2edacc2f930322c36b9798b90131787398324b67bd2130","signature":"b84e31232011f6ac9ded7e727871f7c1ece606d8179950f791bcc03ac3c03b6e"},{"version":"d6c2a85159f32ddee646895592b5c53e04b18cfb5346de79c2d003b814b601e4","signature":"2bc381b2105d5a05c2724fa4ae393e83f0adefda1e390db743e55a0cb949c099"},{"version":"9fda6fefc6a936e326113a3d3dcb56da7dc7c2a7a064bf451429b51e7b645d8f","signature":"f668ac39f924b2946f0e323d23da14308c0d996f579dce2b5fe5c9f2085c9ad2"},{"version":"182221370b4c51b9fdd08f71c259596d747b565fcebeed2875832d1f2f556c8a","signature":"00ec18666782d50d3be062bceb46231a3e2c4abae3128f6638529e9fdabefab0"},"e1d7527f3d057bd92e487081450d9037a1dc9dd5e2f8e84e1fb2f6c09903db4c",{"version":"41d344efc8e2dcfc00c0cd0d7bc8f5dabcc6bb0062766fd17aaf85deb4d60ecf","signature":"83df5dd9f98fa4184cd1227ae312c09558f5a00b35243e263069a3a545e7f6b9"},"d6452b09863385bd57e48e1fb836f95c3a6f36ebe690e342d834fd2868d6ba74","013138b404f25c507cc7dcd1e2ec3b0f7e7e7abbd42dc14003000066fd6b230c","e1f90140453b0fd52c46cc6ebfec2ebe754483c05e50dc74caae321d41e7a9ac","5563052849dd3f93ba63a20568ac06acf6b8e15c0b57aee1bd452e7e4b1d5d95",{"version":"bb2cef14d223750bf32f070eea09b2b2e176e10811d5c34f7a824628bad9dbc6","signature":"cc4068562a009b8285b75a2c53ea7b7323cc91785c59635e98b38256e80a2514"},{"version":"c5e286949fb1b24d3395196df616ec5f9090c2569534e48d1aa86e14308f6f2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"908b7b3f2c71140accb3333fa49485ce1ad10ac4e14faab10ed51c0c28c68782",{"version":"637c70ab565be71168064142fdc7fde5a58ab95425066d3c8a6c3c592ca7167d","signature":"5baac7ee5e50c4c52bf4905d5cf4f735c939053555b99a56d1b743630788f665"},"2178789bd22566bcaa973006fa541e2c70d5698b5c099831828c9a1ec141802d","6fa0ea6916329d3aa5c6056e13512e1757edecbce26dae1e8e5a3334e81fbf93",{"version":"dae66bf6a17992ce4aaa4a16b8d8c590e84c396a341cb70ddf61ac2fe710e089","signature":"173c629dcaca1da42db9c0a508d079657fcc0cc56db24103f8a8171294902ff1"},"e7a672c4cf7f2314673b2fded201b122b6b4eda779709e2cb235531e8fac004f",{"version":"fa8dbed00530fb4114906cd93f7fb55512c8eb9551d2f2e9796c69a4da4b594f","impliedFormat":1},"3871ce03ddb2e068e59178c70a88d8830c1cf28ac243d585e267aee1fb5f0bc7","ad5ea69c890012a5b61d4cad41a2d1c2bf581a023eb58290c5ea86554184bae3","da11ab55563abce966b549bc9121f74f71d0aa0f0ad86f74c93d7304634c7007","6958e241f880588015372a690454d0f7c0727b78e0a9882f493e2ac0fada857e","fceca4d896e6fd11de25ba760ff482c087c3a2150da1d841b8092bf8e1dd812c",{"version":"7a732b03d5a0bd9c071d5887794887796b8a5cc30decef607a565735d8cdfc0d","signature":"f1983b8ef21692b453a2ef5ee21b5f8e5c32fea1e87bc833c55c781c109e45bf"},{"version":"c2d4dfa9bb5bbafa31b4423a78c2df02ccb51ad3f4abe7dcbbfaeb8dcf2cb82f","signature":"90b39c231c33d05240cbaabcfc21d94f68e05b5d9d2e972b644363be2133bebe"},{"version":"94adbb305113a8e6572989713200d1eba425e7a01443f1d02f4bd9a66f7f4fa3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3f8860ee39a7c1ca7824f29d948eb3f2160caa4cf4b1df9380a9b9b0c1ea184f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a13bfc666d310b2b552c8a2af2d17c2f8ac8c4ad431eae9f9e961a1fb988ca04","signature":"a7c717bceaf09367324737ee4b73cf87e7c45ef1447547eca4853e516478c7cb"},{"version":"cbcd7e3639eae69860983466b671c160570ff8bef3295eb9d9cf3a918f7c3924","signature":"22039e2144d09a04262d19b5f0e4237ee40bec8322b5c9574403a9103af52044"},"2b7bd4c530f8df99a7c513289d15cc3d919182a3e47a509f7dd66f7c0c618c64","700a699bc316498b27b98820c837965a737debebb4fee5d0a027e95d3c4a1925",{"version":"b61da04f747568084ac75ba893c009197a7a0bb511ce6e8ea11ec3727b1e0bff","signature":"659c6cddd4e661edcbf460b40c7b690f346714057fd0faf27d1400d95cb6a398"},{"version":"5e92985539c56d5b665b392fd3883c103e0a83b63a79955d940547f494a87f27","signature":"d273813f1a71341c5f482788561acb719f12c65fcafb4f36423a6d409856d472"},"1e36aa6fd246d7240a3598e917647e1d2ca0380a1b7bb3b8e3945cb26941b031","205f7ac530c6e5712a640fe3b0dd9f29296ace25043f7179ec1adb56882a1c37",{"version":"5e9142cb5f88305d5b5db601df9fe78244144630b03316f24b123c2bdaec5cd8","signature":"72a4b4bcd25bb33acac0c8d83f0d4d198714a06e03c49046690f380b9736998f"},{"version":"735334eab2d97fbdb987695b8ce10bb987dc318c3dd3f76f65a2f955bc4dce45","signature":"eea48c3b3a4a380ccfec9ac95ea1f6535cc57b17efa2d13009895942af52555c"},{"version":"b6fa86562861ef430157dbc9d6913461f6bb416c58b7052efa28a7fd503a2e59","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"34817c134a9a64cb3564c424f056a13554d6c40d31af05be7ea6b28cd9d0ac53",{"version":"90453ef456c9284945d132c4d1c29753bcc06b0b7b1df7910768f748d4630160","signature":"d4d3b854dee0def611af8377422b5caef70f3b8c2c2d10ee3bb9ffb97d51cd45"},{"version":"404d9825e0fe3cc10db20060d068fd4f33c85c245ec9d2f99a20d05b02291b20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"354afe485d131f817329e133ea768376707f9e4041d68975ba6f8b6eb2deca05","0d0fb8169becb3c35ffb1069d105e59d36e1152bfac10d47d122129c8b6ac89a",{"version":"af2b2a730449f22a36e31020631b3f73fb89a0f41fcec38cae6026bc91ffa8f2","signature":"a93511a9ba3c3a239d6d17527c51c2b2a75c994c354029b2d3512e321980e4a9"},{"version":"cf9666636c6b695a0188d6fe4e8441f685cf76f4639552360a084ae53ebf8eb3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8a77be0564f98c1e1f8f1c0ca31bd9ec2d4cf20b1c780dbfdf4cd81d954148c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc4ffeecc189d198af9ce492abed824b47bff7e7e6f8ec739a0eccc849836e4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"598e1063a09bc7bbf1bc527cd19769aadf213b151d89921fafd9eb6c74121fc7","39ca1c24e657b1083a1798a005f0b4c498c547d400b627ef388ed8498d334e22","9b05dbef22d051726098dcbc6490886790bf7bdb93aa9f8a46403fabd59128cd","3f4784aa9fcc39fc0986a29e1066a510ba747012e13a944828b737a0ac9d890c",{"version":"2d0006e2c2a094ea0fabc4465b2cab0d7e8f5e785b3dda2961c2242257908b6e","signature":"a82c92852eb3872216a45757430fb88588440285e6f17c1bb864abe9f209fcd9"},{"version":"2f1a45e754761c4c17af1ecbd707a35ef9421ddb2daf244d1237aa929f919ba1","signature":"a76cde90a90b5582bffaa8aecdbdef0ee7d82667c57cad2c076404a3bcb741b8"},{"version":"5ebf1bcfa735477bf05c2a72f05efa171db37d28e39a690cc57d28447e09b070","signature":"ff96e4d1e720fdea29de66b9f495391d4c8c6b20fa4db88964df688d5a8538d4"},{"version":"73a0ee6395819b063df4b148211985f2e1442945c1a057204cf4cf6281760dc3","affectsGlobalScope":true,"impliedFormat":1},{"version":"d05d8c67116dceafc62e691c47ac89f8f10cf7313cd1b2fb4fe801c2bf1bb1a7","impliedFormat":1},{"version":"3c5bb5207df7095882400323d692957e90ec17323ccff5fd5f29a1ecf3b165d0","impliedFormat":1},{"version":"ed79d26639d1d98ab19d6f419180e5abe2f7fc6c194877d809282813888c98b5","signature":"46713144a8e07e24962b43c73b40a4f4b16e696eb52b8b519876fcd1f5e6eaf3"},"31152f7b9d390e7fc7d92db8ac3934a2f189432dd8cefa237ceb51667511535a","26a7fc2c9efa90591c196a780ebb5940a3cfd7a74245698b2c0e648986755e76",{"version":"ef20d9124a12e824c20d04362452ecbbdc6c9f3de2c3c4b231222870b9586e27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"02ae185acd25001f4af91e9f275661d7d284ca994374867cc564ddf22f8a6082","535fb697e71bce5739129ef269f852ba83a2eea358ce8ca090f4b1cc905af9bb",{"version":"27cefa9a8df763b7c4e3abc76cb9867d1ddad908ac8c8d1e2bb32c3838616d4a","signature":"ea71b9399fcc1d3c46ec554ae15f397c40bf146ca2d9a58374cfb7116c343ab1"},"d1520fdce7489a3ad57359fab13c79ddc0a2a6d743940358a4dd3ad8d959fb38","c65bec5967ebb52be456a4fb70ac4cd92ffd671aaae4661cde2062fe3117fb7f",{"version":"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5","impliedFormat":1},{"version":"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec","impliedFormat":1},{"version":"08b2fae7b0f553ad9f79faec864b179fc58bc172e295a70943e8585dd85f600c","impliedFormat":1},{"version":"f12edf1672a94c578eca32216839604f1e1c16b40a1896198deabf99c882b340","impliedFormat":1},{"version":"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a","impliedFormat":1},{"version":"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53","impliedFormat":1},{"version":"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c","impliedFormat":1},{"version":"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904","impliedFormat":1},{"version":"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b","impliedFormat":1},{"version":"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262","impliedFormat":1},{"version":"4fdb529707247a1a917a4626bfb6a293d52cd8ee57ccf03830ec91d39d606d6d","impliedFormat":1},{"version":"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206","impliedFormat":1},{"version":"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e","impliedFormat":1},{"version":"8e6427dd1a4321b0857499739c641b98657ea6dc7cc9a02c9b2c25a845c3c8e6","impliedFormat":1},{"version":"58da08d1fe876c79c47dcf88be37c5c3fab55d97b34c8c09a666599a2191208d","impliedFormat":1},{"version":"e770447d49d5c7ee25f80ccfff0f95003e08bf1147d039f0e8320d95d882c76b","signature":"399eb8b682bd93241cc96cb483306f8634ba94bc17ddb123e9106088240e9c7c"},{"version":"15ba1669f8cb8433a7a7b40422f81fed4f7e037e3cd4ca65b7b4af0434a43560","signature":"4f83f97fe204009c8bbad58d06e956970062930bd694b7ecd88d13a6f85f7e3a"},{"version":"a18970969188e47a48af09738dde83579f9c85bfd731675b671c1f32c5bdc134","signature":"f6c3f2c52494a1c44f58bc28dc1f8f89c7e3b0d005a5c3bb8789f82131996dd5"},"68ec8a37a3f7ce830a6be8e0ed448f8907f638e02a22a12a0f76a900d9f7b258",{"version":"ca74e09cccdadf9ed4a596b473809cf922455e3ce70bdb5d8f20a6acd4a83209","signature":"5b1d834d8c16cc056da6e18683b1fa5bc036290b0a0cdeba3b315826c73eab88"},{"version":"93df3c7e9e01564c615aa025fd2ea367cf2371731eb04f531a5f2c040bde1748","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d9380c6a61635f8a7019cd889f3c9edbb47a2664847d029f935e632f35fa7b09","e70993be79de2ffc2132f91126903db8573e68b0f5318ec48eec97a5e09c5f8c","9eed204f26aed45ba513a001aaa78dffd4bf0194ed42fb59fa4a5b48dc382767",{"version":"bbed8132ccf8ed24e09b7a0c103afe746ec74c3f6d497676ce9a2b09a8e0e4ad","signature":"e9805c8a045ade45cf5dda8406be734ed77bce51fe25e6a431345e403964f502"},{"version":"1b046683cc56fca31919c8cfc9a7b47796d986b2df18c1e55615f7f67a464c0a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"954e64b65c8632c8e6c602f86ddb7a855b541f719153c52586da47df81740592","8e67d08427faa2cd614ffde8279aca632928a75610fab7f0e80eea0481c3ffa0",{"version":"4dd230e5ffb901e4d715a7507fb671f3510cbcb3781701177e09efce8cf30c6f","signature":"7a3b7f911a6906b2fd8d38f7347bc751ff290914c35f2998438f2985dcea418b"},{"version":"d79917970e2012fea644dd1c3d00e7499579d4adfdd3628bc4d4153c2fa38d2e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"eaf4a58ef586c168ad73bf0bf2e4fc7b50b0207046cb03e70f064e23cc7410a2","08e69d30c063db86ea2221adf2282b1c118723cec16131cbd40024f3f43e5a90",{"version":"80ccde47e35b2546400135ffc69d55feb65a4a473964a40cdeda39bcfa10aef9","signature":"2cc35ee4dd1c4f9d97475451cc25f443a692f68f9bc47fb0044f009e356da599"},{"version":"a2d8e5740b1d7e274651ad4e68fd99942d7b33d67adce2d3ff8b976d12327840","signature":"d14e729f535d0e6d801090b439ff6f73f8ae7d713de7468a36d5989f0f10f19a"},{"version":"1d57813ffd927563821c58c16a5a7c35d350415b2b8de5978b370c78b8a750ff","signature":"d5d64072f36683f1af5cdbc66e7ac58d839b6b2d99cee1b0e96df9f4413640a2"},{"version":"70fe6d07a4bad7a73b493a4bfbe2c5b501167449f0e95e3a896261e08d647b67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7d2792a15bf4bcd948e330c3cb747a075d137db57ac53adc6900f69009dd8978","signature":"fb6fdfe7ee4e1c16d6bc8b3c8da0d22ebd365981b6c4dfe881b391328d68f220"},{"version":"75e64a7fcef4db0c9ff13acc31c53cce109194012351733ce9833347e0a8e518","signature":"a97e6b4712135857efbdd73004c551d3a71d65d6b8a9d8f661f608a47b607cf3"},{"version":"a24154a3954030448c58433c23ca4f6d78e763a3af035de3d9633cc9158d7038","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"72fc4be3f73ed954fa04a52ebc5975c56b5f13c4265392191c95028ec27daab5","signature":"13a8b4fdf45f95814460c5001fc04194f85ca7055d460a9f852eed3fbd5c2293"},"d3a3a8fa4cac4860d3fabd83dbbe072bd0db08b6dfc5447fbc3f65a480bbb896",{"version":"773df341514640879d77b0b24b636e6a8ccae2e88bbb09cee7383274046eab2e","signature":"3f3e3ab94baceade05836e0805fd32550fc1cad12d3d31a2fcae6d56882ac2f8"},{"version":"2d96663076cc7fea06c11a0165be63c11b533672c6d02ef361bd86f8394ecdb2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95f053ad6f9e8f22fb9a0309e14a768302ff5f8072b9bc24b8decdcbdaaad0ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc4cc5cacfa347d15035093ecc8a2c650968fd9208de260c8c141749d1797d23","signature":"8efda6ec7129eb4762df1d2b2a593fe59c69f1a2d5696d6d7bddeff50c24b17d"},"de7f6eb89010bc7d22b76bfd8d01ebdf803df6bdf7e7b7528d2705f74c401e58","745615f591324c1ce4fd8a905b5af838474e781548807dff21154e64b51e945d","a1a2e508046cbc9709255c938bf9935cbffa6cfe006cb5bb7f36b9f4c5a3a2db","d09eaa9c4d651a351d0ed84a88a22b35bd41f307ff7aa0fc356a2b7ac41ccf25",{"version":"005ce56f0d10ed61656324f72713a4e920f12a8656def94bb1735e9cd8392ad5","signature":"484482bbf35c97458c170dd84777adcd87d6e9fcbeac3ed86ba79eaeb8cc7968"},{"version":"265c9ae2b7a62781e57de439be00ccb1b8693156cfb98a0618ba6c5c54596e42","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34330c5f52442c69dd7c50d7a95912d87b85cc01be135026a5d7ac060b184464","signature":"720e771373458011bd56c0c6bbeea34302eea42ccf08c8a6b5840a338e7e93b9"},{"version":"e7f0547a22cdcb3e5d9b0fd91191cc2dba8f75a2694eeb4d45a9ddf2a8352960","signature":"c1f5f74ae95ba44d64781ed79486fe7192478040d82d787876f44bc7e77418b2"},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"eb9271b3c585ea9dc7b19b906a921bf93f30f22330408ffec6df6a22057f3296","impliedFormat":1},{"version":"aa4a927d0c7239dff845a64e676c71aeed2bbda89a7fb486baab22eb7688ba1d","impliedFormat":1},{"version":"340a990742a00862049b378aaa482b5bb8323d443c799dded51ce711f4f8eb51","impliedFormat":1},{"version":"89eeeebbc612a079c6e7ebe0bde08e06fbc46cfeaebf6157ea3051ed55967b10","impliedFormat":1},{"version":"4c72f66622e266b542fb097f4d1fe88eb858b88b98414a13ef3dd901109e03a1","impliedFormat":1},{"version":"15d8dcd70d6cc6c75476a75ea83c53df1115bdd551c73ef2168a9b4a4bd55a51","impliedFormat":1},{"version":"2acad3ae616a9fb5a8c3d4d7bb5edb11d1d0102372ee939e7fc64359fec4046e","impliedFormat":1},{"version":"c812eabb7d2e13c8e72e216208448f92341a4094dd107cbb0bdb2cb23d1a83e7","impliedFormat":1},{"version":"f734b58ea162765ff4d4a36f671ee06da898921e985a2064510f4925ec1ed062","affectsGlobalScope":true,"impliedFormat":1},{"version":"9619b4a3db123eee6912ce9cbeae535739a1b1736dbbc224a697a2a98fee560c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9c5c84c449a3d74e417343410ba9f1bd8bfeb32abd16945a1b3d0592ded31bc8","impliedFormat":1},{"version":"a7f09d2aaf994dbfd872eda4f2411d619217b04dbe0916202304e7a3d4b0f5f8","impliedFormat":1},{"version":"86ac756569f83cf0571646c916b546634652e92a775e964304912ecafa81dc42","impliedFormat":99},{"version":"a7f23fecdccf1504dae27c359db676d0a1fbaaeb400b55959078924e4c3a4992","impliedFormat":1},{"version":"bee66a62aa1da254412bb2c3c8c1a0dd12efea0722d35cc6ea7b5fdaa6778fd1","impliedFormat":1},{"version":"05d80364872e31465f8a1eaf2697e4fc418f78aa336f4cea68620a23f1379f6f","impliedFormat":1},{"version":"7345ba3b9eb2182d8cdc4c961b62847c3c9918985179ddefd5ca58a80d8b9e6a","impliedFormat":1},{"version":"81c4a0e6de3d5674ec3a721e04b3eb3244180bda86a22c4185ecac0e3f051cd8","impliedFormat":1},{"version":"39975a01d837394bcac2559639e88ecdc4cfd22433327b46ea6f78eb2c584813","impliedFormat":1},{"version":"7261cabedede09ebfd50e135af40be34f76fb9dbc617e129eaec21b00161ae86","impliedFormat":1},{"version":"ea554794a0d4136c5c6ea8f59ae894c3c0848b17848468a63ed5d3a307e148ae","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"9b048390bcffe88c023a4cd742a720b41d4cd7df83bc9270e6f2339bf38de278","affectsGlobalScope":true,"impliedFormat":1},{"version":"c60b14c297cc569c648ddaea70bc1540903b7f4da416edd46687e88a543515a1","impliedFormat":1},{"version":"efcdea26e9115d5c05b3f4c5827fe3b32b4fef1b59dbd67f529c6cb685c7d9c4","impliedFormat":1},{"version":"bb0c361fd2b4bdabbf1307f1a61fd14c953f2692fa642391f93276f2df41de50","impliedFormat":1},{"version":"90588fb5ef85f4a8a4234e8062eb97bd3c8114dfb86a0c67f62685969222da8b","impliedFormat":1},{"version":"6ce50ada4bc9d2ad69927dce35cead36da337a618de0a2daaaeeafe38c692597","impliedFormat":1},{"version":"5fbc333346d28f290d42ac81cf16e454fd3947c6e524384dfd3ce59d4ac3af04","impliedFormat":1},{"version":"072163fdea42ece03bd323b907f5d6acf575a34a9dac4620e517e4378d773d0d","impliedFormat":1},{"version":"df6251bd4b5fad52759bfe96e8ab8f2ce625d0b6739b825209b263729a9c321e","impliedFormat":1},{"version":"846068dbe466864be6e2cae9993a4e3ac492a5cb05a36d5ce36e98690fde41f4","impliedFormat":1},{"version":"94c8c60f751015c8f38923e0d1ae32dd4780b572660123fa087b0cf9884a68a8","impliedFormat":1},{"version":"db8747c785df161ef65237bac36a7716168e5ebf18976ab16fd2fff69cf9c6ce","impliedFormat":1},{"version":"3085abdf921a6d225ad037c89eb2ba26a4c3b2c262f842dd3061949d1969b784","impliedFormat":1},{"version":"8e8f7b36675be31c4e9538529c30a552538c42ff866ba59fe70f23ba18479c5a","impliedFormat":1},{"version":"1fe8b45c1564eca8b1bd27d427d193ea8c1a5d64f7144a5a64665d5d0f27a9e4","impliedFormat":1},{"version":"a03c6f93651e458531f223d52eac1a12f2aee8adc2cbc4b4154a3fe515984e5c","impliedFormat":1},{"version":"8d05dbd747569cb1b0cc2ec1018a3378c47d803de0e7d34f7e12909ff48bb437","impliedFormat":1},{"version":"1afb31819f4b7d04f4089d575acd30854a4cc614baea960066f7cc5755e9efcd","impliedFormat":1},{"version":"35cc30df63b9fa7c9d3637ef315eb5f21f5b0dc0f982c736cad20d39e29b579c","impliedFormat":1},{"version":"c5b47653a15ec7c0bde956e77e5ca103ddc180d40eb4b311e4a024ef7c668fb0","impliedFormat":1},{"version":"0528a80462b04f2f2ad8bee604fe9db235db6a359d1208f370a236e23fc0b1e0","impliedFormat":1},{"version":"94153ca0b430f575f45a5e07d66771dc5ab331af7791691855ba3499958c4e49","impliedFormat":1},{"version":"dd361fe00d3033451e4a43c9eaeafcd1b9b6777adfbc8b8f91d63ea56818c31c","impliedFormat":1},{"version":"b86720947f763bbb869c2b183f8e58bca9fa089ed8f9c5a1574b2bea18cfbc02","impliedFormat":1},{"version":"fb7e20b94d23d989fa7c7d20fccebef31c1ef2d3d9ca179cadba6516e4e918ad","impliedFormat":1},{"version":"8326f735a1f0d2b4ad20539cda4e0d2e7c5fc0b534e3c0d503d5ed20a5711009","impliedFormat":1},{"version":"8d720cd4ee809af1d81f4ce88f02168568d5fded574d89875afd8fe7afd9549e","impliedFormat":1},{"version":"df87c2628c5567fd71dc0b765c845b0cbfef61e7c2e56961ac527bfb615ea639","impliedFormat":1},{"version":"659a83f1dd901de4198c9c2aa70e4a46a9bd0c41ce8a42ee26f2dbff5e86b1f3","impliedFormat":1},{"version":"1db5c2491eebd894eb9be03408601cddfe1b08357d021aeb86c3fb6c329a7843","impliedFormat":1},{"version":"224f85b48786de61fb0b018fbea89620ebec6289179daa78ed33c0f83014fc75","impliedFormat":1},{"version":"05fbfcb5c5c247a8b8a1d97dd8557c78ead2fff524f0b6380b4ac9d3e35249fb","impliedFormat":1},{"version":"322f70408b4e1f550ecc411869707764d8b28da3608e4422587630b366daf9de","impliedFormat":1},{"version":"c4ef9e9e0fcb14b52c97ce847fb26a446b7d668d9db98a7de915a22c46f44c37","impliedFormat":1},{"version":"0e447b14e81b5b3e5d83cbea58b734850f78fb883f810e46d3dedba1a5124658","impliedFormat":1},{"version":"b16a6680ef4108fb2982b1d47e7ce36a8b2c382cf76b3e1b500de70f0a62fdff","impliedFormat":1},{"version":"929939785efdef0b6781b7d3a7098238ea3af41be010f18d6627fd061b6c9edf","impliedFormat":1},{"version":"fca68ac3b92725dbf3dac3f9fbc80775b66d2a9c642e75595a4a11a2095b3c9a","impliedFormat":1},{"version":"245d13141d7f9ec6edd36b14844b247e0680950c1c3289774d431cbbd47e714e","impliedFormat":1},{"version":"4326dc453ff5bf36ad778e93b7021cdd9abcfc4efe75a5c04032324f404af558","impliedFormat":1},{"version":"27b47fbd2f2d0d3cd44b8c7231c800f8528949cc56f421093e2b829d6976f173","impliedFormat":1},{"version":"d5426c0e36296daf07cf2f38227907469c33a53473d9c2721d21dc515c5724df","impliedFormat":1},{"version":"cc03a3e284393b02fdb646931e8576f6dbe839a249d172eb3397adec80559450","impliedFormat":1},{"version":"3d94a259051acf8acd2108cee57ad58fee7f7b278de76a7a5746f0656eecbff6","impliedFormat":1},{"version":"fc745bebefc96e2a518a2d559af6850626cada22a75f794fd40a17aae11e2d54","impliedFormat":1},{"version":"199d42a358b3b73312d499dd04d9f855bf8ad492452765de4ef80b8cb6871cd3","impliedFormat":1},{"version":"eafa048ffaf72cdb64fa1d0dae49aa91280a7bb94e0b034883ae48cec27a04d7","impliedFormat":1},{"version":"593bcf66433eff881c9abb75d2e55a7403c57905aa61d818a616bb3c7f076b49","impliedFormat":1},{"version":"3f3526aea8d29f0c53f8fb99201c770c87c357b5e87349aca8494bfd0c145c26","impliedFormat":1},{"version":"6ee92d844e5a1c0eb562d110676a3a17f00d2cd2ea2aaaff0a98d7881b9a4041","impliedFormat":1},{"version":"b9dc36d1f7c5c2350feafb55c090127104e59b7d2a20729b286dab00d70e283d","impliedFormat":1},{"version":"45d3f1d53fa99783a5e3c29debb065d6060d0db650a6a1055308a8619bd6b263","impliedFormat":1},{"version":"a14febaf38fd75a88620a0808732cf9841afc403da2dc3de7a6fc9a49d36bdbc","impliedFormat":1},{"version":"6052522a593f094cfee0e99c76312a229cf2d49ac2e75095af83813ec9f4b109","impliedFormat":1},{"version":"a0ceb6ce93981581494bae078b971b17e36b67502a36a056966940377517091d","impliedFormat":1},{"version":"a63ce903dd08c662702e33700a3d28ca66ed21ac0591e1dbf4a0b309ae80e690","impliedFormat":1},{"version":"2b63d2725550866e0f2b56b2394ce001ebf1145cb4b04dc9daa29d73867b878c","impliedFormat":1},{"version":"e885933b92f26fa3204403999eddc61651cd3109faf8bffa4f6b6e558b0ab2fa","impliedFormat":1},{"version":"22338cc18afb909a95a6c55417f1a67db99badecbac1710a963f0bf63c952124","impliedFormat":1},{"version":"e61b31fd5fd627c73da6041d201c0bbd721170288381f09055cad4fcb2ad327b","impliedFormat":1},{"version":"6e2d2b63c278fd1c8dd54da2328622c964f50afa62978ed1a73ccd85e99a4fc7","impliedFormat":1},{"version":"e151e41c82004cf09b7ea863f591348c9035e0f7a69d4189cbac89cc9611b89d","impliedFormat":1},{"version":"dedf4655c327e9c5294a63d75764946308700825e8d8c1d4318a10602581cd6c","impliedFormat":1},{"version":"b83ffe71adbac91c5596133251e5ec0c9e6664017ee5b776841effe93de8f466","impliedFormat":1},{"version":"61ecf051972c69e7c992bab9cf74c511ecba51b273c4e1590574d97a542bd4ea","impliedFormat":1},{"version":"068f5afbae92a20a5fcd9cfce76f7b90de2c59a952396b5da225b61f95a1d60a","impliedFormat":1},{"version":"18d97e6b17d1196d88d4deb0e37d8edb7fbdd102ae8a5681f03e15030b6b2fd4","impliedFormat":1},{"version":"d7ded5d2060ac6a4404e6001a46d5a704e3f325f95e2cb0dc055ea05404c9cf6","impliedFormat":1},{"version":"99c88ea4f93e883d10c04961dbf37c403c4f3c8444948b86effec0bf52176d0e","impliedFormat":1},{"version":"e88f3729fcc3d38d2a1b3cdcbd773d13d72ea3bdf4d0c0c784818e3bfbe7998d","impliedFormat":1},{"version":"f25b1264b694a647593b0a9a044a267098aaf249d646981a7f0503b8bb185352","impliedFormat":1},{"version":"964d0862660f8e46675c83793f42ab2af336f3d6106dee966a4053d5dc433063","impliedFormat":1},{"version":"292ad4203c181f33beb9eb8fe7c6aaae29f62163793278a7ffc2fcc0d0dbed19","impliedFormat":1},{"version":"aa8e5ac3f73eede931d5da74ef1797c174b00854ac701ead5c4a7d6ce4a49029","impliedFormat":1},{"version":"f1a4ca3688d951daa2d7740da5a0827fa34d4a7709eed7b8225215986ee87108","impliedFormat":1},{"version":"08e159b5ef9d14bdd329457c5cbe181e84f13c4ff2546a24b9eb9129b0c71c46","impliedFormat":1},{"version":"f8453a3fe0fe49ab718357120bec2b8205e15eb91ff62eada60a4780458fa91e","impliedFormat":1},{"version":"06f186bb9a6408ef8563dbf17d53cbe23e68422518b49b96afac732844ddbaa1","impliedFormat":1},{"version":"525f9c06245b5b43b1237cfd757396fd7fd8090e5d6a4ded758c7ce17a04bf42","impliedFormat":1},{"version":"e46b752c48b3aec77516d23b5cbc0b85df78c740c058a822b43a32c958e468f0","impliedFormat":1},{"version":"f693b1fce39951823f128590c6c837b70f844b6d3746ef778b7fae7f1340338a","impliedFormat":1},{"version":"bc264419318f0b174b5dabdd465e1eddb82f872e899b6c696c67217b346e958c","impliedFormat":1},{"version":"6046bffaa17bbb55ffd62926a966a7badce21b27d6239ba0b569b8266bedaf19","impliedFormat":1},{"version":"9376cce4d849f1d6ad2cb0048807c77cfeb78cee6e29b61dcfe74c7ab2980e18","impliedFormat":1},{"version":"2e0dc55ea1ade444d285576a4ed7915834d4a87f71b147c38afdb877ebb0ad2d","impliedFormat":1},{"version":"4edfc4848068bf58016856dfeb27341c15679884575e1a501e2389a1fea5c579","impliedFormat":1},{"version":"0c3d7a094ef401b3c36c8e3d88382a7e7a8b1e4f702769eba861d03db559876b","impliedFormat":1},{"version":"1a3b915d24b2a26df000ef55ed356028dec11ff54f7e93a5c095c313d7016e1c","impliedFormat":1},{"version":"7e3a4800683a39375bc99f0d53b21328b0a0377ab7cbb732c564ca7ca04d9b37","impliedFormat":1},{"version":"b1b5e35575486918e155ef02d995598be2b5d8e729f857f8309ba0b76e14d833","impliedFormat":1},{"version":"8d87de8b839a017541ec1baec68292ddbcdfad0f2f3b5f2ae8abd61f06105cbd","impliedFormat":1},{"version":"7cb0d946957daea11f78a31b85de435e00bcd8964eba66d3e8056ba9d14b9c55","impliedFormat":1},{"version":"b3e441cdb9d9e55e6e120052fe8bf2a8b5e5a46287f21d5bc39561594574e1a9","impliedFormat":1},{"version":"0870e8eb0527c044e844a1d83127f020aa7f79048218a62b2875e818355f8cb2","impliedFormat":1},{"version":"38400b70ac70600c632ad498df2d956ed8ca6c6774dfb0ef69a2d35a9450df7a","impliedFormat":1},{"version":"abab86c01d001d0cc410c7aee59168eb09bdb7d6d9d39d3c0081b36235e2824f","impliedFormat":1},{"version":"7ae39872b4f4d38b9df079cce4223e999754eb3b3f90e4e46b978b29e72c419e","impliedFormat":1},{"version":"dc0f3099379383bf14f2263c7987584e81b6d9b60259c9e31390455ca0619dba","impliedFormat":1},{"version":"6dd704b0ba0131eb9e707aeedc39be6a224b4669544e518217a75eb7f5dd65c2","impliedFormat":1},{"version":"6effa89f483e5c83c0e0063df5f1d8b006d9d0f1de7eed2233886642424dc8fb","impliedFormat":1},{"version":"5c6dc17513298b4daac99bf8e88ad4e4a504310cf69a0cf3cffefa5912b85234","impliedFormat":1},{"version":"d43130c35762a80da2299f8b59a4321b6e64acfb0b11a36183379b4c7b83314b","impliedFormat":1},{"version":"6bf44b890824799af8e20c0387ffa987e890fac5c5954a3a7352351eefe55d5d","impliedFormat":1},{"version":"e61999c06ae79ec587c2e7db514a024d85732b32ee2c997bf4a1ceb2b561c611","impliedFormat":1},{"version":"aecd29a5bc49b1de6b933344e9c96384cd098162c46873673ffa1408e6195c52","impliedFormat":1},{"version":"f83afa274e0f11860c6609198ecca220f5df60690923b990ca06cae21771016e","impliedFormat":1},{"version":"af31f37264ea5d5349eec50786ceca75c572ed3be91bdd7cb428fdd8cd14b17c","impliedFormat":1},{"version":"86d01647c3c215e53729aa2cb15d7bcc2b049088bc76bdb5e04a0bf25f97c386","impliedFormat":1},{"version":"9d3173cf740b742d1048d8ab20469060a2b5e2d426f8ff7df36042e6829c4aa8","impliedFormat":1},{"version":"f1063f0e6ca22a9fae0c0338768b03911c954b8e6ad4fff5381cc6a964b34324","impliedFormat":1},{"version":"4f85d12a28937e950b123e5385448a3bce0f04dccbca7ceb8aef351ffeccb228","impliedFormat":1},{"version":"85e4673ec8507aef18afd4a9acfae0294bdfaac29458ede0b8b56f5a63738486","impliedFormat":1},{"version":"40683566071340b03c74d0a4ffa84d49fedb181a691ce04c97e11b231a7deee4","impliedFormat":1},{"version":"81c8ab81daa2286241ad27468d6fc7ad3ecc62da04b18b77ce9b9b437f6b0863","impliedFormat":1},{"version":"268755fe3b7dd5ca84fc043de1502c56c5cf8ef70271c017964a9dff8af94a7f","impliedFormat":1},{"version":"8e56db8febfe127a9142435940c9a5a1ad17ddb2b2a6d8e9e8984785a76db1fd","impliedFormat":1},{"version":"f1efa458a3f630de51e30c823a8e1109eadf8562b8b90c764ef1fd989329bcaf","impliedFormat":1},{"version":"1ea64554b23db011171f7e0dd59d006b239fd4ec2e7e8b31ecf995528de79423","impliedFormat":1},{"version":"46788ee6b4670d904a54d56fe9e3bb308ab4c4ba01a435d39d8beaebf85f1a54","impliedFormat":1},{"version":"f4f6e61f620861b576f466e8af34e6064a997aa93ad62593c0c3f51489784e5c","impliedFormat":1},{"version":"f92fe945f94fee5c2811d6ee81b1751a1f1970b29063907d48067f1c2389bc3b","signature":"2825a8ae716e344c54428f3916a5fb98e4f7b7d4f521e0aa40a6781766e2b2a5"},{"version":"3974befffa7647e5d975081c15016cda5f16062c8957af8d5b93b85bd6b57b21","signature":"8364a4867aade4b7b8e12b3116edc4c0cc374833476df15a0cbbe7b147bb1387"},{"version":"da094bbe2ba0c875d680fa8957a0b4056d806ed8093c4eb84f1d1319bc148924","signature":"2ecfec679572556d5739697241ee12faf6d1c088a64eb646f358d6b908201893"},{"version":"884b3c4b6de733bea0363994edfdbc08f23168c3819ee92eacf9ee2ff38b9e31","signature":"8b18201daa2caa4d6dad664291f923d8607cf8211ebd0dec3986e400f02376b4"},{"version":"d61b3b8b5d54ffbc1159015019c05472841f9b12287ad1eb0febb9d50b3fcf2b","signature":"7bd1aae3ca5e15b45dc603fad958b8d228f09e8c43ad9a4efdc70c7b3f96fc35"},{"version":"4b9b77c14bfa8102fcb57b14ffe92dbff3b513a8c4ba62893ab009fbd4c73647","signature":"9d2c9cbb279702e44a3ea7fe24bfe19cf27352d4cbe4882bbe5d521d27c9741e"},{"version":"aee88de82317641d6391f0686ca4acceedfaae5ade43d00dfdbb2e32e83870b1","signature":"979a61915ecd6734d45f9ab06a423a5b75cac28c23c512c838c10e333ff88a02"},{"version":"9a889402f27da6ba13bcaf7e0731fa06758e971c0d4ed730d6b46f08d9a05f34","signature":"f9d6f6e5c3e8a1dbf9499c426fb4d97386c7aa5b205662a4777f9289ef9152ab"},{"version":"f6534bed93400a60ab02368c4a698062e31ce5ad4eefd0f4994c2385ae83c54b","signature":"9c2f866be60bdff85a59bf2cd9b85041d63bfc369560cf59b88d7a95c6072f28"},{"version":"b6ced0b0b07feec87098d3eb446bdf772cc268ee3ac4230a4069e61dbf75cfe4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bb040b86775b25fc0848d43e3cc03e1b5cf4f00c40cbd350dbce010c06404df1","signature":"6d46cfeb3c048590784e9c099f0dfb8de954141c9fbf25f6d9ec87981ebc6fe7"},{"version":"d20eaead87726a364899203678c3e81614bfc368630e7a38ed0008acf7f7c1f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e892e40c4a0fc631c78d3480f7edc5c1cd469ea0b8edd5e21951ba39c996b889","signature":"703090444b11f1b3ff7c9d90d1f20f336bdd927ab54747e57150d42e86e1f62a"},{"version":"5a2958fdf63b7d83f8d734d08ab6975b2a66defa7d7ee4988c0abfec0881b3a3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"7859b822b52969cf5421d8d07df464fabd68c67579733fd51cc994c6f3b9452e",{"version":"05a0701aab09b3b50154c4469670a6af40d716c1bd84258ab88c4486efccc2de","signature":"cd7eee6f9641bca037731468d9b1012d11858efb65ccb7a23e35377d824b2a4b"},{"version":"cef9a872724202d022975121422e03878a38b6c4a78977b7e277733a2ed5151f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2776443e3c5ce498f62ac5661d0e35884afea55b0d3f6f9306f8ffb97b35e9fc","signature":"c049b08ee071ee35f8623f69360d9b11a4e78f6f903a9601e9f76346ff07ffc4"},{"version":"18981392c502332d353be793e0eee6b4b71b92c4cc159879c76c0e412b50166f","signature":"85a5f8ec84196d475ea68d0239a7ee678d96c40274f16d0e940937166e2f9fa7"},"091c011d67fce1f188bb8c7474775ffe3275ddbc9837bd5fb5ffa26fd70a1cd8","ede33324139612cc144cb9ab0658d31f633fbbf6e5654b4867ad17964e494463",{"version":"90bdcbca0e0bf6c2b0bbb00673a340a12647ee9e9c0b7ffa08c1cbee51683f6a","signature":"081dc84b2cfc91d910771f621446c86f99ef16cba361a195bc5b6415671e3940"},{"version":"c3a6e46122a15e372681357101c151aefc21040e65611ab4edb7366b6694b2ea","signature":"84a5f8d870d0e3a83ea81b7fdd41940ea8af6ad244f7b5a41347a696ce8ee863"},{"version":"5c6fad27889b29555ec4270fa34f6d318520fc36e3c368efef6988c2240f943d","signature":"e569b4fa591b1a8f2e8bc65df628ea7b8314ea9e9fc4de877bae1db7073f82d3"},{"version":"75d850624f64a90b0709ea1dc2742d4b189c106098f94125af7cdfcbc9db0852","signature":"2bb5816f801dc67b86549dcb0c662be56b248c5dd1662c1042691c7329148f98"},{"version":"4e127bfcc9d5d93ab906153cc602cfcc13188847639ded607d4e83fd57838e12","signature":"f91e8b73b979d76dc70cf739261a03907357f4b464c2c2b57aa1da3ef60840c4"},{"version":"43fbe80cee30066d6ade0e64b13f0987cd6b23946ec6265728fa2adb27146000","signature":"017bbf6636858e6e607294afca49a452e39c06854a04aa20ad3850defd0025b2"},{"version":"f527325efcfb6f6a0d9253f1af0e0a32ada4f9c5cac06ca5689927515225c440","signature":"4105893a2351efe282a947f23f959ba55f8f46aa72d55829d362261b1429b42f"},{"version":"753dc412c871f3fdc65bfea46ee79b435fabb41509238f866f6249d44f7c1dcd","signature":"c286b503f750f73cbf22d1031c189fb27e7d8a93ef017dc18d17bbe37fd5dd9b"},{"version":"4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","impliedFormat":1},"324a1e17354169e427d4c5b39f9fa33866c2474b364fb66bcfe0c4e46dd0de08",{"version":"d6ad5279f8d296ebb50264aabcbd50a5296edfef7f23ae4c159579028935d119","signature":"48cf5a32fd77ba27774c29824c8d1bf27cfe16081169b1a013e0874292c3709b"},{"version":"6f2d007923eb835494e65dcc1034da47cf8e60aef0554d323273a59b8b8c2f86","signature":"bcb9686b97930d312e851d879aa0ceb39656e4e49b07b8aef72ec0eae03cb376"},{"version":"406af28178f025030a57332cb2a36516048ecab7acf102b84f1c1a84f09d77fa","signature":"aaeb521b6f9317f1358efeed044f7b8c9da2de643c9c444c86efa4b5974707ac"},"00e2552694e9ca66c48d911ae3a46b5ec592ceaf1aa11fc892a11ea68e8f61b4",{"version":"4e028b7fdb99d3de652792b19e13bca74e46bc452c80b9272d77b1ac2f138aaa","signature":"385f9da32da6c7c156c4c273f1e08ab2fbe264bb424469850b65c9e6ffa9c9ed"},{"version":"fa8896708f7c899af3f718f77f46489b8d3efd15204184f74b878992dd516270","signature":"e89614e458edec1676ac424f0a893a6e87bf5bf38d34a8758b3e4823f0d2b48f"},{"version":"85b1d0061b1268cbaa7efeba177d96bac002d38d7acdffd7a023decbaab2ef7f","signature":"0fafd7d6cde3e37c7c4045f298b00355745dcb2147c0e8d0f1240fba867274de"},{"version":"bb5660a80ad6edc1e4a7831bdc38cb4f70adbf718846aa3bb936a27b62d742d6","signature":"e017494fbef6d93da248b22d25416f95d8412cb4a4e8cb12c7e64495f16de3ec"},{"version":"91d4ebbf20c7ce05ce56b901d34ac84f18c5de49cdcc8b4e2e79416bf5863a52","signature":"00aed0049902c591b92c49af96b5a8d1b3e202604017f34241bf72cf89f80756"},{"version":"f7cde16e51986d5a1361c4d7e36cb8f8089acd60e7b43b7c0cb7ec9d3c58bbb8","signature":"fbfd3cb405fce3aab2cc8b6c68371f03f340b5bedfb22d1a0b46408ca184aa4b"},{"version":"0f55704e7fce1025a74958ce04d7d099a3605ab1ba105c63b7fde02139a17eef","signature":"69652f240dac09436bdaa4cedabd63700a279aaa035b43ade48742fbe5b37d08"},{"version":"4d8d7e049c7a369a07b41963903b7041bd8c88560b55af2b4b6c4fd7be645cd5","impliedFormat":1},{"version":"83f6b233e11c9f2855f7f318f608570e9a45db007ae924278e7a581d7ef99b35","impliedFormat":1},{"version":"7296b6e2f2accbd8ed583ac9fa90c88d7d50ca2ff95a04ce2959d46e6cf7696c","signature":"1fc7a196b7cb9628c96283d1c55177082524e81f5e607404a5ca9a1ff53e45e4"},{"version":"231a843f95abff5b70bf76ded015c4d7c0ff006544d27c9747471a495743c2ed","signature":"1507e471793e1215912dd1ab92c0797ae9259ebf7fd0f3146e2bcee42b776bc8"},{"version":"deb4df42f640706245617d22c38500d0d24e34689f405224004477c47b30a287","signature":"84225d531b0d673c7dee0a7abb7592e937c207fb0393e85d8e9808505e415642"},{"version":"05196723f295c088f92bd93f9edf2f9d72a0e6fb7a468f55aa270214519857a5","signature":"1c7c3f06b140f7f31f69c3f2a6f87659c1a487c552bc733f9de6ed963779a17a"},{"version":"0684c0f5805f8c75a0613dbf6e8d386e93721218828baed8a419dad06db0266d","signature":"a0b2ed7ed78ffb63bdb8c45c49596bf2792676cc3c527c599be027c2d772c840"},{"version":"60baecf2ee0b36e0b6f81536d77774d964bde3e3975c00328874e3b564a97e9e","signature":"9813abe08f8dc60f627701e1576bfbbe8498fa01840b3500ef120ffbe3ece69b"},{"version":"babf8f17c539cd8e5309393275eb17fa2a6790a848f9b6736e3e75b69ca12ae6","signature":"ee79b4e030d4b005413044e47295b78001ccb4849995c4dc59e42e65c509f21a"},{"version":"fea6e19848834ac2c8fa97416625b380176f0fda1396eef00f84d136af989050","signature":"d703ffb3cf86f2e1cf7460554b6fc0a3a0eada0040fc48aafeacca14bffb7ebc"},{"version":"cb262ae73b7b864a9cc5e62142dc12600f5afddafa458e6c26218259d5ff67d7","signature":"433e57f0df48dbb4612309330aee7b075651c0ba5d29c483b17bd92e81cad910"},{"version":"f060e1946eb32ff62b101bbac21a6cd02835440c0892554566d0dde5d4838cec","signature":"036240f98ae8d5e07ad6f648996ff0630d6112eaee5b53fc3b309a1acd7c0721"},{"version":"a8f8ddbbd5a595a3a45b89108072fd7c11afcc5df839f3b2d234ce93bf5ba511","signature":"621d7479105eaf0b7002459dd4a7746134df8f621a6d9e62ac5c69f4b73902af"},{"version":"25a9445362108d35961825c730d1385aa52655c253523603fe3a514699a08308","signature":"0a82088daf1f69f93a6f03b7ba430d6605a8b48febb578e7ecd2c3564b8d235a"},{"version":"9b015283fd545bc487ff27b205f5f87ad9257e30df4e0137deb8260228fd97c6","signature":"6e0962e848047bf57be651109d9ee3d4e499277d114392e6efdbf60a3863fec6"},{"version":"b92e316d7caef01a7d96aae2fc81bac3411d81aa08c08369fdd79b79052d0804","signature":"a15f6b3477115f885bb24033267a6e06e889bbc393c5ae977513f0ef2c29efdc"},{"version":"883e6a16350e6a237822deb193859ba6f80f68b5bc63d37932eb5a222afabcfb","signature":"4e4f390cf28f71013350ead1ba25290872b936b31244feb495c7da040c655c54"},{"version":"20eeac8a87d7e85f13f2ce118073cec7275054be646bd47823f1e9cc8951ed4d","signature":"9f2d02e65e22f5bc32f727fb091f17315fe58a8792d8280ec59ab072272e3376"},{"version":"88f2985b43e7af3d4dbcba54e609861fcd28cef3ee74ca4d54e82917a9165b30","signature":"2e54daabbe58c730286e014d2bfe4a80b6d533a2bc9c5ab6fb1e3e654d3a4872"},{"version":"417c3d98d4efb99cd7f3c683c2caf02ae28758f18fed72ae0389aecfdab29878","signature":"2a39da52aed89ee43bf5dcadf72fc7ab5d16b8dee17ff890bf0ad3b72a0320c0"},{"version":"15e9ece6b9f5f2ce89f2ec8a96bc9303b35f07374b94005eb2443efaa0c6a49a","signature":"46676fa7ca6a5b6552a61d40d41f41eebc81cf838c14933cddd35203d298b874"},{"version":"0aaaaf9e39d6225f0fcce6949faf7254a473de642dd96f1b6cf5501b87347546","signature":"1f8e872ea16e6ef3029e47f25725a22c286734fcb4a88ea2e13c437e905f0c21"},{"version":"735af8f14d6e3866b5863742bec289903bdde848ba8754fb628afb54af74d5bf","signature":"6058d5942879388147f8aa5e9c2713af05d0f1680d7ba91d1999b97dc6b5ca01"},"2e4dabbebd31ee7206edd6a4ae429f487df1734e92df23dd037839f212c3e9d1","b27d139dd9c71966306fabe2f545b928f610ffa6b0c75d84a9dded090f66a422","98ebfeb0805807ae08415404af1b664e76353e70e5e71e6f086c7ba264b76fba",{"version":"fd79331caf4d1c981f82d179a8f8ee1f5f9db5485b5960d2ac5252ff91ba195f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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"},"9c5d15efba9e25be56ae6fe11057225c74316fb0c90e7d34b81ec8f633a9810e",{"version":"b02c8a9ebc617e95159a1d928fce2fbc345f3e9ccc9f7f6684195d8f8d9bab5e","signature":"45e169847975d5baedaaa5fbe3da4bc92db0b90a305f2536491b7a4a2d262341"},"963c32aa76aee7050bf4b5749bec7c775046f03bcd791d54992b3345f7b80e2e","9e473cec8b5dbb77baf8db593da0a943701f1edca3b3b1ac81af9ce178dac9cd",{"version":"10ea972b401fc77b7e35429345f02bb02dde34fc9d7d1fc3232a187f5b52facf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"38cac01302000992d41adce1e4ea4a985de9d38fc126618de525a524eb000fef","signature":"a7c0fbde9afd282c949332844e31c9d8af5b35f443e19d2237afbc862e74fe86"},{"version":"520a429b325443370aa841a2ba8ed537c6b43385fac88a7eb0a542cc1d48af55","signature":"bf343a08f58ac65036d01c5f5fdf07b339340f9afbb4cda14c89aa6582c3c2d7"},{"version":"90ec5f47910a26321c68ec7e45c5912b61aa8c0b6bae4e36dfd35c6274376ea9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1ab7dfe2e40a14457c44447646438563ffbf187e60a175f258af4189bb414e9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0d29ff571f05d9f1c9ecabd53c10cb9bfcaa313b3b64612593bec64745c4d224","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cd5944f91eaf3e04d8c66d1c7c44508f932ae86fc033403193a81a0e3a95e53b","signature":"2630a9fc3e9a2205f1df08e9d39ac89290da5a35ab782d1504364baa70c67104"},{"version":"3ab2b6455439badb3d984aef6d2519029dd8595f19f614654072798269598876","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8f3b78fbcee37a708acd2a86f1c22645cf34b444cfd7459be341415228f4b63","signature":"14fe776ea9f72086fe119d5df096c39513d6bdd3ba1615b8d9f5cbce35933f54"},{"version":"f14799e6e43275054eb876159fdcb6c55b4e76808911ffdc9f81a2e3e5baa564","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"aeabfd5da8290656189b20d20600d0df6381dc3881c381b815807e9fb745f5d7","3dc95ec98d2db484ccbaa31a47c2633bd619a4d86fd655739ed248f081f49f07",{"version":"6b32764a0410770ea2d05907024bd8ef5044fcc5ee257ddaac24e5a09de8ac91","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9ac8c2249f0a97698a155031023e87eaa74c871229e36b51c3c83fd1a0bc92d8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35d7cacd428e674f89a928ed99f34ddc7c36958b395627a9196a8ba22618a29a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69a81ee2b65949687067f23e7d5ca0fec2110620ff93ca8b92bc56740340b760","signature":"bc160a1badf1d1e0e9b92666b4848dfdc428b553d00ac5d2304b7ad80ac4cbea"},"5f44765c75000e8fea925fba6c2ba696386103cab9d813e72070cdcf45e1f804",{"version":"b11dcc6b3a1e92851fa7626c01c543833b96a9f37a29d80de6f11b320b626c9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"63e34e9210807a4d1af057003031a6689dd3295f8f2524ae7597ab27f326335c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad7e4e17c67808e01a84f46591f2d4b763173ecc84a4863a727b7058b5786945","signature":"b7902bfba5bc8b901152f1ed5f8d9c2cbf2ba2790d351e5e5d61e00bdebbc624"},{"version":"3e5b494cda4d0ac2bce024928f0eacc77f8d9e4ac3d51eadde967ce542efc27e","signature":"b94b0bc72e5a26387c9314dc4f72106bddc0e5056469dcf4a43a351c1523c49c"},{"version":"1c230948c8120cd778cb258a0b70eec899b458db58229de16a2951a7ebdc57b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fef5a24cad5c4948eb776b00fa114e2560ce3dded3abc27db2e2b2b66832331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2cef84bf00cbdb452fdc5d8ecfe7b8c0aa3fa788bdc4ad8961e2e636530dbb60","impliedFormat":99},{"version":"9e2f5dc3da9d83bf4a0a9e5d39d8c9918482d586e0c403a44021e4ae7662697e","impliedFormat":99},{"version":"799003c0ab928582fca04977f47b8d85b43a8de610f4eef0ad2d069fbb9f9399","impliedFormat":99},{"version":"a62e448d3f09fee63ec1230acb23fb54f8f6ccf8d6f0001c7b94fd51594b7c9b","impliedFormat":99},{"version":"5366549884acc57185eeeb64561c2060af008230a8ea645f048f747cfac6549c","impliedFormat":99},{"version":"cd3229a2e4ca10207178e22f215c8e196c837254dd34ee440612a2a14993ffc2","impliedFormat":99},{"version":"73b7e3d5300ad64f9231f5bb145fca4892574d85e2d1a015ce095628f16915ca","impliedFormat":99},{"version":"42944c2dd3115e25cb0aa77aa05fe9e3d0f8a3b4ac251896cc680b7be41ad60c","impliedFormat":99},{"version":"7ed8ed496092801dd5f25f39af223ebeddc97bd64a7d9a5621f790dbc836eebe","impliedFormat":99},{"version":"abc549dfea982be25e0379cdcb6ef2aa6716b0013c11d8d6b14c814cc955d7c8","impliedFormat":99},{"version":"0e6ba4003cfaa90748b69ed0dcc9f99299d1af70f4bd835a872e52705b0c850c","impliedFormat":99},{"version":"3decd4c8e355126e76c9a43cc7ae08017fbcf1d766b204d696ccdfa5128de1ac","impliedFormat":99},{"version":"40410f51558d0b3d635584333fbba6b58b4b7f74037f59a08c0577828539637e","impliedFormat":99},{"version":"096350f9446ef08832b935d4a97c66f74a9133faebd90a40a12abd5f8bc7eab2","impliedFormat":99},{"version":"26baad6aa356ef75b2e1ee150ef6325988be9700bba14249f9e8d0f66bb36087","impliedFormat":99},{"version":"237dd4f246265a3efb18c3d40f54f98336ba2c329a9f9e30b4bb0f1a27baf324","impliedFormat":99},{"version":"3fa62b954262157916a65b3dd57faf6cfec7544579e673204da30eba00852543","impliedFormat":99},{"version":"8ab0b13972c8018bd18d49236b8c08448a38c823e28f5620b3ef0b43ff521589","impliedFormat":99},{"version":"065dff95b2b9cd6f5f7404222ecdbd371f15c22b844b731eb32286540f499d2a","impliedFormat":99},{"version":"d1c03f0339b8514b7d5420e075684e6b1dfb9d6c27a7fc6fbb09bc3f25fc7764","impliedFormat":99},{"version":"f8900ddf4a4944cad4a81de965c4761094758ee39bfe24198668c397caf5db3a","impliedFormat":99},{"version":"b2b8376bb1ac24155cde89574c32edfdefdb926845d9426ab52815421b3d19a1","impliedFormat":99},{"version":"4e805f78a8acff48feea70836df232a6db887b2e376492666f6b70985fb706fd","impliedFormat":99},{"version":"cfc5a66408fb9a7dd136ec2afd50a3eced54baa3321c473ee4d29046e761a3a2","impliedFormat":99},{"version":"594201c616c318b7f3149a912abd8d6bdf338d765b7bcbde86bca2e66b144606","impliedFormat":99},{"version":"35c190fc184fc2fdca132bb8aad00ac84819f135428b0e906c3e599c74125d24","impliedFormat":99},{"version":"8f567d63ab28f074ab2be3ddc2da27107de8022488f4a3bd91609752045bb612","impliedFormat":99},{"version":"f6c7ae690e2d224a310c8f967cdb415d8c7c55791a30c00da30e0c19ef4def49","impliedFormat":99},{"version":"f0ee7287284a844f4d04b80ae7a955b12cb50f85fb0021a78cc7f20a90459823","impliedFormat":99},{"version":"956e7dae5b888d02ec65dfe4113b541042cd2c70f96f6b9de0a5465bdf9565fe","impliedFormat":99},{"version":"75722639ade81b4d9a9a7f67f9cee2abbb68c52367322fe4fcd51949dbf60706","impliedFormat":99},{"version":"f90d3104f554535c4bfcf9d429e41318563c40b3b7e0827c0975624722546514","impliedFormat":99},{"version":"c61b9b3f161eb34fe5ed7fd3bb84f0774d74445928a06e9089ddc0a152f2a016","impliedFormat":99},{"version":"17268b7c5aed233ecafd22ac3e751c3aafc101b7ff982de8617bf19fafb7058e","impliedFormat":99},{"version":"bf6060c0585e76d2670629cc4c592e1dd938ac356e974916ced7f46587ba8181","impliedFormat":99},{"version":"dfe68566e870382e203fbf082e3e094b3d3d6712a3b6bf56fe66f69271d27cce","impliedFormat":99},{"version":"f230e4b9b3a7c27975a8af6131b08f6b17505e829073a3faa6ebff4a163090aa","impliedFormat":99},{"version":"e89ae5ee53771a98d89105723fc4dc73205bd96bfd2a784597b5ec6c2ed35abb","impliedFormat":99},{"version":"7f79b823d4b2a1fdee3a799a6a46792a21e4400ed0c2f45f1e1a9bb8de21d18c","impliedFormat":99},{"version":"42b828f21d7b672495a1f538ac49e93ea12da980d07d28999c7eb8dc55f297e5","impliedFormat":99},{"version":"35e7486045f8a29b25ec8adad02823bb82e0876fcce76228bc683e0da0726e98","impliedFormat":99},{"version":"a43f4964d97d0feeb6b33944f750707dbdb539e1c9c3a0496c40789a90d7e0d9","impliedFormat":99},{"version":"8dc6b9b1f772053689d3b298f089ffedf29ee93be2eead0d9c07d77e68aad9e4","impliedFormat":99},{"version":"b44e0ca6cba9c3f98a1b277e93dedcc31990c57c08f0fb37c29eb929afae3a49","impliedFormat":99},{"version":"e236485fde7c092508a177ccfef03ba15ec72ac697b50e241802d68dd99c5f73","impliedFormat":99},{"version":"b7ea66bd111288844e2d0cfb12abc02242af0786a83ddde14abc156a7f80d500","impliedFormat":99},{"version":"83fd9ba9e82b881f410b69b30d4fa9e41b1b6e445e4d7c7eaec836d4cc5a5712","impliedFormat":99},{"version":"ba2733b454a9756b8207e110896e4889859d4a23581e54fdd659f09267a63ecd","impliedFormat":99},{"version":"d94b9c4da700bf7e011fbd442c54b5c88a52db58bc71bb69db67f46a1c525320","impliedFormat":99},{"version":"bb19a13fddc505d633b9d08340c851a16638a3a2c6ba4971d538908b0cce8671","impliedFormat":99},{"version":"da588a0328ea4fa648563415d1ee4cad0587e3d1e1d29cf54d761fbe83ed9670","impliedFormat":99},{"version":"673f71885a78cdf431dd29b801ef2f811a2c793b415c44e64a55489ad010f6e2","impliedFormat":99},{"version":"4c0c16f5d60671e0654e560a94cc549a858a5fb9397a072d3f9935b3068be740","impliedFormat":99},{"version":"2cb58371baa22dbaa02e2abfc40b5640f00e7ed203e70e97fa20226a776b2e16","impliedFormat":99},{"version":"29db777661a60ea3a85cd21ce29b0bd877bb44f52cd583f9f3f7580ee08d4fd1","impliedFormat":99},{"version":"cdf79d50d5ca102a6ccd1ead392b0f5ebcb9b6c8b230e4f4931f0fab8b6ff3c4","impliedFormat":99},{"version":"6e21729eb1f94c93f99d1c13492b6e835e5c2d2ba552693c1c699f0e34d1fa1d","impliedFormat":99},{"version":"8267fbe09febe68384466808d3feaf055ebb7b15903728d23e7fb4c01949148b","impliedFormat":99},{"version":"54e45f5f4f7684c5c49d3e6367ba73c55c69f82973ecf7aca793a86bea5a99af","impliedFormat":99},{"version":"55a9664e49c8e8db27d8eb413749957eb222485b91b1148840a73e065ef6c028","impliedFormat":99},{"version":"af7945629e88f161817436aeab27906b947cea60102066575eb31071b4f84168","impliedFormat":99},{"version":"847b7eec4ffc81b7eaa1bcb473fd5da4aa73ab7e56944df3caf7d284317e95f3","impliedFormat":99},{"version":"5dd262cbb746c2a4d0a26f09369b3ede4a1a36e15c272adfd0289c47cef81ad7","impliedFormat":99},{"version":"677e4d55a1353f1b83ad68faffbdd91ffa7dbc34d67b1e91e88d3ac71b88be0b","impliedFormat":99},{"version":"21ba9b6a4c6dfc6dc403884d34dec961eeb965a4e0c99521ba2b3f9929e26b75","impliedFormat":99},{"version":"452a373c93cae3a20fb8f8309ac48b40cb2a33f05c3d54b090582ce3b8ae96c1","impliedFormat":99},{"version":"112f147e1f4b44b4a4f186cefcae4e58c49d6a0a61faacf7a12f55694b9f2232","impliedFormat":99},{"version":"c293793b601177e19a4230a9ecdaa167e6a44c93147da549941eb8e154510f4f","impliedFormat":99},{"version":"82ece43251947dd304e6f5dbfaf8b97588e5676ddf0bc0fc1a6a861aaa3eaf7c","impliedFormat":99},{"version":"f67c58823afbf2590f2c239d09a46aba9d3456327eee05b593c48ee248758ce0","impliedFormat":99},{"version":"e2647503f56e5c6d41b256af0b17ad3b98455cd8b852ba7336221af5fe99d805","impliedFormat":99},{"version":"c2d12e71e905f9ae80895201ae4b52b0082716d3177d794799f0140c3bbdb65c","impliedFormat":99},{"version":"668eaa98e8d54dc5a22d7a66d659a47f0b152e7b109f798cb295a3c3dd817dbb","impliedFormat":99},{"version":"81d447a1f248a2345a89673774ca673e79da5df8e25c6fd6bffb495d3b704362","impliedFormat":99},{"version":"900f1f5341752c6c2824ea871ae941d60be1359793a0284e56abcf277955a511","impliedFormat":99},{"version":"00c8b548f04329a012af189dfd8e3f3ddd8d4fb187f4fd22fdeba5e1eb740d92","impliedFormat":99},{"version":"919ea552c5b52ac5c8303a96dd7357986a2597de5760416468550b659113bad6","impliedFormat":99},{"version":"8255114fec0d6189524bf52d90580a2fce40bdac621215e562aa5f5b058fea33","impliedFormat":99},{"version":"6020d3e324725ee474aa4637005d2449eb8bce66e8aaf85163d683df86384dd0","impliedFormat":99},{"version":"d95e4069a535a118c22ac66a8b018818f9f74ca7000c8eac977dceaf752d0f95","impliedFormat":99},{"version":"5d8097f4e2588d7912d82772ac6f05ee6def5b738f5e4605f2e9bb24d26b4e86","impliedFormat":99},{"version":"6703ee0cb2405fc9e98a8835e4266ed4131fd25c31bcc0c302e66e9b05271eee","impliedFormat":99},{"version":"4b83d4ffdcb29aa6562749ca797b76a3b914d80f54819c6a08f1014fb6841623","impliedFormat":99},{"version":"41ecfbc96066dc0d03f1a8139e28b4b3297bc231257d27a7c5796d017962a438","impliedFormat":99},{"version":"46e060979c9bb359578744342c37b843529c284e20ebc219bd71d5fbc04b3704","impliedFormat":99},{"version":"720b258293ffe0939688db7b4729d24f64809718157b14ae50fb9e2397c69fbc","impliedFormat":99},{"version":"1273795a90591a538b11c91a7840b1facbb5b6d500146cc055324a16f58c0346","impliedFormat":99},{"version":"a06814aa3f18bf501a7bbd1cf3ad9b1fb090cb89b19375debf6ac3b906ad9090","impliedFormat":99},{"version":"785afd3f604c75ef24a65c0f2ce4b3ce2137f941773c201842abaa7385b12e3b","impliedFormat":99},{"version":"ee3bfff84df83f9e3cf0ec85aff97df52fc57e740e41fd4780de1cb3f9e73780","impliedFormat":99},{"version":"2025d7779d9356a37ed4142da93898d39f811d9c5937f8c107f44ab2344e87b7","impliedFormat":99},{"version":"471b3d02d1af08c6b58a9a2ff5c85da205910f782a7783d7a1f59dcb681ee8ea","impliedFormat":99},{"version":"e1902decb3f07a58e9be70b5136e3d715997025e0f0f20cf7e2610363f38ee04","impliedFormat":99},{"version":"323156c80e3ac6175f4b75952ed871ead30b58b9ec463131e368e572d89777be","impliedFormat":99},{"version":"7c54717447fdfa134e43c6f1a71f8ae4e955538f9e59a8bbd60eb65f5bb965e6","impliedFormat":99},{"version":"0d153b01d0b1e33ad2b8c778765c3f3539a3ffaa595dc3e9d53d91cfe5615f11","impliedFormat":99},{"version":"0ef8dbf7f717c2d8912df768687073cda1d7ec73ce2861fa8ee30ea8c15455e7","impliedFormat":99},{"version":"355ae3751ad1378804c850b212bbfed1bb68af9e4cde0cde857b86c6cbbe2140","impliedFormat":99},{"version":"06b02b230ad18789680a5d286d55d566451973456fa33b63ddff6c9b2c2ab41c","impliedFormat":99},{"version":"971f0be2884711cdbd2dc522224ba68db24abea620e9089b5432a9ed73dd406c","impliedFormat":99},{"version":"1e45c92c3241e189027db53310d5b3b8d713fad08ca6ec5f8e0734b275b6dd76","impliedFormat":99},{"version":"a374180a9dc60b15b4fea69423ae9d8e3cdfdf604e8cb314325db23a2a8e3cf9","impliedFormat":99},{"version":"acc82e49137ccc0be7e523164613032cd0a35a08b38721138a228926539a33f8","impliedFormat":99},{"version":"990951a94433c2efe6e42266ebd096f63154115a37c1f4e5bd37bee57bbd3563","impliedFormat":99},{"version":"b65b675fe2b1ad0d621ce5ad94e9fbdbd16b17e8afebe2863361e0d028dc73fc","impliedFormat":99},{"version":"1bc87b80ef30a78d0cec6f6c56ad41b68a8f03d30a7052d1a0f1e946f5eb5150","impliedFormat":99},{"version":"79ace3491ac2d2585e2e3748827466f99d0fe06acfb8cfd7bb5ac6e272d9b742","impliedFormat":99},{"version":"f51bf6581de40babf85946efb37bf4bab0a5357b46b4a0cf904278f3b8234350","impliedFormat":99},{"version":"c728002a759d8ec6bccb10eed56184e86aeff0a762c1555b62b5d0fa9d1f7d64","impliedFormat":99},{"version":"586f94e07a295f3d02f847f9e0e47dbf14c16e04ccc172b011b3f4774a28aaea","impliedFormat":99},{"version":"cfe1a0f4ed2df36a2c65ea6bc235dbb8cf6e6c25feb6629989f1fa51210b32e7","impliedFormat":99},{"version":"d94d06e50f58be0a417ebc0336be0c51e5aeb06cbb59ae7d5d4cba95e4948418","impliedFormat":99},{"version":"02246d22f0fc51c76534d953f606aab7c012d1acdb182f822c8ac8a37926a72c","impliedFormat":99},{"version":"0166e0f095473027f6f8744378f5ac5cb6557e788540fdad76e0abca9eef2567","impliedFormat":99},{"version":"f950a4cec73ccf53ee3c56f117e5c585872bd13328c487cdf7a614246feb075e","impliedFormat":99},{"version":"f325583644b63525d1c4d22825633c220e478411d813f134d5930207cdf8aab3","impliedFormat":99},{"version":"e25a05c0fd866cf73c00a281ea11bb51fa8d2a9955f2edf8a7b8f3081b37c165","impliedFormat":99},{"version":"2c86f279d7db3c024de0f21cd9c8c2c972972f842357016bfbbd86955723b223","impliedFormat":99},{"version":"df6ccc0d7f7324035b05a6294404b310a23b2f07fbbebe1cd298f88647ab8b6d","impliedFormat":99},{"version":"8cfc293b33082003cacbf7856b8b5e2d6dd3bde46abbd575b0c935dc83af4844","impliedFormat":99},{"version":"62391e62217e8a22a4d5f3ff123912bb4d182598e051f31b287096d187cbaea9","impliedFormat":99},{"version":"81895ab68da9cb1656eca90934f01924181d57439e980aa3df8c788488363272","impliedFormat":99},{"version":"cb1ee5692cfe21d8865ab74cc64aeb2f3319f2c2ea2f63cf2662b6319160beee","impliedFormat":99},{"version":"ef6ed27ceed062efa353f3c108dd31d3e4e83e222ed9e18566fa85ed4600e366","impliedFormat":99},{"version":"faa076baad26c7c20856aa86a22c8afd9113f0bc47feeacc680a9e6d4493ea5e","impliedFormat":99},{"version":"782d76ae47ae31c1169c04d93f11e6e13b50c704833517ffeb933516abc4dc12","impliedFormat":99},{"version":"9036dd2d0b09989692fb0eb69b5142647a709aae1f2bfea464701df33758345f","impliedFormat":99},{"version":"14eeb7d5737bc074d1020b7648358ad0488dfb576aa82937e8447586c1b02bd8","impliedFormat":99},{"version":"73b252fae9083ac46b9f2fa376c3a4f5d2c98f5fc0d31922e6d74e7a416f1034","impliedFormat":99},{"version":"0c35ff99747044453c64a4fe3e0e813adc45e67c16d6c47a39cda7d5b2c45764","impliedFormat":99},{"version":"a127363b7f50b5ce89ee98b2faa52a3e7247af32785f937e827e9ee32578d803","impliedFormat":99},{"version":"ae9e8befa5a81361fda14b5c44953b69a6b32abed1e9c62c533230796ba2b39f","impliedFormat":99},{"version":"f3eeac608cb47badfaf2218914776864558c08a392fa626d3a0ad678b0fbfe38","impliedFormat":99},{"version":"2c05477216d0da559ce805e5b5cb8f3c72e1897110886a0fe22808ac37a2f5f6","impliedFormat":99},{"version":"6307d21b4a02a9de0ec25ad7c8a36bfb3a25d38adb1dbe877f7e73595a4a924c","impliedFormat":99},{"version":"cc78721e9ec12b7b62352b8bfa1e37abe055c17965d5fc956d4edccb1bf4f673","impliedFormat":99},{"version":"53565e07ff42ff137d862dd402cd1799785904a50cbd75fbf8402d7ae76fb6b8","impliedFormat":99},{"version":"c8c4e8de61ce90831b7342b6e4800a3e70f4c06eadb17dd743e652ece3562ebd","impliedFormat":99},{"version":"37ed869a9de36bb1ddf343286b5cd0e0afaddd892ba28e82fd652b7ee2c46dec","impliedFormat":99},{"version":"5dccac21bdd7a3a3f399f2a0110bb1bb22a7bb002e3c4a3403f781299faf3f53","impliedFormat":99},{"version":"976ff2cb836f3b64382f2090462966b6b82a059b8d90c4eba54ffa2021e5c150","impliedFormat":99},{"version":"9432e9ba2ed3ef0169d133a2fdb113002be901691ec78ec9d2329c12c16d5065","impliedFormat":99},{"version":"f7e369493bd11921421f51025608f6450675e5d5fba73a1f5617c96072449ab9","impliedFormat":99},{"version":"0919c74e404e0f876c1687425547263ceffe5cc184404492ed2f8deb8a13cbcd","impliedFormat":99},{"version":"7df13a374704470d39a931dd1fa3602a3bd1cadf064115784e4acc3b25e6c24f","impliedFormat":99},{"version":"36944fe70fea641703d40efab3585844c0ed20ce7e783fdcde90bad50bf77f5d","impliedFormat":99},{"version":"cde65d40e64bf0aedba644d8841fba8fecc6f4793d7e4a4364be954bf273ec0c","impliedFormat":99},{"version":"ab9a48af27d31f50da02f40b83b2e8695c4ac28bd446f37d34d5ded0443aed3e","impliedFormat":99},{"version":"0b1a50c36805a5f3be773ea73339750c3619a7ac53c0f441f5e9f1cdfbddc695","impliedFormat":99},{"version":"b85424e3eeb4843556cc1838289e1d3aafc8907b44fad864f228e2abf1af55d4","impliedFormat":99},{"version":"0bdeb9f8d6472b196355591ea4a4313cef5434d24bc79c6e5e733132380b87ea","impliedFormat":99},{"version":"91fe1b91f77a6080c156f0f6af3f6b12524f04604b6e0925432c48f7ef58cfd9","impliedFormat":99},{"version":"9866369eb72b6e77be2a92589c9df9be1232a1a66e96736170819e8a1297b61f","impliedFormat":99},{"version":"e84281e45703810be96251405f8051317362e453f39f26e078cde8967fd2945f","impliedFormat":99},{"version":"0bcb04a160a2a2a934480e3b899b1d2255970b25ffc7408a5d07aaa07baf2878","impliedFormat":99},{"version":"8e3a9c17439b657424fc7e311943dcf9444fbcac73f3b9b72aec2f449a11e203","impliedFormat":99},{"version":"a6c3df80c7c5e8a15e302df97c8a35b1deec48f6a8639110663d6c85ea562fff","impliedFormat":99},{"version":"4c69a93a4645185c445f0050939645592d49f2b8dbc999ff63176c607f3dc319","impliedFormat":99},{"version":"0e2d2919246a4491005fba1612d101a68dad27a5592a77baab1523b2de335cc2","impliedFormat":99},{"version":"c32be5821ff157b2845dacfb257531e932a1161b933e6cd1cd0a4de9e057bdea","impliedFormat":99},{"version":"eb14bc57e220517c752f74ab7c810b72a80632c26eccbd7af690ed9ea7b5ee03","impliedFormat":99},{"version":"ee0de1f85e4fcafe9019c89085cedbde41a22d4492bab87623eed5afb91065ec","impliedFormat":99},{"version":"588b99d933490c59f0ac74e43491ec1b71348b049b1a391f24318b84bdc17b97","impliedFormat":99},{"version":"d78f57a7b922e855a90900275fc93805e07f8cfc7689039840118eb6bf6f0057","impliedFormat":99},{"version":"3c20a3bb0c50c819419f44aa55acc58476dad4754a16884cef06012d02b0722f","impliedFormat":99},{"version":"82c69793fa09d8b58a3589f08c7d16163c566cca5657dcc45deaf5160f2c0e95","impliedFormat":99},{"version":"b89268c927a997e32030d8d8daeb0ee65a7c7db40b167a39296459e114ba7511","impliedFormat":99},{"version":"fb8bc4e79a3b9442dd3e8b1bea89b3e0ad93dd154f94fcb7ca81f511c7c06b65","impliedFormat":99},{"version":"b6c9a2deefb6a57ff68d2a38d33c34407b9939487fc9ee9f32ba3ecf2987a88a","impliedFormat":99},{"version":"f6b371377bab3018dac2bca63e27502ecbd5d06f708ad7e312658d3b5315d948","impliedFormat":99},{"version":"31947dd8f1c8eeb7841e1f139a493a73bd520f90e59a6415375d0d8e6a031f01","impliedFormat":99},{"version":"3a4b1b3e62543a3955e1ad5cddfcc59b25074f722d5dbf7aee1971a43de8acd2","impliedFormat":99},{"version":"19287d6b76288c2814f1633bdd68d2b76748757ffd355e73e41151644e4773d6","impliedFormat":99},{"version":"fc4e6ec7dade5f9d422b153c5d8f6ad074bd9cc4e280415b7dc58fb5c52b5df1","impliedFormat":99},{"version":"3aea973106e1184db82d8880f0ca134388b6cbc420f7309d1c8947b842886349","impliedFormat":99},{"version":"765e278c464923da94dda7c2b281ece92f58981642421ae097862effe2bd30fa","impliedFormat":99},{"version":"de260bed7f7d25593f59e859bd7c7f8c6e6bb87e8686a0fcafa3774cb5ca02d8","impliedFormat":99},{"version":"f9d8848e3c6d82c1e348a9e5cc531e433be58c4ba233a6683a4e9bf6d923a462","impliedFormat":99},{"version":"48a3ae8b6325c87135a210f6d6a7ce15d58417870a2ad78d70858313c47eee99","impliedFormat":99},{"version":"9822da8046d00ef9b8a230345cc163599e58629112081ba55cf4f8d88ba5bd93","impliedFormat":99},{"version":"260a0f4a8a6dc69a2dec8ea672d702629ff7624d5684b29be55cca02a3e42e7e","impliedFormat":99},{"version":"9789d7263d261044cf33f0bb5fd31a2f4ae3a4cc2a010aa45db6f7d01fb019fa","impliedFormat":99},{"version":"d81f0485800e8813d917c2edf184ca3a7fdeada1472cad6dc41e43c37e240800","impliedFormat":99},{"version":"f59c2a64fc652509e0cc56fffb59d7b81f4c7950c7dcfc2da44b681637604797","impliedFormat":99},{"version":"ac0d6f9d09ee9ec076ec3045d20f0d6f5b32300d5a2fa05b5c5a9b6492c0de1f","impliedFormat":99},{"version":"1d8a6497f663251332519c392c6053d5b5e93e5a2189e2669620851b93fbab65","impliedFormat":99},{"version":"52f2d4cea9e3b8e4821b6ca71077ec5f41316d1b3c7d599ef10fd7c8c839ee09","impliedFormat":99},{"version":"013d7f1c5798ac843bcf24e6f3d97efa42c79f038da9cae4fc95ec686b3087ce","impliedFormat":99},{"version":"83b28136beeebb45a635f0179b828e0d0ec9c59330db43060c5958d796e35ddd","impliedFormat":99},{"version":"81c1ea7f9b00460828ef1c92fbbcfa9ff0a7bfcfb2dbfe2510bf7916c914fa75","impliedFormat":99},{"version":"6cf0bf08cc2ffa6d25c7a9852e58f7de9b26122a42380a89105c201e8bde13c8","impliedFormat":99},{"version":"07350c1be768f0446138cf700b47a8aae8e2f6d828310e519bc500200d519a92","impliedFormat":99},{"version":"4253e0bc9530f4c0eec62d1c566350dffef04ab26d0f72befd2ddc08ccb61925","impliedFormat":99},{"version":"9237ce9c67ba997f8cdbc795be7628c1eafefc3317260c38c1e2df4ebd63a62b","impliedFormat":99},{"version":"1ae2b7f6a1352e73754401f16a7894c1335f3fd199acf4c473274243f89c3230","impliedFormat":99},{"version":"ecfe3af749f3c44ab0fa260d7027b067332f0841bcdca1c8db75eb9b1890bbb5","impliedFormat":99},{"version":"94899ca690be8b491a49004460b79426162b218ef26948625fc025cb40a092e9","impliedFormat":99},{"version":"7fd2e48e2ebd92a381e745c7cfe58003969296f7d0cb0109808e6e867bef6a4d","impliedFormat":99},{"version":"98d7fcdd7c0c682528a70f6781f7a00cc0f314b720d1b15996f223c74dc0cf69","impliedFormat":99},{"version":"155e18326afb2fb26a380b480e0c892cc85cc9449537b3346fcf5aaceeb953a8","impliedFormat":99},{"version":"523d1775135260f53f672264937ee0f3dc42a92a39de8bee6c48c7ea60b50b5a","impliedFormat":99},{"version":"e441b9eebbc1284e5d995d99b53ed520b76a87cab512286651c4612d86cd408e","impliedFormat":99},{"version":"f67db9e9b24275680e88888b618e0d6514a40cef9aec2b6ea8eb1de899f97933","impliedFormat":99},{"version":"0968374af7bf8bf67301b89a4fd4bc8594dcb90b16b4be06ee57d26a708bb776","impliedFormat":99},{"version":"f57e6707d035ab89a03797d34faef37deefd3dd90aa17d90de2f33dce46a2c56","impliedFormat":99},{"version":"cc8b559b2cf9380ca72922c64576a43f000275c72042b2af2415ce0fb88d7077","impliedFormat":99},{"version":"1a337ca294c428ba8f2eb01e887b28d080ee4a4307ae87e02e468b1d26af4a74","impliedFormat":99},{"version":"98a667d4585d5b040af90fb5062e31da7c215abcb47521ff57e33f62755fdc17","impliedFormat":99},{"version":"c29e02568f8b68e62b83db2243e4bbacb5ced2d6c8d120e322b56a018d1070b8","impliedFormat":99},{"version":"53c8e58bcea418aa22f5ee013774c08dfe15f0df9625868b7ce5a7201de29785","impliedFormat":99},{"version":"d56ca5a8aa5dd4937a82df98dd930ef154f340d259bcb2980d36c28a47ff2901","impliedFormat":99},{"version":"3d421ded6ae2260cfd45b230eabe38b6c8498a1a35db809384c85ff2bc3ba822","impliedFormat":99},{"version":"ead83f43dcb956f13b924b9e43e7a64380f830efc67439a9e9e479bc985df8f8","impliedFormat":99},{"version":"d04ba54e15442a067fd28679bf18d11ca2162d64f3b0695b9ddfba2b8e1c3b59","impliedFormat":99},{"version":"6b6cced9b26444d621bb62a1b8cb65911c22505fd559411b5a57e699c7aa519e","impliedFormat":99},{"version":"dafc53212e800bc9bbfed7f3a7732ba8f401516b2bc0c71b2f601ae2583a007f","impliedFormat":99},{"version":"940e51654c3c1967f34160a9674e3bf1dc436a5d36c5d7833718aea235e52fda","impliedFormat":99},{"version":"16f2023402fd0a4eeac99edb5d75d3dd8cb4b2f25f46e9bcdaf0c0bd9670e77b","impliedFormat":99},{"version":"9d7765384806b08a522ff85c20184667eec635fdb809736184da23e89533dabd","impliedFormat":99},{"version":"d53593a008e289638eac5b0a0dfbd4296233e395205831367992a87e81eda13b","impliedFormat":99},{"version":"aa0981acabb92a87323aa1579664c293a968138d9377310fde29429e92febbc6","impliedFormat":99},{"version":"796d8fd55590f854e79d3f4181b54f28108e90118314c858726163bb9961e7ae","impliedFormat":99},{"version":"b37e4e4f8f34745d839c334991d9cf227c34c2ed7fb3b297011ddfddf3ac7d68","impliedFormat":99},{"version":"ef55aaa329259ffcb1694dc5d0d688f05e5a37eec2ae34510ef751e9d608b90d","impliedFormat":99},{"version":"264b53b60d27b252258cca58f80b81e143b6299a866402819d5524fd20febd0c","impliedFormat":99},{"version":"714456dfe665ce8b398af312b56b68a927a8a182f8e78dd7c1ef5cfb596ade25","impliedFormat":99},{"version":"27c9ce7c539db9b37ec0d7476b4e9d9ba7439dc41549e466aeadde43746e8390","impliedFormat":99},{"version":"903e813fb2d906d278ab54626f4ade4f43f96f4e636dc66f5aced69d1afb871b","impliedFormat":99},{"version":"c80bc9ee4fa024302308d14084c0f6c3026301db9abbf6789e6b1caf686ce35c","impliedFormat":99},{"version":"8cd470e7936934cb17c70c18a2e03282980d8d047ec08467925a31bf99ec1bf1","impliedFormat":99},{"version":"c09f5d7d8cdee279972790105f90d6adbfb18efb905cf04815ac59d033f7bb7f","impliedFormat":99},{"version":"e92673d9d3c39fff66b14270f144fd32d2ec6fe92e8b2c51e65bd7b4a0e5f355","impliedFormat":99},{"version":"d465455e9f29288b7c879ecd390256571ba306f8b947698f03b1429d6300ff67","impliedFormat":99},{"version":"108b9e022f7dddd5e5ed8165170d65b752fa7b21ced5dd1005ffad3c36242c57","impliedFormat":99},{"version":"1890b77d7c36efdd18174e345b295ece38e66179dae192fad21e8c3642b993a1","impliedFormat":99},{"version":"636f9c9b34b3f33b2258704da1187e271fbf36081a8e22da97be5b53488a9863","impliedFormat":99},{"version":"fa6693c8ad74ce099f2a93ca8d1b0a643dd7f6026f41ba4b244d440d8dd07f03","impliedFormat":99},{"version":"fd76be177303d35dbd29c11de5f935f5d21ad605d34aa4aad9e309ec494b51a2","impliedFormat":99},{"version":"f31af014cf064d7cea0392f02595f09d8cd4b9d06c7794397cf3ddce13111d81","impliedFormat":99},{"version":"d15de8944d6dfb1c8fab88ed1d56947c4ae438b9fcbd9be18f7840b78c9c3bbd","impliedFormat":99},{"version":"040fd90833b34b59436ca6545a00a3b5988f5a95e6cce0a378ddd66bd2cf44f2","impliedFormat":99},{"version":"f332d07979b46f12410417a97153271e1bf5ea11677423718c59010df71a3f2d","impliedFormat":99},{"version":"06911ddbb7160760c75015d2d6fa0f1c0f94d9f0d61265b2d211238b571a3ff2","impliedFormat":99},{"version":"af0612a0e9b7efc543168628fe60a8d3f4d7ae8d97fe257788cb60bdac2459c3","impliedFormat":99},{"version":"9dd05d844e6b99e0a3c8ab8e37bac8f6297d531a844af0738f9b1eaf4aead087","impliedFormat":99},{"version":"5f7be41a9ceed0632c19b7cdb5ad9e07ac19093cbe23a738fe0f1c8c2f27b036","impliedFormat":99},{"version":"b33e84f2148cc81a9afa6d4177a27a1d246fabea3c0cf391aebd3e62eec04f4f","impliedFormat":99},{"version":"5dd273430ddfd576316532f118feafc41f18d5128d7d84e674d98f4a57107384","impliedFormat":99},{"version":"a9c40d74fab8e810c62cfea99a21d09f529fe6a0e60c39353510974c33df980d","impliedFormat":99},{"version":"2665ad2e88b3633b417e176af058b1c20bf5645327a8c4fd4f08e35636b72f9d","impliedFormat":99},{"version":"2321ad799e7ff9c6c6a886dea5ab208d08072a8d33da312f1b9a10ebc888765d","impliedFormat":99},{"version":"8e2f56264cfd71093034fadc1c788d6f46d58036a57e7189e8eda9a7f87eb9d9","impliedFormat":99},{"version":"06deb0a45f5a6dd23244cae8f1ebfa2400ec7de804980f044316d2d9d35a6ce5","impliedFormat":99},{"version":"b27242dd3af2a5548d0c7231db7da63d6373636d6c4e72d9b616adaa2acef7e1","impliedFormat":99},{"version":"e0ee7ba0571b83c53a3d6ec761cf391e7128d8f8f590f8832c28661b73c21b68","impliedFormat":99},{"version":"072bfd97fc61c894ef260723f43a416d49ebd8b703696f647c8322671c598873","impliedFormat":99},{"version":"e70875232f5d5528f1650dd6f5c94a5bed344ecf04bdbb998f7f78a3c1317d02","impliedFormat":99},{"version":"09b103d94e6bf3723cc3642b164dcae50bea1d1f0ab1f5cccc38dfed3fb2beda","impliedFormat":99},{"version":"e3c2cc25f470bea78afb3af5ca6f30759cfd44e4b1212e84657fb36e45a3a1d3","signature":"6e0fa1db91df6484a033340dfafa435f2e98844b66e876a01f963ff84a878646"},{"version":"16bed64f9c23abdf4e18425ab522343d5c8f180214832e5b6d40135d838f7841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e6e6f01cb776da72cb3ca11adf3fa20c16d0f68841c2f550e485376491fe584","signature":"981a1e9bb280cbad4485d10bfb76e890079fcc75cdf11f502bd995e1065d2616"},{"version":"2e0902468e1a220489a3f33dc82d5eff8f70521cc4ad8eb35abee7a792a14a6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca55e9c482d5da0295fc69d21ed6822af32439b9fc3b1fc55ab593deb4a83880","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a0395b4c83044d52eb3954c29d53ccba5aab9acf9765dbe663f8f95783629609","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abb13a376d731db2984464da46235b3dd198602a97e200bf687c9a9a2bb43593","signature":"909a9f6b4a08c0af15d0c0e3cb1f290ccda985ee205dadc0c735d3bd1467d5bf"},{"version":"1652f9e9c84699da6c0050e0818ffbd6093fb9dc67e082bfb5d3df1fc92012b2","signature":"e7ed13b72a29fe7f0c628bb06ed80ba591e327268a8acf0cb66fa1725ce5e930"},{"version":"ccb92614f79729906fc8a9f5c9c18f163c1352d2e549cc49b42c4302fa591f30","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec90498fea3cfaefc1dc5badcfa5d2c8f05a73f96abb856d63707c0cd25351eb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eedbc489481913977cc00e055bd0f493c8ed3115928ca006929d1b353411e722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"9344e6e424dfd647c27be85b5ea478753830f7fb31a74747ce6a373b479d51b2","ba98ffac19abe3f9aa945abea3b81b3ecb435ab243502108b61d6af1a31c00b1",{"version":"73a98325658b7d7509ec766ae0f0b34b9a4d9819b388d4e5b3c74ef8af5af185","signature":"211206a1cc64d3623a54c919fe8e7e8cb70032936ce7dd2625ba2f185580334a"},{"version":"8072581b3b7e9ce43d9553465431ebc422579042d0a644394d018c6803c45918","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"938165e53c76725415bd8152fc8363d628ac4a95e04d4c2884f75349e3d337a9",{"version":"c868f50837eedd81fa9f61bd42de6665f74e7eb7a459135c6a14ac33ddc86798","impliedFormat":1},{"version":"42cf6b642a67b27545981d06932f7e5ef948a68dadf5779cdfa9e052e3a13d76","signature":"41302973852bac2a0d545eb886ea0b819803722d9d6344a011477d235854894a"},{"version":"cb61a5aafcdee23a7ccf20343670924ee6cf6ec6f631b65a3ab249e27d9db542","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d2e1e7f9ea69da6734503f8b7077edde2e9fc91596141725e2beffba76ea2ec3","signature":"0f87709207a3c70d4c4dd8ca7a866e5114b412c6629abcd9f4bac4a7b91495e1"},{"version":"1e3bd35220cea102b5a84d579f9bb1adf4dc20dea714829473bb3aa87499a64d","signature":"230d47db97c6f501ec507c267dbecfcf25a3a8c8c13854734008f4294a0da41e"},{"version":"ad3e839b384c5231de4906ea0d62e778d95f1e46937d9c005487b0897ffc48f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebe9b7b5b1909551f7fe8a5aedab9f4c713b928f5ffeb7b83c9ac876861a74fc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dae983fc2e940a628dd197d10e67ca9cdaa071d87d7018ceb8fa5c8a690eccf0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"eeb5551958a1e9c5493e02cc7a0eaa112e946b7590a018f1bec0e29de91a64de","29e02239f94241d9f26f19f570a5eb688c86873d1e77e43868fd69f6a38e771d","681ab80103a45e835b91035d733228aa210d75cb0cd45355dbe6e72fcbe1806a",{"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":"723d1d05be7e263d358580c9bba607944fdf6e5093e7bf62a2f578754b779390","impliedFormat":99},{"version":"7a59476a46fd4b3e1522e9c6ec6cf436b6d5ab8ac97a17ae867aeb9cdf0371ff","signature":"57f1ad6cd433ecc0e78e4616e780d4db68642604162b65747c70a6142d28e49b"},{"version":"ff3e228e751934dd42a9f05cfd75bccfedfb529eda504ee0c4f0d184da345050","signature":"4a1201a691800bf407a2703017b769c5ce1a53418279b7682e4cde1afc7dc6d9","impliedFormat":99},{"version":"ee70ae40394baf9312c35363c42fa429ba3e037ab10cf767a184ec38d24b5427","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b5b7c87e72f384980ca1d92c4f54d6c30b2f099556e3843588073cfe0a0a893f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f329dfad7970297cbf07ddc8fce2ad4a24e2a3855917c661922ef86eb24dd1f1","impliedFormat":1},{"version":"841784cfa9046a2b3e453d638ea5c3e53680eb8225a45db1c13813f6ea4095e5","affectsGlobalScope":true,"impliedFormat":1},{"version":"646ef1cff0ec3cf8e96adb1848357788f244b217345944c2be2942a62764b771","impliedFormat":1},{"version":"c864801e02e8547ed49024b3a469d6fbf600ee240be6bf413bd6149f26241348","signature":"90ec9100c29e008c3d9194acd818e2cfa6dc6e177154bc8e10c5959aa35619ed"},{"version":"6c8c958cc35f90494284a36edeedf503f3a56a93960016a618a1e587d19d86c4","signature":"2b02e2635e94d92d8a4c1fb05177aa1f9bee04c362dc8600559080aafe963e14","impliedFormat":99},{"version":"9c947051913ac9feed2de4ec57656a9f38ef4bccd22518b765f5877c69894082","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0260838d2473bd7872f0fcef24bdfebc247cdf5c95217670ef50931bf93f2e91","signature":"2affb08b140b8e89210e4b39ed75b00cd5e5ccc3553a80bb3e83514fd2461e7b","impliedFormat":99},{"version":"e9e4ac4ee6a2c612f408e17bfd9bd5398bab08053196d8e8c6cf64d8a7335a51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15128feed70d09b1e4f994cee399f093af7c7c42e224db77f3ace502a457a2f2","signature":"c7108b0b3c30b5aa5fe1fb0c2399dbe7da3e7730cfdd42e7403a0402394bf466","impliedFormat":99},{"version":"3bbf19210a7e08f50ce1518710ba0ffa8e13a0d55d78fdf3cb62cbad44d30e1b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca273ec7d7789662ab7ae9e00a4556a0e42416d6f8a13702a5746b5ea6862061","signature":"dc89f83d1e61d147d010a811cad4539c273b3ed227aabfa8a9a130b4180d2cd0","impliedFormat":99},{"version":"2e71945b350a81ff50fd4e21a3660e7e6055a5cd5691d6d8d867d0e6f10cf313","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5d0e499d96f070dc2607f23d55fe54ac074d1a840b6505e1978f70f57232cf7b","signature":"9e4d212471d83031de81b7c76834be81b4d32b5eb573cda6c61023d1cd5f326f","impliedFormat":99},{"version":"f20e59aa1f8ad6e7dbfb10f7c7147773dab8b5d8e4d59eeeca34944b51e4dd14","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"84daf1ace1a44a36500dd4dbedc8a92e50c4a1c1e935ef732dd18b7e2fb0aaf7","signature":"5cbfab9a555788720d027df70fa580bd727ad40aa2d325eb0b04ec4642f9faf8","impliedFormat":99},{"version":"1784d27f3095418bde9b61739c7ca7bd30b1bf05c95bde803514bfe48ce23f57","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"e62fecd6655ce82858142ac7225caded25ac9b7da81632bec4c7c054983bfc68",{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"8589487932fd916840218fdedbb143741d22216ddf630c70d401ee674c448e1e","impliedFormat":99},"c0fc68a185e7479c68bb3304bf208d87e9d8bbe9a684302d06c40245670cabf1",{"version":"31caf28f1dc08edfe3cb8e7c4011ced09f4bf3f5f725c50e5dc0b5ae573b498a","signature":"04a996928d0f8d5efd87a2990c4f4ce70e00fd0c975971fcbc570df7961daee5"},{"version":"502c011687aee1a48fa221d356f8f2d8eeb035c0706e8f8e9ef0104660cfc51d","signature":"1a734856e43cee0599e8a537f131cbaa1e9290b47f2b496fb504f95e252b8495"},"1a7163e59864fbaa14672752a70c8b38086117e5a14afa00893611dfc2fa803c",{"version":"348b8169a6c19556863ffe85bf1fe1ddb0006affc951bee6eeb7dcb3a2d6eb30","signature":"f12359b22cbaca86f938ddee38c0c33924e768a93042ad939fc2288f2471e5e9"},{"version":"43ee1831235987ca593e76b22b4116009f1ff6fb0e7a3fa6bf1e5df1420fd6dc","signature":"a05af3719b211bbf59b553f0760633dc3095778bb0171502d7bb7342a54d3b15"},"dc4085267e01a46acdc4e014d59e60d40d6acfe0806a041e857ed5b91c688c5f",{"version":"45311c218ffe1c8393be29ebab04527a9167c2e48a5fdb15adc0f22cd541614f","signature":"dfb3bb27e47ca92752033b3171dbe6a1f8e9404b34577d1b16eac221e1745a2a"},{"version":"093616375ac2af574eac9fdfcd18193c3f9394e1b1d4d8c79d2e6068790ac100","signature":"335f66607c072668fef3e0563ad5619a7632e75eef9f85740e84e35523d1ee82"},{"version":"383fc1e3823bc2d2cccdbf51be644b7f2297d6d04190008c1ef7ccf82eed9b76","signature":"77658513755ac8d8ad639e6f969539b6d98cdc9ea85a2eabeb33fc94a839f395"},"eb2ca2e825628da3e61a98cbf8338f9af1da351244346ee4955b09972d60e7c3","63d36b8af9723f5416b3c0c7270f4094ea417909c8196a01775da5ecab082c9c",{"version":"622276611290ba3952b0585e626d99e70ae18719e1eac03fcfd026e8a44cffe3","signature":"257d9dcd4c3e61e552ab3f34ead65de21367da6f92f9d62979a26fd748982849"},"6b43dfa5e9c9d89bcaca0ffe7da88f34e20d760ca158398a3276cef61f738c4c","83a3f59d023ec0384755cb114026dd5bf1bdb12fa59e166330486c05fd6007c2","63d9dc36da9bc05dfdb5ccf23b5738648c073c545320dbb619c6b0b27ce304b3","c01e6f5f2acfc5e3a04850fc1a502350f37b59192124965753a18b8c8c0a3d6a",{"version":"bc7bc237e289f8d435d34601a22322d303d64d497e25d80d555f06f7acc34e4b","signature":"7da246bb1c2b2ce4879114715c5bd7714bef80824031c70e814efa143acfdd51"},"ea148617618060b428a28a47935b7d220bd76a20c909c3f55b15dcc94fee0b89","9a7b469bc32fae75951dc069e760b7945d91829873247f00a5ede47eddfc5d2d","5b8eb6e16859a5d0b869e2607f6510cbdb93ff3b24942edfb5098f2e6b07e773","983793b81b9d3f63b32a2b4aed4cbecdd215d0c00487729c5ee788f9d8a77c13",{"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":"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},"3654ba818fbf4ac2c49aa3dbb050b912277acc71b6d5e4f434720c27a1a68f3d","82783f40f1fb9a547a1c74622a4cf4c671fb927c57165ebcece5cb133a68f4fb","2a7a18a2cc9b4656d9eb1d5f4fd0e3f3466f600c32ea8148643dd8c909bb3476","d9cb0facf05859f0f35707063253d8b55d8fbb565afb642c0edbd72ce77817e1","c0d0f32efeb6b747b535605fbc150723df43935937ad768694508546bb05cfd1",{"version":"c8924b198de81de4222b2f0b171e9262f80bdf62beaabdf8ee7aa13b27245871","signature":"2f5adff38c8a75301b364bad4bd26f79cd3a86bbdd3cbba4541673d903d47b4f"},"0702499aa6384244a89e13df162163ed41949de76518a8580c8044e783876dea",{"version":"9f4f376e778fd1560de1f3afa4b8ba1971bb8bc5f272324ea61e65e15b685f1c","signature":"6b211c08718dabbbcb8d48382a8416b20d9c90e3a7a3a9f8dbb192baa29018dc"},{"version":"b16a9573271f151e37a10543a8faffe67811ac8570d87054108ba01799b73ba9","signature":"c274af3f97f26f9143c42701bf431c06ff0af56cd5b14e86c661a294f335d8db"},{"version":"48f96604f28e1d321ea8c94e7e5cc889f4ab3720d92ed9f412ac7dbc2931a1d9","signature":"0d095606a67e17da85041e7a56c4d15c377ff643b56ca69eef8b42d670748bb2"},"1d899a3b3c762069c87a2363e38fc467d3fc0c17f6d22f98de3a98e1a691540d",{"version":"3f56d8959b17508732d17ec607714398e73009d9eaec652c8fb9d5891d1c7c7e","signature":"b4f0b3be4ce1aab443b18ffd19432c63b332180881e573f620cdf4d257b5426c"},{"version":"61c5a30df40ffd1e5917e3486964faf57b3247e4727ded537fdb0a37ff8a3050","signature":"9a4fce133a99a8e4f1ae6d4d95d1b5d86491c18fc13888f5ee534823e1f1f830"},"0be20053ed11b126b77183542e054ff77548fa8e5910baa789512abd13be724a","fbfef1742d67c1f5379b4cc569959b96ef446e074d63ca93921a4a86ed3dfd18","aa40d71dd57a81028c76d4080716d6dde78ff51e92ad1460e5f973adbfaa193b",{"version":"955ae27fdc755f32aabee0f82c2db6b3d8505f99551cc8376df389eb90e7c84b","signature":"4675797b0de56fe3c5a6e468df193709c7f066a244e2da0d02690f193eed5345"},"4eb900416055b66a7063f285dc36561ccd1d276de8a637165beef04a3b3aa162",{"version":"7abfcb37b73f3b4fcca65caa3cfe40a12b8a89fafcfa783f93605acdddb0cc25","signature":"1a574fe33afec63182b358ca9e29944cbdd13c69413c53fcbb8e924018e33b8c"},"4bbc8169c9196d2768927f96e35712502c0445e653a5d427f670aac13452f77b","e712c5b04b15a0dcfde9b382f466dece347f88369386bde440848fe8e2501a21","0961df49eea10f9fe072e10c83c8bd96505bf9b93cb0ca6fa1d10dd3d68e506e","c9835b14ddc4e4115f493b814c646b64cc592bd18a8168c0b94fad83406aefd5","e99bce1c616138462e9ad01d669d9667759a66a549795aad43cbd8d3829eabd2",{"version":"88cdbc3bcb4689a70130597de7c941b2450bb2760674a02ae816e0667a1958f6","signature":"6dbaf13dab6dc2db0cb7312fba7996ca7f548c7929bb627315cc89b43bf93ada"},"93fdaea06f53eda94b236d54909091dbd7046bc96315b59d224962d4a95299da","5437c086fa05daccd0b205f10e71c34f7a5c65a60b70c449a77d71c547777399",{"version":"7b4bcd71a2ca99183c38b93f34926a94615833826ef27f05dcf62494e196325c","signature":"0646934539246310c9949fff3507ffa197e60e50821f7ba77b5518241bbfd7af"},{"version":"55a9358103d4d3fc812e7226cbf54ec885ab5d274c3f0fd7c7b89138761420fe","signature":"09ac449d6431aaeea979c72f664e0179ac698d66b9dd695199bcc985f21b10b7"},"332a923c1b65c0e3342254ffa34852cb772db010cdf62e81fc77b9acdab179af","151fe72ab1a9917c0822dcc922ed5d1ab5999e4cc39490a329d5bfd088223de7","33289c6ff4c33c404bf9f1b11602158811e4a758d02f7ac67d459c72043c6a4c","e991b473ae7d3407efad7d94f21cdbbf38e0edceb0ca77a0937896db1a69a432","139ab031d84be958f97af2a882ea123ea54c99cc05f4a4f2c3afebccc1f76059","4e3cef7add4741ef800199b5d9f6f45f3b05b4cbd7b4f9713b680370488856b2","83633eaca29decaf169278318269ef988fc92d0b9a47531dd5301ba069652fa5","d100a3684e4d3e61492477eafe8fb250d6463f83e66b6739ca270b99ed9ccd52","2564e83977f854fbd3ce140f2d86f6992c1332945634a2f803306596ee0bf69c","b66cadd5b2da034134f0112a8d584d76eec9e8025f23eb6b556dea5aa74fe3a3","3402b3070b7f9a2c6ea7f3082c2ed7f2f0d8c589badb6f3ec62044c3b7f0184c","3db03edfb97a8c0b482a94fc0280ae10207fc529842dc268fb0cad92148a638f","913dd719a5b5a91dfc16f29bbb6af8d1f8aa0f2b141eb4320cb2ff4f973bec35","a4150749c6aa9db1224cefcb07931a35d19f1f8f00f4b79674f6d25c5423f181","5eb87fa9a117af0d5672f63271c070d9294dc2592a8a71c7f67dda52635bb8d6","132f976bdb7a85c0fc4a180cb4673d199394e4b38feaddeae1d0939c90df34b1","2fa9260ba8c9c073651025b09d81b2da143ed8a4d7334d20a0f7f7eaca3c3ec3",{"version":"2023aebac248da544760947901d5fe7aaa214eddb7c2d7a92d33bee0650ffc2b","signature":"8490d17f8c61b6b1b705fb66b5d5e12f22aa29bf3b5ac54718fb95a75513d46f"},"4658529914e4785a4ce0213e8cc9af4f2cc86adbc3bef7cb6e9836ca17d8f0fb","83b0843643676904927c595db1a32660cf4eff0ce34cf374082588566851e37f","ba42a3ea09e763f637b9f8b040704c66d052c7e0a4c3526fa084516fb34cac0c","c742723bb689a361dc0e32cdacf7f4160145254716deb013292a2f45e6f5e1ab",{"version":"7b6e8d32728e05107c573c5dc2b6fe9cb14332dd7c82fb530093a840d6b59dc7","signature":"22146890ab30bea45bc289ccc48192249fe1cead53510eda7d9af2b09e065189"},"7a81b4127658262d3b44f32ca2fb5589bdd370f3c971b7023ec2bf0fa80208f3","0471574e07ea402de091b23741e7759f0293fc476645407c75e8807fce4d508d",{"version":"54a60dfbef03a8f34a21a1b21e6f8c6b991390b6bdca741071f0e9aa378b4610","signature":"2e695038b1f0a6040ae88a1a869e11cc466e03a7b13b526efa90b3ebfcb0068c"},{"version":"8fce03e56480ecfdf0458b4f97596020c7740577638075be2337984e2f7e0c27","signature":"b86d5d8bd5104f1ab29d23cd5be61bc514b8146a091257366678f5d99000a957"},"503b83a8c33ffdf3a4fae4b560df55b7e98c0722c4ea69e32b7e71427888f440","ea6437c6eda871607d5a01adf7cc5afdcd66f674509289cf2c226cc8b9734773","61a5d2a3dd261b3c2b751c713d088f6548be6199705c2c9c5775d12bba1b8fcc",{"version":"24863e2f4b2b1bb3a3450294a76b5e0eea7b3a2e295f2225745da9c8592ee216","signature":"861096a3a6ca8f6ad72022664dd68b02ce3c37ff2d8f05354e1cd3fb3342b366"},"ca5c0df4cf20a1e1a7b2961248f35767785a03058ed250be5afa76c2713b202e",{"version":"9859454fa6df442ae16cb0ac31d0c02a0a85bac28c82b9783e8f370adb33b245","signature":"f6b87832d9447b2e9d26a9676efe78dc75cff9279ee64a499f3e4360d22f2730"},{"version":"583137ad8d520191737844c217f6e5d839105c7ec976abbccd46060ed8cf928b","signature":"213e8f64d2aee549df8047a587e27018fee7674c72407c2a191a634d8e05ae4f"},"92f78731c5130df45847dfa1a46a00a27686891e38ba51f116c586e520498ee7","25cc87856525e88d4007f5f84251a00b6c47b90fb435ad8459037f18ba6b8a11","ce0d61b977618ef61cee89091bf0bc0ac139c64da5b41080486c84f0002e755a","be2f617d92b80f8cc4e567b59cae553cecfa618a81b93ffd974ee7f2a94ecdfe","f264d234b8645ae1bdd723fdeb71a0314d77e06d8e7f6aabeef77c6607acd56a",{"version":"a47d50bd2f57719021eb5184bc1314ce3f5837f2f78c4d25858e15c721e07ad8","signature":"516fbe6606f98a2736d92faf0b928b6f1084ed15368ba3cc8f055ebec38fb818"},"b26e8bf9c6f7701c5fb76c46235e05380573408867c4d57f68000bb3f543937a","8b726542035580da854bccfbea23223e0fdac7df070292db0856bc04cc3989bd","96164479311e65dfb12975f7cb97fa997328e7f94a0174377f4d6b8884e9ff83","7ac8e07828dcc1a5e01fee4cc13c788dcbdce430795ff0eab6d39e7b3c095254","87a05689f17c2271a7e63a0c5dfb6734c823f69bb03a6e89cb558ddca0db79fb","f71d2d69ba2857a6ba490861f2ee808e7c362499c19e5f2fd350d2e48b990d93","3124c0b40c96a6ec3df9a6053d4107ef952b5353c72ff85ddfea0c56cfcb56ff",{"version":"c09242a97ddc30a0ed86ad6e481998869972d43d60027ea2dea569dfc4ff79d6","signature":"0fe3236fcc755ecae3aea84e78a420d59c851fc19f1623254decd6408be9747e"},{"version":"15f6b22a1a9dcb5d6ae6b4cb465b0c628f5d065489e0250ce46921de4c343df6","signature":"80366674fad0d2eb8bac45ad76aacdf3112cabf2e032fee7755a61ee0fd9914c"},{"version":"ed078b6e6e7eea82b93d3e16aecf4e5264db34569ccecf89ea244e130a0fcaed","signature":"92c9c93878f36fe51e3431455c359340aeacd788cd1f7dc1ba24faeb4fa87d3d"},"1d6bf45b076d03144b3058c0df777f1efec117c18e32e691f41bd9787514eea5","d86ab7f08858c5b466e689581092e41d03390d1b527a476cae72331305dcec24","f3c042ee7810ec25d7db134620b13c2610c73f55882f6ab8be13e27252117d40","7c7e71e5e39435b48e0271eec28ab242ed6f1a65e740a29932cb83b9e617c83e","cc60fd980e5701b006200ca499fcfc09b7ac317785fe53307bc9a50fc4bec464","852c7b0aba9aeccc21161dd2e0fbf11250730018343d88986bae2f905caa3b40","d0b7e2a5548f56597acc899917e354c348407549ded42ee13332c83b5c045bfa","a030ccf7a13e613b354dcdbe5f197a9b7fa0819a4d0d8ce7d1ed0aafdaae48ae","29228a2fd8fa9e03243e2af185473f8abfeb407cdbe4f72ed329bdadbdc484b8","84ee6c19db9aebc0f267dad9f38b59769a344e20ae762030ba2d8db629f925ce","1314a35a2551c127f4844fb29fd49321ffaf3701afc6ed7131c90833121593aa","9d764bbb43ce204d8fada7418d0681720eb5fe4cc2bc14018a1ad6cff876aa56","bd49ae74cc4c2def51418f9bfb393a8b303c05972c2fd8bdbc0a7d9c88d2bbd2","e12eab448b2741fbc58fd99df25cc662d647313a3f5f6ad7cb0d168b35c512bc","ac37a6d8ed49983b7045356b04ad84f58799843ea2afdc53a08f2614c11b662e","d1fa13f317d3637fabb663edd46b39ccdc420e0c5a3913b7fa4e906d99497cb2","653f388cac26465dea74d7a695412dc4285bff051db33e18a576e941c79842a3","9ae477f6e996170dcc13a79cdfa0a2b709f3eb50b6de974c1ed15fb2e32eb98c","62fe41879cae66d14c865c973556b0e24a904d9c6445557f5414007a236ea56b","071f3deb2c96ba5dd81668fcf4f909d6402b64c0c053846ac9d2aa561a136b03","cc39c13ff8d9df5c8ef59f0e717e3b5b4e96f7dc05859a531f1f703718d4192f",{"version":"10acfb644142d4c7da056485bd721efacd6ee61c0543c5762862a88f4ec9be94","signature":"1857ecaad23982cebb7ec28e547ecdb341d40713e95988b7f7d9da4c20f9646b"},{"version":"d337b2b575efa0ae09ab5b8bb94ca907728beb48ad4f9a43c653c247ebdf871b","signature":"be1b859329d61b0f74fe1393fd56c1542d1807ddf5a035b27a7153c471f53e32"},{"version":"9af90dcfb3df248fa3f8abf701c073fa30d6ee7b5758ba4de460594c56e4af8f","signature":"5b09eaef203954c253a646fea5d827882c557488a4ec3fd8cc50493e9ac5ef4b"},"c44bd1c97aec9b3731f94e4ca33797b718f355040fd1a3531cd1cdf72a092f98",{"version":"c7082c44bffd6cbe3c72aef8e57431fbc1d554a0db75d11b0c38fe4e213545ee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"9d116a47a60bd0dfe34a66d2a4857a9a73bf2406915bd5b19bab3d1f42b8115f","a0460c3775eae1effe1641d510f8cbd74a3b430951edbddf3b8ca9cddf732bce","6aba6fd003ec6b75e94e40335a2315213295714f33e38b4164aaf7bdb2a3aae0","b72a531a79d4cb645c43c6782dcccedaa609b2c7efd71547a56ee74fad0c3dd0","53cc94938d41698f1994b5de600edb7e89aa936944ce1d2955720f69be6d460b","259cc7fcae5804316e63f5d00416e69fe28d9a3bae59dd20c767d714626dcd5d","f5c2a1cb2d8619642ba9bd687227fe3ed43787235c8e980c34aa844645728465","295e589e5b8aa32d6997d6c604fe50ee40f25b42ff0134c5167c651e27c332cd","0f4d43d34056d61a57ff787c29fbe5b2ef301a333ba157449ba3df4f0a45649b","254a9df28b54e73e3fae641287cf5e938315c436c42554e7f39970a5f41c8f9b",{"version":"f4edf0a9027ff9279ede897f9c304c9f7e42c93170d2b2f66570698048e887ec","signature":"abe72455f516e18ed06bbb7e01ea1450572ff48cd86d4153c5853474dae5e8c1"},{"version":"d45ebed0a7af7351812afbdfe2cbfc7f88163d72bd79807532bce53cea6e9cb4","signature":"4ae59f31cbb1d8f65520a2852714b43fa800e651ddf50dc1a3e68c56d6537f9c"},{"version":"13bf5a8573fc1891a43ebea36a1ef5517d59f06c22f6e3bcacd8c4fbfdc0be76","signature":"654865d2998e7e7aa50e64fba9f1dcd717a7f378ee65b7e40311035c011e91ae"},{"version":"452510133c135fc44ee7c3ca38c2169280ba85989504826040196555e2b03c92","signature":"0e6ee02a5692f58fae9680a1c9b1dc94d3af9a97456ec14bea39bf4a9e5931ad"},{"version":"2f33f28160bfb02bedb63ddf4f6a8241cb2ff6967041643a0d7ee0909f75c3e6","signature":"e7d315801dfb219e04a94c847f0ae759b7d2b451783d38974a72e7b695436803"},"04fd50ba4fdfc24324446f14648d1c95fd08fb7c3f91b6de6a17ef503f052e36",{"version":"fa160d0c5713d8259b2648497fd70ba7c7b7a6602a840c574eb1c0a6f46e0454","signature":"4951a5459b063778e07d022547e89168c941ebe6bf458f07ea66f68b5f2e8de2"},{"version":"e74268ffc9270115d1d343bcbba879e819fb149e693a0e0524e1f321bd55362e","signature":"72dcdb99ca1e3ca76a476fa8bc73a89768a7404721c1ff2266d2c649bfb9e11a"},{"version":"ee82aa0ef404999ad87bb7a2baa1d75b0fd94aa2a0ff93bd673b39f7901fc37d","signature":"080b3addbb0d6625d7af627d88f46c15af2dcb962ca35a4715510d924cd470db"},"9267691f6b1c001d1ad417d316eb19e3448db243cd5eccd9e7fe1933dc80303d","c256e102702b489676e3738666b34d985b2bed2835c1c6a7da638a2442ac8d88","1fbf86d5c06434863bf58d1e0b464481274e989244d9553ff867d4f742ab0832","5bb0181380d7d4f24d5b59efd31845ed2835be7a0e6ee2fa113735e2d14f7be7","42f84fb7fb1bdea79ffd6b67b36c9906b21f0457783277abd39c047f053b3e42","65c5c1e3cfa7e96ddf00b29103d558810220aeec2c5e15bb281ff6bfb7e61148",{"version":"279e1abd50429cfe84b8dd7cb57e9684d8ca7864af5c3fcf853efcacf680830c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"578015da0ecf6fb49aaf4d86e90e8ce9f46a7b6ac293ca9d810a291d42501841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"e71fe8dc39bd428a96ca05a044b5a87e7fdb21043102d1eb4fe32f758e88092d","9ee3a3696c5ab964b6ba7d41121d5b4d91ed7d70d2ba7cf0dbdcfaa617d19735",{"version":"19cbe4c67f1b32b90b7ef46d4bc60f25d42dbb6cb95f35da6d41c72ede463d4e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c22f3ec19a761c9989950f01e38fc127ef63f2c0a3300cdd0b3b54cc28dc75c1","a31abfd6a1707f3d3fa8fcd6380a7cabf458285d7190030215d8a92b0c360827",{"version":"bc5ea5422be0834017b7ea3550c58d61ed1f7f976feaa321634d7fe60a0f26e3","signature":"117ec0eed14f00ef3524ba8069fbda8cbb45fde70d22b16ed255473b2108f1ce"},"f32c35930719a4f9920de8c496365ec008e8cdeaa312c8902b6ee6eb8167da17","22d768ed04ecd7cea3fc40851466b04fad6078e979dc2ea835646413b2a05acf","0ef3b705c81fb51f3b20c828fc50e9d2902644ce8343281c7a5c057da23c5f86",{"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":"4e49c2a5cd5b413d6f345797cf2db9b1de533863cc4ab32c4de16d4866480867","signature":"4ea82f415bb35563eae553ebdd9cfc541d18967c536d1bd821f38ddf50836ec5"},"b49285ffdee55942615f0dbefbad0034203e214cb288d2cee09d3e7b011c92ac","d46d48d5ccca19b55042e2d48a773fb97d0bb9769d9f457112c8273851b84d0c",{"version":"565e7c8592a98903a22c5caa7be9df48b5defeb0f9dd5c95cff6cc02db46add9","signature":"19f13e301afd7de9e6c815b06b16029cb6ba524d50bebd2b381b4b5009521f72"},"6e3cc8174feee7c91df7b15357a2a608ed4389ba83455b70278f0ca5630cdfe7","259042b0a833022120c295f2e44f95bd7acece59830d6490ce6ed9b2f9ceee52",{"version":"f721d57981e266030ba4406ce641861f72fcc09ab59462db608ef66a5ebe4e6b","signature":"8eb5d3569824aba69bac738c173d655d8fb61b8585dc4417a04c591d8e1b26bb"},"954ba07c66ad24d7d4bb222993578083a4423c0f92a9bac4fb9e736a3d4eb813","bae8f47ccab731836cb7117c0a8609e8c02d0addd4fd4c7009e8cecd476e818e","14a5129ed9a94b8e4a84095dfbc088e5a713ecdb391ee5bd7b0a733e64d69301","8166c477f254219baa01afacf9e1c7f90a4afc2efde83183553b666f582fd1cc",{"version":"978ac9ab1977c957fe99662e28e319fd04ebeaf373bc16fa6adfbed404f61b75","signature":"be1719b0c3e5f1de72217c7107e2106c1e3762ce3ca52d3d26db16dc36c52150"},"6cd466c69267ba1eb5e573879aa16f6ef4cf9547ef136f7a9302519e63d76d0b","5e1f26611ca7da9b91ade8b167414353ecec33bc80baf6221e5380caffee6d77",{"version":"5a34bd0c56b037d93c424ecd406abe72f1b269601bf41192361b141adb9074fc","signature":"02808a98b0a41f297bba68b200e2b9d820bda512785431e9e024b23187a17c73"},"af0cf510af3d03a0b9fe72d343822474a7fb9d983a5055e6ff3230b7b5be14af","014f38ff04103744e6afef3477513156f3764c2b716e29dc06654ab68ee9b20e",{"version":"a234d8736b9199989054d2b41cfdac2bbdd5614d29199ace0392d74b90125ca9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"561b834108e87bb7d1af50d2bd2abc639ab2b500127194847f60dfc8f773262b","5515ce465ee3acb15a149737e6c68ccc5471ff3af40f734bbcebde04843218e7","a6e71ca46fb789195d1a5be98c7736ebda95ca1aa8ee682407357f51de94126b","597350c994dd8612fb904fcd1a29aa30bc85c98a2af98c26a1cd5c6bce9f9d94","d0fa6fb03d1d5584fcb167aad7269de2625bba64cc45c92d023558309bfe6552","153915f06dad4aaac530cd789440038545c7634f5b4fba7ee5a7df597891b26a","4f088bd2a2b33a24314e7d751ddb7f1b223459ed170ee2b149ac5fd9a2113c06","f54b584ec4aa7e62786b850734101d7a26ae631c71d2dd0be082f13c722d4cd1","878afe3cfbea7f16b757d79b58604bfa14e483ebcf672c9fe7eecb1425c2dded","26ea4b6af6742a924b625e49614863deef40b7ee5aed16af861589c265bbeb28","5b228259001804ec6228a9a0ebd02b8b549529319be68558801a8d93cd50a5ea",{"version":"662f4f9aaef37a862d00552a59d1aa314f681e424eebf9576b16df78419903bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b24944dbb9cae7dcc4282a42546e31fb53bd8a2f2cc7f8ae6c272d5924a2ba55","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3b1cb47f4d87126cf3f2973da87105edb404a1c98c0aef3a2a289b98fc879029","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"1856c2b5c7e6167bd7869d46273e730aedb23f80c1fc013f9f018cde1ac508c9","0387f0bfbd708bda5035a03775563836aa22508d2459e017f75b415c5f6b3452",{"version":"dde98beb8bef53cee95b020cbfddc90d0012e9d98fa19595035191cd7d2cc1ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d06d3fee2b986f19cca9483a4420497ff3909f6487e229467e75e62e283161d4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f78fab13e0f5ee19bf3e2ef18b5ab38a47dc60899def7a82dc05860915155308","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c3eba367c1921c8f9e7f231a941cac022824cb666e652cbe754ac1e50804cb11",{"version":"2e0c7c56fd6742b25af440e2a83916cff12be55ca6c91f899f1b4fea9827a69a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0257b9ac5edeb935209106b79f9b4565fc6bdef9f2b4c5be6bed787a60ffdf1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"982ce1b9e222732ba63b4312ad1702dc81cdf4e277bea0040898e35fde086058","signature":"1af3e359f2c3a3b25e6cf0532c9b12b27c8ade0e7eb582007452ce06db289d9b"},{"version":"86e3ae5677e1c3559ed9078231e1e54111f3bff63b1adea3d14ff48d76e2ef0d","signature":"bf3ca96bc59503b3214f0618af79741f5a28de7d7ac663c13578af8a12fdc385"},{"version":"169351f1ba2dc3837650208074687baa7b7abdb18e43d3547565ac0ca05918d1","signature":"dbd0c498b5edc07924a5f7ebf0ae90efb9436dc2229792eaf175e16c03248f98"},{"version":"87bebd1c58e39741b573ee8d32b69b74d318782eace1a9a6d9f80586324b2d3a","signature":"800a7b9dc46ee24c46db2afa893e0bcf0b8c8bb1bd9b8db5361e34fa3c2aad18"},{"version":"06350267a4b707023301d6af8e0011c9403dcfc5eff31ef7cedb13d8f97c22e2","signature":"b7b254f81d9a367bcf98769a957c2f8dfef2267573a862b52ee2748647af39e1"},{"version":"f1844ef5a7fe33fe520fb2ea00ee03f96e3ed528392ca36de5f42964490a70cb","signature":"d949e121e0b71673df6140eda41341316408ac47555ba72b02b4abd3f0b6bc3e"},{"version":"eab55adf55f35c0e404ef2ed03340e5bfbfcc9f8e631c1ccb99d28686b79c60a","signature":"dd569f5b0cf0ca74aa2b1b5f2559d99655fdb41881b534f6d27e226903a24880"},{"version":"013936b36694b71563cf2eeeb8a159cfe7832432802c33a2d991b0932c81a36a","signature":"7bb19ff78f5dad94999cc2e0debb65a5ef8812b4507a7652b0b3fc455a9e8ddb"},"80ea52c65ce80ac3d8d81821de8e8675a7497210ee37b23efa79f21bd57fc86a",{"version":"a384e31103a21be8505d837fc43ff1a3653f70ae795b4f46d1484bd9e2623301","signature":"214644d2fea678926fe214494d5b88720514df481a09f665137efc5ae653499f"},{"version":"bc2c8875db5a1430437c82f060faae49d0eab2295f7ff81c5b82279fafa8394d","signature":"5cd36275e5e2e7c71e522a445740890253664d68f28df4d62a4a13c21e3bf45b"},"1c789f799f0a7e4ec25f87d0d72d21a19b416abed2674a3afdab31ad48f3bcf5",{"version":"414844c14d31371280f1024fdc10ff268455384385eea30dc5ba252f3e4fbeb3","signature":"da8aa5942188ad3147f0afacf4c3f11b24942ed40114ac1a2fb9444119d69e17"},{"version":"ac002c49c6dc6a9a524d074a6f4c324cbd4c320e222eda80415d53150d3b10a3","signature":"ac68bf7e24525499431c6bf39d62b264a7708d2393d1aca05a3b8d153657b2c3"},{"version":"7516f8012b8b4fceff405a25b09facf3eea5aa640fd6bbd91c169ef0ba7119cf","signature":"49dbff2eb0425c00c48128b4ff64bc5c8ec07f8aa6fda343bfb9302a2398392a"},{"version":"61df7340e77582676d6a10c309862970af30ffbb6cb10e86b49005764fea89db","signature":"9a1fa87e956dd9e945a728ecbf0bcdf59b5bc35bd4ab98618c8f51fe319ae756"},"547ea3ada84754869bc28f5822c247c0525383c3d8805f342a512ac2ed139f0f","8758d5e30c12540491d40282af28875a47bca5b8bd5e7f3136ebffb4d57a86c7","a57ee60e0e362aa6d65e1fa853b4521c967a31485d2ddd5037212f09910c0dd8",{"version":"43ff1f43dfaad43d87026e3a953b74f70368ec1ba49f67eb8df40164a4ba3056","signature":"4a26aafff5702c778bf7349914063554cabacbf4b74ba530aea9fd2b5c060e1b"},{"version":"c954faf290c5251991902e64a51f18bf0a99836430e50c38126a7ec753629bec","signature":"e5d7539a72d07ef9c3d686776f885111a063fc63c60c975d027b6b643970a358"},"be035cd5d01eb15b85322a205f090f64d333dc047ca1082de84837dc31c31d97","49c25190f11126bf668831364bfc03a136ee59e33b3ee7a7f1a214cadedc2bb3",{"version":"5dbd0527243a6d622ede33b461f27551614d1d4071c9dc1b246a7cc9db850cab","signature":"5136f18880c11778e02967105e9fae9a0482deaad8f1583230676bdb59fb7ab7"},{"version":"3ad1bdb57b05fd29dc468a42e71c4ec8f12781a647edf5029bb60f5a8afee701","signature":"5ac419d5eeb2a884c1d260bf31248fb2a853d3628aa0d7c3a99757ef99fd6c2c"},"678dd9537cd28a491bd13f7f3177c851120fdf39f27e9a93b349979bb21641af",{"version":"0d010c0b5a9166166771c8c48bf48e48d9d037de37903d2b2aba860d1108a2a8","signature":"137de1e22724e42c5f197f61b17ec1264467852dd37b27811c311c97d705c138"},"41dc35af0efbda57ae462f8791b8fe355cdbd57d7414e238e2622af12f83b52b","aba095f915652dc697979c0b9ca5a3111b7160144f9a1e18efc81fd485ec9c3f",{"version":"fd1383591235471dc0499e38fa8f0be6bf354c4ca3aef2bd052f93e34c821f38","signature":"2ea178cccad298208dd3300fecfc1e882484d9fdfca4a8c473cc345f0a34eed6"},"5e7c8ec6ecf3ed122b8723973c85fb4db2d0aca907a4854a1a800140e8bac53e","464597875def0d40d6a0ea4bc5be50373eeb35af3023b4901fc539c19fb088c4",{"version":"560ec4980f9fdf84e4df149a31c676fbc624df75f33af2159b30aaad1d624506","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"5a31e889fd61c82203d9d046edf845eb54c12d63b13d2028e5c1f16c26c5a535",{"version":"e0875a51dcc08220ab37ee44e0d604789fb0c24c6435826eff6522f9af79faf6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"3494b784dd3988b30529cc0f271d5750b85f3d241eb612e4bec87d99f3a79de5","2f9d111343117be248f5860e96c68b5c55e402894408fbbaa4b031ab12572474","e8252a1e88c45e4b76044e3ced48484fa04faf5873eeb2a15e88813fcae79808","bae7d0911c58609a404bcd7255d5c80cdda6d568c3b95fb189620ed7bad20843","23d90e3d7b8e5a17f760fff35617a57ecd7b7f042602b3f9dbe314e938c77330","00ae1a801699b73d425782db51a2eba53741776741421dc8446480d09091377a",{"version":"f89a50a4a14e6ef1a1c81b263997c2d728ce5b56bd1d93dcb907d57114ccf955","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"830b21dc28f068d3d362d407c17d010f37a9a29cc412527c274b8254c448dbde","4492c3ccf40d889bf6eb454af8a7fba4199af810c53d10ef8d0bcc16156e72ff","3c21f5bee2f1186f62b01fc606780ad26dfc12ce34fee2032d2c1e35ec2e5334",{"version":"6fb49bb8359a76bbd80e39616d5cc6de09d3a9ff938cf58be22c155e8ff42916","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"effed161b9f183f637fba8f96864ffa67bbad3a3339b18d9d368438fbfc00bd7",{"version":"a681431952e1348dc231f334ee2f4818b4be12d2a720c06f52c842d0a577aa9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35707dbd962f597cc72a0ccefdcbda1c0cbbddaa11bf9a072fa9dc2f2b40eac5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"6d872c4f980d7e6288d80742c84f1dc087a0ec7531e18cdadfb47448a669c2f2","179703b328f92994e719755b197ff2310945583fded682cb02b88aaaec0b3d33",{"version":"d2e752aeb02ae1be73703cc7834f9bf1de14b84d32121fef58982b29bb138019","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"954fa93b29dc7267fecdb55b80d28bc943cf370e0165963ca051c0cc6899e114","bc8201479e29d49966186df4e5c359d507dbbcd4f772499b365e6836e500bde1",{"version":"ce4cf241091329ede4bf94c365874f20cb8309b02ec32980d9bb47f6527e86c3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"522ac15e66211cad975f897a7eb70e77ba20b34ba8f9c4babb8f75f37e19c24d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9bbbbd6f4a35a22eefdd4d13b639ad27d2b1316a6e833e262a126d4310d904ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"80c14a78262fb095d375cbeffe6a6b53a300098928410181ee1140a3a8869a47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"66cce2ff73442b6f95408d5847e2c8748bb4e47e44334546e94e52be58c0d163","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"ad628be53a44b47262b560ab15866282ad4d257f2f214369e5f8579c84d503d2",{"version":"98021cc3dddc6c8ce5498e301424f2314a9f17deb130c609269c900716a6c129","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d4011e0ecac2f22d7f639baa671cb23d19be79b0dc64c1cedcfd026469e5dd46","563fa16b249fb0bf5ed14f72e40b6ead283ccb254dc1ecf0304c0165ffd4dc6c",{"version":"66be3a972b4e3ca0b0264b6d5de8436ed29f06e9737bb73d355e1cbaa8aed008","signature":"5284727f6ff23b3af566b99ce979915c2adcc7603f6d73c1155afc7860b7bced"},"d0fce09b7c0187f24c9b0be74c938a6c39b6275b2a648df401219a79911105ee",{"version":"92d32911e086e087141b1aac3b7876089e26ada9ca8758a91280a05b4efd3a7c","signature":"6d53e68963aec64794baff110983e875c60a42a3e3d1bf17ea385752c914c1ec"},"0409151083cb223c8bbb1c13940f1aef4c1cb2078e750e3e1b6dac6403a11848","7f0c36e389b38fe05922db66efe56eee73475c748275e5d2b412bd4c4b495b86",{"version":"fd3e19108e40b4bd6502bcd08768a75693473f1ac31649f1f4ef6ffd7c88d36f","signature":"0b2eefc3650c7cb2c277d27ea3a3290f5835e2ad871b17041ff92843b06bf99a"},"b14453b02122266e37e186d1935cd337dde89929a1417cb87c6b962b39af0d36","af627ecf76e60d85bfe1697aac2044ee9a1b4f0ee8439eb51d351db84cb56654","20a066d0baec26f8ee4902ff7cc7afdec57496053b60b5d3bc5c85732a14597b",{"version":"c216d4cd926b1cd512c039ee12dfdca10a292a08b76ab11198dc2293eec74ed5","signature":"64718cf0d577ae9ed2926faff603162ccee149cabf0f7d6c3d2eff8bab3f54fd"},{"version":"c8fe61044fac5d42706c4c8854e03e5eb073792202ec4e7180f7397155e34f9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"ffc13d84cf2d63a59bed820986ff22900e8605703847e99fd0689f513278c8ef","48c2e723a61bfb7e205ee843adba993f04a9764b6a9d11f0abce43a08bf64c4e","6207173cb052cb38b9660453164db77c4c677e0901e1950be658ba41c01cb250",{"version":"65db26f870db2f36af509737119f27bf6fbcfe7aa413b169dab0f6215758243f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"52977bca0c3c391efb84678029b1816a997a5069d91f8de7277079ad39b00c53","d15dbc0f96e7b8141a77749e01a4e920a5381ca32a2aa58132bc5f7223f291d2","544ad2754ea5eb052e793f75425624b7522f638801fbfe50cc252e8bda11e0ba",{"version":"75477456333eb2c8c6de6021163fb889ce42464239529345f7bd77a77414a743","signature":"1416dec78fa5f6be6be2e406d0cc50d6f98ce0c77dad79080d5e5a80be18084e"},"0debcbdd5a9e7131d85401b18ef4a9e4dc73a0e08641b30d08004be0d54e3ebb",{"version":"b7277bf592b4832b905af6bdf6120ea14f1b9a9210efb7a4ae3803a87287150f","signature":"3922dbc2ce29d177e9d0c1abe636860f8a1559bc545abd790c0060803fe2e1ea"},{"version":"9a106f225ee7bf695ed69744df6cb6982083b3f9c2fec9610d5ea74e2e49f6d6","signature":"9dab80bdc4cbca67c3eddb3cd102b87f111b1e4d1ba1b3a0e27a38258e31e426"},"d1df8b7eb29b69426f6328b503a12b4408d4ba4a3a305ada40af859fd0d1542f","71590a10d662a3f420f700c10793764811c558938e36311c61eefb13033a21ba","ef07c47a9f22bffbb585da6ab96f379d97e6b72fbb658e78c03b11702cd1dc6a","e561876a844b5d66796e60c5374a55e3666d17b8026012a3de1e78dc03e045a5","2bba20822fb6a665abb0944bd00e587f93272c5aa1b2a513eeb9f2fed00a7e7b","fc9258c7768321dff71ae7ff240ad1b5a6b204acaaaf8c088d37f1c4d644f20e","fedfde2b5b28d1a1ef04e2180aa4872b9d6fae211c9c2dae739c58eb7c24264a","c78318850aa730e4e4d4900d62112d87546cf76bd8c0f8a5389de62a8fab97e9","e21face6fb69732353a584c8ca54d4c4f32840b9d976bd2ed16e6e04ddb1b689","cb29061301b3205ef763d9159d82d81dced1528068d82d9248aa828b293473b2","47049411d18af8a4afd89d6507e87fa1e1f761cfadfc49db94ff0fda85e2db4b","ac87cb8a327e1a7e15b3597bf1ea9207128645094941d34c91a4f4294cb50c38","a580c25f701be8158ca4a6031e21954544e71edb470a31fd4a572b6aaf3c7064","4e5ac226a5b7a72d76155e280d38d71077346ad9a60eebbbdf0b02b8c34a8512","b552dd1e51bbd18d0d9b4904dee01cad165d7f0d3471b42f00ee36ca78cb81d1","a6ea6a6419bf0d19369d82a80852c63bf4c4585648584fbb65c3d7cfc2aa688e","b83570a2939d33a6ecfdd2766a3e416ba612d0e8d6f83ad11156a004fa033c77","10a08fede9729e6432dd4a751e6d512f298fbfb9d361104ac97a2f4eeb2a0625",{"version":"256fdacde97c7484ce9e383899d94781672799f45a45c16cd6a079ef5f9f8a5d","signature":"5f5d48b01cdeac8202e38db3454ce0904a00a64bb7b521fcf30e5376084d9de3"},{"version":"a6bc0506fd785d58fc01916eed093992884c77047de6ef24a1984e870958f3b9","signature":"6c67fa30e0db9490403ce70c9bd112dc16256f74e793f48501b0af56cf31c2f5"},{"version":"96a0fcac817c0a1d7c901beab6ed6c88071511191fa3abca730407a905361481","signature":"37d3ec125450ce03a992226a8a4952bdacacfa88aee5d09f821236d9c63b20a0"},{"version":"419ca4ab657409f45b8db6cee2d5d6888a2b08a2fba70ddda952d968c69a16e2","signature":"072d63362c70c19e5647e1dd12ada4492213157c48c17ccd13a008f9c6b4a12d"},{"version":"8231663779bfba7f580018479f74d02df7c9160b3e8dade1940a569ed9d80ac8","signature":"b19e055eff7a9ba8d3416874c9a679d800a5df0a0c5219cdd5aa5335c6b8b072"},"cd9961e19450bde1798e94855447fcea0f9483ba8bf4bf4624951e42a2bdcfcb","778019a2b3ecf4e408cb6b4c19fe86bb89ac9af1420d4564adb23bb7a8d499cd",{"version":"e8ec03a7a1c38bad33a45b08e9087012dc1a17274515d72f7f99c8e591c3b2a0","signature":"0e094d3f18ed4a44baa44ef3264239439eadb03b3f8e2ae278d766c852fa0754"},"2bcdf74ea61885bc9a5da25620364899a0e8cc6a2f6bc0bdb44d7698152d4d22","889c6cf5d2f17ebbce07ec946521f4f1a8fc55d9530b4959a7ae954ce7f0300a",{"version":"bbc45438a3de93d2d44f46fd0cbded993b3f8afc82779a42d0a6819a10898fcc","signature":"f376da706da2e3ce62334b6d086d2d91040531879603f039d3cb7682d8d889aa"},"c15fd275a051a6770515950834e07dc22b7ebce6a9e8a93bce69d67d92f39e40","f2733e4721a9ff2047d46ddf9f771aa053a8f482f93d4820401d0be58dde660b","fb742dd0eee88f661ddde482049aeda9648bf9997a53db2411360517e1e81549",{"version":"7db8deb452f9faf63b51a33fc3a09dea5a305e4dc231b770aced708f902dc7ba","signature":"2fe7ae68eac160827cc1ef3f71109e12ae1ba4c407fcf41d653877c7a3008970"},{"version":"8dab908f81bf0eeb9611fbbccb2508c2b4a8e1d57622968cf98993e878a972fe","signature":"b01970e81b7e682cd2d51def6b76c7bffa451a1b58fc54b528629c35dd89c9f5"},{"version":"9367e99e6028dfce0d37891b19a17bf1a3b04fb2649a89ae7ea832ffc7507b99","signature":"ba000331a8a0915160cf82ffd04d583bed6ea5547a117b8d88ed7d3ff6eece7a"},{"version":"456eebe80c579a1f7462b21134feb2bcee727f99966435c3fc7cded50fc80e3d","signature":"525d97773ff298b2f2fbdf14a791f0587ea0172827b6ac8821f3eb70b20aaf1e"},{"version":"24892b8255b88ef0102847ef8b231c6bfc0ee618a69b17e40ff1438f9997f2a7","signature":"59203658389170eec22beaac1509a33cbbdb6dff49b69e34593aa96c90c7de1d"},{"version":"d644c33e3d80969acb5b187976c8cf99eb0a259f63bcef80a6ee38da18e83247","signature":"2769f26e263572cb6b16ff1b24f373ded17e74e710e33725c98a22a0b7ae79b6"},{"version":"314650281c03451fe80bb91889aec0b247946fd5b52a318d51c5faf64cdc57ef","signature":"5fbeb568fafddc09e602cdbfda7df5cd0e561ba1dd8443318f1bb3b586066c9a"},{"version":"390527c96f2bc590de934f2ef5bb5bb3d6905d6171bda8835c197e0abed15b08","signature":"0ce70420a3f859e7ada24e14d433bf75f91954ea538e9f25cf32a9afe7e86539"},{"version":"6cf137bc48f40ebfe5138b9005c22a2a36c6d0eae90e27f7bc5dd58a04faf07f","signature":"e3575536a31286b081d4db3ae027a171f9567fb73765c91c67550cd330650e49"},{"version":"656330b9d0697dbe04cb1d8b8402b3ba3953dcf48e4dea01887c992036bb173c","signature":"24733ebd4c83b4d7b05b39d79f1eaf60c6edfc8f0da5c2f848b01517947697f7"},{"version":"8c4c8c4467f9519b0878333232f88ea38920588b21fad94d09e7d191c1fac691","signature":"5bbc828ff668bd2cad6f88b3f8bc1e85e3ab4a84af3eae83b3931bddb79d5d5f"},{"version":"b7567dec5ce2d27ed70feca5c5a53b033bbda727b3d65c1eac4d5256adf09315","signature":"31f22cee584992be54d06fdaa9dec55d060c358cf67c4d162fb2f5fc0c98283f"},{"version":"7354576cc5cd9410252734f2a40c4fff01428a753a672f354975a958e7c63329","signature":"b4444c70cf4bbb122a1983ec33c297027a05fc1dfc23376d3b140745a58a2ed6"},{"version":"3e8f5153df2b58ffc421a7d8440d3f92fe8ed9bade9a7b18bb0ed161998b40f4","signature":"15c69a20c8c5420b76b7c62d82cb284a1608ad67c2e0d1a71e3e3caf90bc4201"},"4d87ee3b202e0f2f91804622d86dc5cacdf3596c0fd62e4debf04d02ae25bfed","6788a1deef524d1bb463645a178f02627169ebb47346eafb1a61faa5cb144333",{"version":"2ddeb4d8ce27590153aa6ee84b36bf9764700d7260124167167a2d2a32166bee","signature":"68597f354e4595d3f1f99cfed58a445a66dd9b5d4ed5d8133e445ee2a3de6cfb"},{"version":"f7f56d7774204ea550efee0d9e05494e8df297bdf32634dd601fef7fe45f54a6","signature":"1aaeb72b56fbfc7fe35e5c5195bc4b102fdcb25a30fa39a8dcf26e4e03afe4f0"},"2ac12549f1ae0aa1775782876baa9c06e9d845be26d99ce56a36276a8831a395","79e36ac740e122de9323550df934df06c908a26820f221c758dcc16beade6618","bbe55ae5cb40ed5f38ecbfe673ec070dacc7e0d55dd02472263f7903b3c5ffae","c10b1247cc334d64f4740702063dc4dc4251b96427e0d846b5eb9a7d0379bb1f",{"version":"672ec17aebc02c37f3bd6a75778652f5cfcc450b0b2f4dbd8d5821ccc7909af4","signature":"0799f99f4e37567f2fe31840ff206efb30c29b21bdb0af72d55aeae15c70760d"},"6c6bce5fd86564171cf1bfc4122e6b4906a820790b4097c87723c2fb92eca8a1",{"version":"7bd42610639a14bb0c854bde2d3bab07cce272b4f9699027258eee83d5c11a73","signature":"cbe7252f19d4397211500df1c2861e7c4ed9218b8d1614ae2d11ca03679f9551"},"b7b303f6ccc15e4db96956737e538d893c25b7092a159a39c0aa8ad932d3c636","8b2eedc0f7bacc05c6f0b56dc41f46d1b06ba1b9868fe0fe77e8cb22bef6f2a9","0c7459b35e2665327b17a7693b824fa83a3cc5647510a2cdf09a6635b4561c60","e0c10ae5e38df160cb240dc9e46ac464dd22ca7432f783f75d77b1b0e1aabf46","ae6c80232b4c2c4a00fa2f7dc51552a73683afd6acd88dc6c8a745cd39a823ef","2a4f83a64245f53cbd1eecade0cd429c73f5b4e992439b773fbf2e8680ca4572","3e0238000be6f6ecf94fff98c1c71072caa73ccf6c318c7b8fb324ff2903103b","d80b042bd5c32bd812910229cd6176855852fdbec7afca06d4bec3e1af8e1446",{"version":"622cd7a5b9b304ca18aa1723952f21ec5c939f7c70431c1063bc25e59a912dc7","signature":"90c7406dcc6fe0fd8b0fa3e23b8b1440b2506d841d8e629a6b1df0283c8fd1b6"},{"version":"83837a404834ce7ba3f2498e3faf5dc31ae0a5859cec3101d684f824f8cbe3f1","signature":"3eca308a8adead7d78f165d89c01c30c4dbf141cfc5900a563ef47bd2b652a27"},"64cd8e7ebad2b8827d66171a80c2b516c5a57a91eddfe3f9b317faf8879dad26","68ee63044e87286b7a2100c05437babf550d647e748e3ee66ea6ad4cb268d52f","ff60cb0d4b987911a9db25c4e372a81da6211e9248bf9eb336d2070b77771bfb","187b3e36e643d6484ba0286c361eadcf4d3f4174865c8bb2e92ede5cfc0206c7","b5cc0ba3df9e33e2fa3849d474b4c558b77c854ec3addb719919720786f2462b","996c05dee2488fcd52dea0baa6bb03cbcbbd451bf22ca0982ffc1bb412ee5dc3","e83b7266b4bc5653f60004a5a07e2dd1484a92b256fded2dc1fc65e828b4bb57",{"version":"2853d5c65a6ad064deedce24ab8dbf06aaa5ce9542a47f078fe02f03ac7cdd03","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3383d105a4eb14ea4ed618769f30b75f90188e7935364332f7082793d1196b9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d96eb2f5d3802c4a877dde7cf5c19f3e938d792a6c623e806c9cb3d64f134d19",{"version":"df1cb31463ecfb08b80ee1fb021dc44fe79934972679382b951fd13eded5d250","signature":"2af67711c0b92f1ec7bfe590266fb550a2a274b8e60fdf1a37d57af36b0bed07"},{"version":"40b19636fdea5f4ff717e2b8c783e06978d56ddf2e56cadc547203802f3ac0ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc0e638cb5f96071486da3fcc349b7f938455220ad96d4e80a1afb444b7fe0f6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b65293780e6f9a13e12fd16c069c51294f40e1e12a28add6a26d8205c74b17fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"82a1830c87e3d98fdd26f717ed49b8781d7fc773c0de5264e05e62640da8987a","e7b9731ae386cc1518aaa4172cab2116a0b1a791cd8ad34ffe09459f4574a415",{"version":"1984ac4245d99924a641f9d1833899c51fce20f0c51b713d21f2d386c87f4492","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"ab754ad0ec19423ea27bc7313015d6cf738360f4631148d2d49b9b87c0a46929",{"version":"1395fd01fc1397b94c1c12676294a680c57d649db4b3b28e1c260f0ebb541e6b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc40a65405b276cba2de5d724820da75d7e30c9e7d10e405719a7be5b3e31a5a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4156d5f13cb167807cd3b50f1ab673c57ff0051d3c5ba40aa2dcf95f310465f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"1cc522ad210d2c7dffe081392e099776aea1a12b7341387bcceae1546565fdc6",{"version":"0eeae146f6113ee176a29b1625a3e63bf9e84e3d15c25b672653db26e45d8ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"85251bb0af0b000acc3eddaabb09b481db2d5b09be42f25d52b056b966ddc6c8",{"version":"a63ab84a834b223bc3cd8224f1e39c2ff0f906f3c29375f1dbca5a34ea1b4005","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"093fd001ca343779855f7f386b448d526e734c0d8c707eb3b979eddb84d40161",{"version":"9598e8f2c6880331c2f57e6fe39fe65d279d5fcee0879cdfcb10f676f2af9ae0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2deaf139a18640875564d069b8df011081214018c145526504bf2e378c716a3f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"8e7a92c6c568e3073f67648be4f0ab0e8d77e36fcfad8aa97bbb268ffce6cae5","be1f6a316e168ee956b44f0e9587e97a5989614d65651278328e6de12800fe42","1c26429a84968da0f9f6818874208d5395d5d681789eb62d1e97874afbe55156","831663ae7da68955e3dcac239c6f0c4b4ba951287903427713c0e5434b268c3b",{"version":"a5032259dba8af052f8650efd4b8290d61a0213e30c43255cbae505fcf6d1581","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"a61e00af63164e828f24a3b5abb6c76837562fb18ad83607d245762b92566c22",{"version":"ec80f7ad05865aa145a03395e48710a388452c5573da23b8680f9fc4c40f7023","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"0c2cca1042ac885087aefc61dd07c98d4d763e1b93da979e9cffefba1535103e","90f918fa4bfc8a1ca28e5ee6c726fb4314e3dd5e4e6e5c138d3722a4406dec1c","c1acbe64c0dffe2769da1455ffa7a7a54c630553e711ed9ce686509d5a7ca22e","50dc2f59a00d680eeabc050af25b1e67047756935d858c7f1b11bfa25064f92a","1418e41691be1d8e5b6c0ba32ba0279999a75c035c1826b88ae914d8799ee8a4","68a712c8150b2351406c2564d71be4e6bcf2ca9d5d5a241e99421ecd917043d1","75b3fb36bd172a0191b3540170778693e0d098328f7f6b783d0155848717a104","c360f159bf7cc50cdbf9fd68912ac63bf5889b7220045435cc681b4fbe0b8f99","a93c39a33bbf74c81cd249032ab84d98f1bb5b86a5d557111792af7fa51fd3b3","a08f9d6a255a986f0709ad01ed4719d10a78c911442258e3fa586511e54a68db","0d74771d5bfa09d8ef0f129e8f5d5f64fc0fa44ca6e2319d711a301544095623","aaa89e91914fae87683fc9b47537b7561fe705e3a19ebe436209a8553a9504df","6d55514cfe052291428316f8b5ddd2620f161abcf9d180a39ad04f2572852a5d","e34138d79ebd653b7302f054d2f5b086c40075dd387ea3c02b9cff1cafbf66c2","6c6f6ae7a61d464a58070d9204181c34f88def3da2364ab213b2769fe1da314b",{"version":"40d866aa997f590716d7630f06025a53e7efe1982140182fef03d1971594672a","signature":"aa43218ec3abf932ac6aa3fc859581b8f2fd8c9469e3c919408062e57d232bda"},"31c12db46e3320bb3d198856123b8875d4c18a00a9e8e8e6aa4c87153954a24f","442f6f1df0e9859c783e9c1260324833376cf2ef977b0a8e7c5fe85ac45b2b4d",{"version":"727b5a06aa2c2d16692b1ff55cac347033ee492fb0132f3843133567175c5926","signature":"edcc0d9e675c37f8b8345ef683965422335c183997a5abae692e03fae3b476d6"},{"version":"104321bbbae499a49b02b529e4e5176eeb094395ccabb51475b94ee7ec3fac31","signature":"cd789dd692d4dd223dfd8938a1dfe00325b137c3852e6a85bfa9ace8ed00a10b"},"8d1549cef4bfbd34d863903405a6f4146fca4310628edf97cca7f43eb1b5b70f","90ddbf56e752ea3aac5906c0bf372a35361bfdd05e37674295b930d1a6145676","a312b9b01b548d6a5c198fba7ebba16e890e181b09441f9b358f082f927c7f84","db89abf280f68499f246e5e7aef6fb38059f8d9ebfc4d485e89441d72cffcda8","1ea8cf150ecfa2e7100ccb91fb039af6b12d8f5f022266a716f6c6d3d0564280","de3c3ede735330a69dfea482cc4d40bb5ccc96bca1ce3e0255cdc07e96cc93ce",{"version":"1d75e713898af44896feaec991b57d2e9a23e8790c7715eab5617b37c81b1304","signature":"c44cc8c4930b76d1fbf935484045f099a078a0e56218439da28da5d829065a89"},"a1bb76d514e93f8f24cb98a136465f860a25d9b413d9ee1af1016d703e515a62","3147101b0718a86a739558fa3218ff29d597b7c7b706ad8c4169c8c80c4daa34","73179d9bed7d28d279c73fdf5c7ec4dbc78493af676d383b3f3c5c35d839e7c7",{"version":"cfdec451e6198722f6f1a470ae1d702e91aba34c5a82ddc8ca2c46eb2841b25d","signature":"51e03b177ae1693a016731d78123a9375e88191258438907cfb8c28289ccb8bd"},"f73cf056e756688b97c5e8b366b8516a5cbc18aaf1d5278cfd567db66b4c77f0","2e818b0de54379a805ff642430dd2ebf684b6cf4d3ae133f12fe826a47eaebd9","fe83b119da0f5ad1d6de35dd8a8ff11c6d3b4f430d2ded235430d4cb84bf32e0",{"version":"993f4c89fd25bd6aa86e3329183f0ebcf30123d243bc6505e945c7d23213fbd1","signature":"076becc81584aedfa7349ab56ec3058a2c48e51aecc5ecdabc2fd4aee654cdb3"},{"version":"5c0d0bc099cf3cde30d02b2d11f7fbbb934c2434cbfb69d8d67595bb2ccc1d95","signature":"43b7a0a2b2def00095492d167729428073dedf1d85c28159254d3b64e77eb0b6"},"70b90a13137fcb5ceaefaec6c636bdf5ee4fec1b03803f5bf1d93d3443231741",{"version":"d9aba09758928ec2439f08c4736980c6e52ddf6a0cab476c161b3592a34d44e5","signature":"ffaea7fcaed416769800cd74682a38d1335953e1eb903bb59c22e45cced12b52"},"cabf742a4ea4a60f70f88efcc2d320b8e1560f54d7ae90fcb8c79af7a75cd920",{"version":"5b42e9d056ba435957700d0585861287052c08afde82e5349a21a43ae7113457","signature":"c892b55f40a8f35ede8ef7f1e0cdd1dfa70b22bee55d10674222bffbb703ef02"},{"version":"fe2e6c470b89b10fdc90714c6c734713dd0809189913130fd31fde1c152dd96d","signature":"d5dfca986d325fb72b02cb63065520cc0128b46d77c1c68441ca2241ce17113b"},{"version":"e221838e10f6d2c4b1fe86acc4066491685a9c6e878ce39480638cadd1fa2650","signature":"b98c55bf5fe063f227004fe751cd334ce9690aa25afe7be7570d66c48cf86e56"},"742bbb2ee54b65f16f77094b8444fff1e3f1c4aea3a2bd44cae5de3fcc369411",{"version":"dac20589f2919a63805df5e02ca738dc974c5363fae35c526eccf6b7f9dacca1","signature":"a6d58c8a4ac0a18d66afe6789e372e54e3663f5198753e6e94481cff20b7452a"},"be476947bb48a7e4e2a2bf100c43026c646e115085d076247f276f88111d254d",{"version":"a1c585659ad6a50677fc7ac3252133c90ec1d60d2f44a716b6ed4f945c0c337e","signature":"092f1a685f107b5dcb94b5d54e07eaa58894ea17312c10bcbf11921448776f41"},{"version":"0c0f21b2b2173e3f325fb91099b32f3e19843b80a92f4ff2ff5a3d4235afa72a","signature":"e5861628b3c26867cdd721803b8fd63ac2568b1a32ece964adb39bcc895a1ce2"},"f8bd37d0e25c4048cb2f19e6039b8ebfa0bac6d24ac8ba58aa0fa4efeaa571cf","09256332eb93d63b5de0c4c87a64562486589143068b03be359bbaf2038601e7","2dbb440d516e8a8107ae311ca6371d7808832cab07de9c432b60d3e3a7e89d5b","e46586b8eff1754102c56c3132d0e4622535a474a8a6b82f001baadaffe33779","d48210a6d909980fbe83eb6580fe3a2642fe743539c17cfcf8f89dbf7e8b9c36","b948cfba8edcc72a86f90f7ca9f7de41fe1777dc92405022eba2163d92728e95","268d9444d7e21783addb24299011460c065a57101057d5ce904524742f7fd5a7","b820c9c3de6cb1040413353cacbee04c9b8bc8dfa653a4aab2c25aa6c7c65120","5ec9b5391ad2f1fc9329b4d7f8684642d34f6c7fd339fbf5074ea3115ce9b5dd","615e50151e5cc86eb9c7220aed8849841dd2fd2b2cd6191cdd12943c5b95cecd","fac01cc464ca9dce1a0a9480945abd88a0098d2e8787cde86a848253cb20ff56","21c8e068769517198fb91373415bb22206cbc7b95021c213c4b70d9d7e5cfe78","f7956442417275691905a10a694cf23e778b1d4650fc39f23e4ea91435e92cfc",{"version":"f2b1ddea19be18b09439bc8c694feb590c9cf94ef579bed1e5d68de99fa895aa","signature":"ff90d881a61926ab079e751164b0087197a1a1a3b234431af5abc14235e438e9"},{"version":"9cf58515da90a71a9353678a765a8b6c94b63b625fc05bf5f9d691992d0c5ef1","signature":"0646461331d1a1e9f1dd6b22fb002a043259f5210cd693c5959bb6d1737415b6"},{"version":"b667b78d8db3056c14aba18e2138fd530838de4e44437153b8c338d97eb7944f","signature":"67cd5d46643ba488aeb104da791a837e1c564361ce47f8bf02f18a49b1ff1eff"},"9eb659b8534f4f030c58515fb79baff9b1f513df9a8c9916fb0e5a2023b9c6a0","21366491057467278d3243b28f9065797bb996a4e4919f1086e4e2710c9350dc","070a5b980cf70e9e54d6291f31a634d1346707662aa0b906ebb47695316d94f5",{"version":"cfca522a29f53430f1d0447baa732bf2fbfa5bebfe68d2e475432b228a496110","signature":"f8ac07e911fd9a3bff7d0a2cb3b8589e14a3b52a3d26a2fad493348f494edfac"},{"version":"431b70b424860910a8ba2560f83bd864a2939b87109f11ce22873964f1823b62","signature":"24cf0f162a2bcae8f5ae4b678da2ca97a91699cfc30898c606cda9ebec4d9f69"},"c56c127a7dc75963ac6a68383a14f81da3c0c9e3d8e86c24ef9e37ae0ed777b6","703e7b32062955f1941d78af2bf1a972cee1905d9f64c8c945e0307b71c6c8f2","5320b2c2ba15fc1d0250cfacd6deec6a1b242c1d03f5d5bcc2d7ea8186fb8787",{"version":"7b7a0b908bf6dbaee29816d012f9ef2ff0b0745ebe5977484e12ff0d2a1d4fd3","signature":"7b90b0d17d565ad2bcb84936503634ee9f77cf9f40b2353797af5c1dd26b6161"},"4d1f600d2d153c7c44e2ac25f5d64776f9eb7a52f1e92bff302e50a7efd08a36","043caefcacde199496905b469f7251c08948c4921f91e5a0f5c0df4f03cd2d55","d33e44d9c563ba82cceb0c3fd5a20de58d19a4ad63160482de55bd9c50c3ad2a","58cca2c47d5dd00d8585348eec7067b9e45f9fc89c9c430cbac18e2846c4bb80","a3d3ea65ca56bcadb960f2e884fc5a2b3ca80ca7949dd540ff63d00719711fc6","920099117da73b53caf5e84b81cc4d2200bec4f82e818bc23b7d079a2a56907c","7444ab226ecde90756e4e31ca68280797132b5c3b38348dfafbb101346ff9c4a",{"version":"4674b23baba8d8d1145d47b4d8db58a1161a0f0327cc5e05aaa3c70dea3aa4f2","signature":"597635cd2982b768c8075e33902d4bcad6b823ad6837b83bdd5df1108a8b5ef1"},{"version":"dbdf5c99dd4d0362a790a664fda2f7d80f0b90ec20d2dcf9f4e71e5d859ee247","signature":"7b9aa1a8a9728abd8faf699093ec32552e44ceb2e3e4eea7fd39fd4a105abc61"},"7007c577d3881953fee9f301de570abe4ba1f6a54fbe2873968dc002ab5e5629",{"version":"cfad3779365697cae7b23669d57fdb286de38dcd3c9b1fd53689f9ca3a91a2f0","signature":"51838b26378f28235d88da3177a2f581d6325b1c546464f2f5bcec82149eda0e"},"a6e432450d84b15cefce91791dc06498a08e357e05526537db4bc137807316d5","2cdc33138be52678761d11065245a401d3499f110f502a2cb34cc2632e9c5e61","236b3b7d3b7a86bfa27e5bbf1998dd7a09b9b6ae3ebcaf1040be305324dcb5bd","e22bf0ed5f47dd3f92ca02adde6e4459657a649c00c57071c4b401d6883009bc","e420009e6a6660fb935064b5233cf09d28f28386810a62ecbc0c42044d5e97a5","d592e7b22c830bdf0c8da2ee4c4d5d3587675ef62db03bdc0c78df8e7f7b7c80","19edabca93b6826a91c26832d55037e487218d8f29f2172917ef87ff08f8f380","fae8d4ba3bdfd3f087c40507d8748236b0c08aae3d74c706651a15c0e27ba16f","9041eb411777fc80385d1b639173fbc6675ad3d7cbce257f52605d4b18616543","3aaac2c7f4e18c47e5197948b4f1c4d1d569257499c1dcd2395bcb15849fdae4","ced161d675dae30f24f7d001a14ad62504d69069b971753e9a8003b2200e7cc4",{"version":"6b76f984a9fee625d81fe94eaa63de765b85b492e2936255608e9935f577df97","signature":"dd0fdb6f0c71a53e434d39a22ad54d9d196de67178d156fa5b13df073c527f19"},"22772822785aea051e4454632aa2bb73baab3d08d48cf7366cafd8f19e1e0c4b","08069afec7cba0f29e89b4fab6af533440854664607e1a6381781df96676115d","cc5667037f805a2921883f7fa5e091aadf2ee8907c46d61a7d7992911965eb66","82dbb5baa7af6aca1b1392a81acc3bbbc07f50ccd8cff2b3eff2ceb1c5db2182","fd3444a7b0304c83565c7d69296748987a09c2a55377bc9e6c4d32961f8c99cc","65f0fb9a264a44649bc963291e7d6e810c6c06350748007de4e1575eaba8d319","f40778d511004eb579d576fd71059f3ae2bad589d17872de170e632e0632f4a3","a1e7d896074ea540edefa896e31c66fc75904a26c4b2ef701a93d87a83376ad9","8bdbddad53b0b942e2bc6c3f2d63a6a3d560dd239e8b30c69805367eadb090e0","8e27dfd3b35176a3a2e4307206a9ec3909995b23657330dc835e7b5fd50ae89a",{"version":"72ac7a0ae5374dad1652ef8e41ef145bb371e9b8af2394b91a3b6e0220b5f39e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"8a9333c31386954706e26b45e586e7e05f604d04bb65a345ce2f47e56b9352b1",{"version":"5900e81e3e81b42e25cb9d29df5a19a255d5b3609ad86f3f6da40c3c5f895a52","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"ea3738176afff87d2d326835927fcc4c4e3b561cf56da5dee6959a08458862e7","90e42071689c6160951272f117347d7859a2ef54dc83f3879eacbffcbfee1868","faee2c92e93b04bcf0bc3cf951a6ab15c80022850773ba6721dba52e84a5ba41","e353b7b008a1c093f02f51d6c46b2c1ef2c28fddbbd38889a6a7e4c224916779","e60dd86cf0c509da27cdedc9fbd456d02f145c04af44fc026d67f1f3ad4d4d79","b858241a65512e697bd928eb1675541fa5fcf2b516aa837f90cca9ccf23162f3","045b680cd4cc18bf4d40193feb610f9692e31e055ea84a89bac2f417831c7ed2",{"version":"e6107e7fa315d770a69b9edc7dd077036f115479e102f2380306a7a92629329e","signature":"0717bfe8ecec022eb7f964ce697b1b0d749e217864e7a11b438c462ca1e55412"},{"version":"b26bd1398c6f71549fa23175f3c8b8245fdd2d2092a6380794cf8ae8ee67666e","signature":"99173fef2fa963dad3c3a06cef6bff8e4a9e4fea656ec6414aea2f84126ffc23"},{"version":"002de79e07e5851f180fd44a17d1f855b5a17fb9a00f9a17cd53ca055d27ab8f","signature":"08e14aa9343103c4781efad6b4d287b2d577a8266f51f389b2ed4db8957cb5ed"},{"version":"ca319732ee32ab8064188cb5e284f7d8994c1e05e67bd2f7f55203eaf67b33de","signature":"0de5e5a2fd2db15c16147aff67475c913395e62c14bd7c5313880b001a88e009"},{"version":"04ff795f13235dcc2df104c2363bc370338976c37fd408129eee133fd481b1b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d043ae7012e9b61fc3a47946b043e6feabd01d8a1d43f5613bcfe1fb1d144fd0","e48cbc2646a758124caa3c87b05a722e8da250afbc6fd5f4eefda072772b4616",{"version":"534cced4db5dcc639cd555583be09c6891c0633dc395308c87f60b47dd54a6b2","signature":"33ecf206edccc488e96cfb5177f19809e8bbb549ed0e94ff66d1cd1ff1a1fcb3"},"056fc04ab05389b453bdac4ec2e3c1eedd8bb661c20c9fe2e184125c8d69dfb0","cf4d4045a6ef47b776863026fea118f50fefbf94bfaca15b330d5c939ebeae61","ccafa0cc21d137d4d29093eae284e4f38dd4c43524f9711d9976b29a4a709b99","2d3dd03df960f48735d9ea246405ce7f2f6501675599c7342965217e6873ac28","dc2a05a4d3db8795c9c161d8e05a72d0548cf61e5b5a1e992b958d287d148f20","2c008ab0e0b102c7d0752086dd258c08086879c4ee036b40eb909f53cd444f76","9c5cf595d6470b5fe3b37de5815b5a13b155e3e100313f9e30f5e8728dd9b055","3e5cd51e11096be95c01de7fee203750f0b365f46dad987f3afe9fb535b99122","e50fe642408753de3208274d3a7c83c42bf821821740b79012eb581ccb425bd9",{"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":"36eafdce35542335372c9104a44db2597b5ecbdb11af1177da13d457efc94fb3","signature":"d6ab9d3f4bfde85a62fea7182d4e68ba2104946541f47eab58799009a38ba2db"},"d153e1cec75d95055701de32dec8d0ba9c9a89ce85bd371b7d51fa15e495137c","fbe6ac1edabe62f9565a5dcf644865c3b839c759c6b81a0991e7314186c77c14",{"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},"916fafd410b9c3a04bb3720774b0cca93d1dee94bc88b4ecb6edf56cd5585abb","ef82bdd9d674d855785bbfcbec2181e8d602c430bf73b4b65ef581d78ecdc64a","846affbec83fefdf905e16b3fbdf845edaa248b5895279498aa6ac733ff2a4b8","227f3a03b267191752ed1a2381855cd73b0915794ed51151e5ce82ffd786dbde","4a700720ced7ebe4c0c973bfc450c6a7ae31f82fd447e0f464c7171562e8aa53","b69c8778c50bf0caee3dd1d2da2fc7d5f6157498b51cdf51fac81476850c715f","39bc5068f51c657236fa9a763dde5bdae05b46bdd49d0390c4a72fa9dcb45dbe","2a6012a4f4a4695bc0a97d29f47861bda054359a9a60d295ab26f416f95e8940",{"version":"094e1a72a14a0f38f950e388d9a4e8f6118b493a5918235de9781d5c47f327c6","signature":"d7cd6120b5ccddff937be1aa22a538829f8a93ffc9b4715519f67bd21da26689"},{"version":"60523c590e7ec5b89c49c0728f8b64ec2132482709ea5c5909752d6d68c93401","signature":"2315efae7ec760b18fa4c15f987003721972b75388eb00f80f3a419e91159751"},{"version":"96118b858afcbe20db893025dcd75e19fd530bd5540c65029500e8cc251c34e1","signature":"bad3fb3837da6b89c49e110430e58827c321031273ba09aeb1c83a1e0e9dec70"},{"version":"17ae4d2c0c3487e077a7f6db647df1ebc56194a135262b8af15b19a3a1072452","signature":"7d5919c7ef0b53f308a78754ac25e352473a2de6bf6e25e109cd03dd493aab54"},{"version":"a389c1e6da14dc436285d19455229c3ecb445f0d26b4de5e4df0c223e43545c4","signature":"7e2734061c31bb7fcc162aa37af53a181cb8db1bc2ea1168ecf1c816cf52e045"},{"version":"c40cca5deab8288e95cacc2e5f8d1d2717f9b49e3617cb3ac992847d5a143fc3","signature":"d23b1f070ca79bf4cececb66c23b78eb3f35e10b3ef7d0119549521c9fd2ccbf"},"be28318b8f96ff27ce32fc1882bf6f18e306dc8ae65ca3361d7769ff98c933b7",{"version":"f8fcc667b4a4cc586bfbf3d76cc17e91bad6749ee634f736ca957ea7377cab3f","signature":"4f3963b6ccad89bd71ea9c5e491a83c9b448df7d36a01ec887aea29400c52cdc"},{"version":"fee446e0178c52a271d63c9d12598620eeba7a0a0178def71ab7eb70837d7f26","signature":"5eccb4db63e70774c70de6e6e6f67f3f4b26f2801767073541a772077c2b8458"},{"version":"dc1d17e168090dd4df773ba839410a2e106ff0f43163a994f681a0044b4de578","signature":"6113e636ad4fffa72648f2a41eb42ef83262089973d54e080d130cb4219dab4c"},{"version":"01ba304e845f2081cf6ce244153824e727d70d9acbb973de8e2b6340e4355185","signature":"9adbd23317b76a09a9364daa02f7ad358ef30c277dca1b05e8df356f7751702e"},"e8ac8a0a426c433de3f592188e1fabc47b43cc63be440be87f36f5f90980fc56",{"version":"5e8cfa753701ab1bdd8545e9436da3c53f24c179978efab36a5d919484516735","signature":"03f8247a05f82c8f96aac14f06944d2dab5061d08fe71d98e429251144b7d9d8"},{"version":"e9ff70874950ac2fa288fe64a6fd622a06d3579bfd781be96bea79fee7fd1381","signature":"1b758ade259220a7723152591ae4997ead9ed62664cd36c08289f94fbaaa5511"},{"version":"574fede341569986c736fd5468e28194913dfe16685e804a5d5fb0a24e71384c","signature":"cb0d718f6419c7cf0ae1316734875540e46cc33e10e9aeecdf79e2f29f6eab4f"},{"version":"2a02d329b308ce5a74632fe2062c72c049e672e5941c5a8204bd14408859c3b3","signature":"291d5759f6ff0d7180456b40f53ac4ec52d6fb91b6688aa2748e70feabcc8124"},{"version":"3970978fc2242f1ee735a0b0f0dfa8a42e17bf5bc8d9200e4e7059c63877f4d9","signature":"f8328683d5b3f602c387cf200cb1726c422dc90197a7fdb0d578fcb7c9bc6786"},"5d420f3f67f2c448b28bfc8a6aaaddfdd5e3fac96381e3764c75cbb0540c3211",{"version":"9607e2d3418c1e50af1dac762f78b031f5f9c24f13ca4990b062f27c4f09a340","signature":"4cb3d1e907efe7537c8b4603e87bba3e9afd8e3294a436401b5b95fd2bdebdfd"},{"version":"3f9ae10a4a447dc5fd8d079cdac3d973bfbfb61149d6d34421ca8ccb9fc25a8c","signature":"6fb16d7f85050f01ba2e8248d33306db324277a87040aed2ac58e20343a0c2ac"},"d8a627c1f6473ead38c4a7fc6c22a1718e4f4b83855f85eea45cf645aee63cb8","7b37c073829fc3fa3f22a6252c214e944b7c306e8dc1a4fbfbc5d6f2a2f95c5d","7ebcbdfd5763421e021e5472bdcde0bb7dadd2fc6bb2d81f70309a89362155a2",{"version":"826e0ef6771e8bdb186b153dfac8f181926f29570c7443560bbb665099eee80b","signature":"39dcbe7a573f3d3df729c6028108cea477260aba94ca082a95de9d02a267ef27"},"f955a769066260ff6a27a22deed2c93ed071342093caf86e7a6309d35eaaa480","60ba574b03771c2da031380bda16f8ebe86e64be2a04f31c53d572edf987d8d4",{"version":"316b866c3bfbe957ec585f572fab4b2f7a35e8d9cb266dffe597e57927a5d66a","signature":"19d7ddc11ff468813dcf97fb05f4e51d6f78e16a0030933a608aa0fb9f2ff9ad"},"a17a4fdad4f5f7be2b342254233644413c8ef984661db43c951953933083d8e9","db6562108a47f4a746b4bea1694912ec1ac7ec51b48e3a31b274b4c8102ab772","85592302683b0f3d636e53a571bee7fe59803339b8b3eeaa9a5f3e43717bf81b",{"version":"77e9654c2e90c0915a4894800e66a9c269ffd3f0fe06bb17c14bdc23ef7f5d1e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"861c7b678c64d3cdfa0ad2a3f529dc1f57ad0252f6bf7db739be18e14c79c617",{"version":"8ab61002066e314d8b266725a4ed3db857e41c7aa11927aa4a38386370204af6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"62845c5b09ae355ab3bc4c4745dc5585b77b447706ebffb09ea3641e5c963da0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca973ee48e138941af5747d17cc31073d1d08f57241a81b8a6370fba035c57c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"56b2d36623f14185ef2134637e9da591a86b6faf40b78ebcde2a390b6cbd5b54","a2cb0d579cfd7ee8015c6adea94ddfeb2d7e79c040ae9ea9b57275096512bf0d","1d95bd896b6216d08fbd7ec10a33b40d09d711e3fa102786292ceb82e4b8193f",{"version":"6dbc20023316e17c6ae6382458fa64ee65049a6367dd648e89ec443cd59ca18a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"5c6eff88e726a6c9ccb73bd9f6b02dd0e248fba87dc47e7b3e211f3e9680b24c",{"version":"31cf5e25aaf868c1ba0e195b7b62f5cb516b70a5bd6e2ffd8eebad1bb011c24b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"1896da12486c6e51bb02c20eaf22d1826fe48349e584f2c59c8506e925172b44","8724ccf801c593b9a763cf5949039e650f4c7ca57fcfa045d295e911d03f541d","5903b40fa3676f924372e37bbdca65ba67e3191a92f52852d5b70a2153f664c2",{"version":"9b80069148c23348d866893c51792242c00832e28ee92964dff0c305ad1ba8b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ac0bb5930db453fdd87419f223d44c23e8852223300428032ca09c4a497d9ade","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f3acc54979130b5a6786b3cb3a1f47f0330b6acecf6c509a09c071a1760e3f09","signature":"73351372b4295fa8b882bc93e30276d7a911cadee0f013b17f66d50ae3de6a29"},{"version":"c7b6e3a82a16fd54330388cc5023d8686071c102d3a4cb1899a74064910e7704","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"7739faa1d7e4719d14729d52aef996a6d8c8b1b1447dd9441728c642f46d4f79","d1e9b0eaec1fc9821665f79d5cb10b16f5aedda997b77f67cfc634c219be45cf","eddd32b79454e4df90e6e3bd8d43a997c8813aa61e2be0c79875c87a5d9f7b2a","2c217dd4af49f75ea5671d76d7129d3d3154589fd0193c323ec2c687ed13ca62","70879fcfe03c15515033be18baa3afa57f4f4a6d6bce8801e87050b02e04df55","811af3b90fea77ad0ddc26b1a7f884d9366a44b20efff4c2a15de5bc9b35bb2e","fe3c52844859ce7b95eba27362fae54be53773604213757448c2bc92760f4c49","e292ff26b159b2acb49baf29c18d233486c78afdd409e727b71ef6cffd21378f","4ed889a15e24a9e0f20d3642768a080cbf47254ba81997f4c88a37d1a7d0a7d0","a161552e025ab65f8b854f9a3338f8c69229c0493b13c323252d07407ecbb1c1","154af56b732ad2cf00fb80508d1f3158f0497507c9309670b66758fdc0461bd1","70e051b3ac6969f054669d0eec72f57662efbeeebaec77174e96cb91dd3d7b9f","c4ece3fe232b07819dab6dcb382d611f3b1c06a6b93cd924ef7d9abd8d090d10",{"version":"1747682b50a243bbda982e8ef09306e5dc2bf9b0a0def44da795c851ef31d6e1","signature":"fb891c7c97af75b1638fcbf3b1cf51815a1f7aa9192f202bc4fbe295827537c6"},{"version":"8acf6816ce4505a5ef68bb1ccd8d4fc30815a83e00da1353b90102ae5160da81","signature":"997cba142aed5347c9d15f4e15f6daef2889c2fd037587841778b8ba476ea168"},"0c82b7ea29dce9d9c5ad81687769d14ff730a377fb9bc3c03cb16fb8ebdfdcb4","80c8854964c4a39f42fdcf47a985104612a776c8de5b7e08e929c4389331a06a","ff619d9cbb2254ea51e7d71384abbbd5d72f2c93c071fea9c32b64ec3342888d",{"version":"5c0d00b05aedbf7b0bc483dcbb388e94b5948cfb1fbff930af60dfd9298dfc50","signature":"064b44fe73f0f7a084ed8e01bfb2ccbf0a6203b191fd521fc2f9c76d21e652c2"},"e89aeb89eb7cf6060c0af880e07093c91b08938d8b3a82a8a9b8fd5ae1d056f5","e8373b5d06c8923b34324f9df29eb35bea64a6a995b607a0b0fb2fb8c3a3a140","6be1b5921e30052b789c02a63eda3517e0686c0d8e359d9ba5bbbee4e738d1b0","0b208ce8494b358652ba9030cf0e14619451807547d25b3b5b720aa57bb940cf","d985bff3e70be34ddba319f5e9209e8eb799e392218201acb3afbd77b6ad4d5f","5f45030e7b52dbd77b0e101bccf5bbc08537605f8fb10927b0281a51fb2abbd6","6897d0d0498030dd4d7b6190a78010c071e924f62811f51897f63268faca2248","cbc7b28d500a1738964097922cd6c6db2adb129dadcfdba9c1d56b77697afbfc","a219c1949667d439c27329b94cfdc416e2839e8214497fb621c491eb24cf3bc1",{"version":"20769f36dc2e033c8fcc237bc2d7a75682dfba17022efbe30ae06f22767869b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"05a2797ae1b679bba91ebd96c9fee9bcfeee3b3dd3e400ebb3ddbedbba606306",{"version":"ae2e40a1fdc7fd8cdab6be243e4541f50b54445387834299471885785e3b2489","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"96fa384dc9c129874b902106257491e15eb6cc80bf921cbf2906a779ac96e60d","db3b9f398ad210cb961c2b5d638e28f99792cefe9c50c81ed383d2942aa226e1","b3277a8f5c5b9d50cdda98e93cc145820c3983f5e8aaffd31f4316eeb0ce465c","bb6288a9c750095a16037444e6026de8bbdee3e77af676ceb41d4ab7a8aa465d","73ee1c42b6c6c78c5d03a0c111e53496a54aa3505a78e452f5e306b84f769812","ab9cef431c5ba3ad0da377558211af661ac8ef1b0e3bc5c66bb36f4cfc3ad177","13d80d19b3c6cf01d42d51623f934ba1ce71c75aadba05d91d0d67860d86e629","f2b455abf7da931e2c6af9e90e22ace14c7f357bd2cffbddd865d15b442030b5","70b399f171d822aca03548b1644217d752bbfde4d4ec2cfed1a214d7fc79840f","d02be3bb5d64b49b5ba9e768fa8448b5c127d09e2b8ba14026773c1fabde1592","a88dcc474c044ec5c3ac8536ae40771d408085bba71d322d73bf2204ea023dc1","b1d512503e816355be4952330e0a427949fafd8cb3ee124017b7a535dbb26209","842955471f601e4c1d21afb2cbb3d250ef424ced61416e8d7ffda5addbda9eb2","493e9f05ac502360eeea2d5c72d28984c6bb2e03dd0c1bff35e2e5265cd8a6ce","5dc03bbe2c52976d8b054be1fdfafa1b7e43f328bf48a19d5f62f0563dfee905","3e06d8650c98a672c6d811bc035a7fa2561bc2c87ad01172e5df460a8629d489","feb5cba45f6c40b8b4601f40eb48697fa7e2f7e3db51337f15c308cf2800da36","c0638b8d32100f3827e3535c4307c24b5ea5e7ae6b33476db318a8d706386626","0fb10fe09a6f0c5fb2f5f7bfc0855cbabc27cc4fb9fa3c56e5956f0673746ddb","7b0daa318777bc57de0c9198d4ca71d7f1ee1e3f02c5bd860ec5bf390e08fce2","4c5ca16923d08d3df7ce8003095ee7cf136956b93a3a87d06e046c967a07d379","6ddec7fe4e03cf6c98a431bd4b7998cc9a11ad1f5aace1c73f6a0784c7c9d503","048955e8ea4fff7090afeacc7a8d9a0d843199be15fb1ab402679f63d2a18a54","ca2ccf002342ee1f87be1682f2aab080fab7316eb8f37aaa6a76a58859b3de76","e85d9d5252dfb1dde90672424170e3b89cd14b07086f790d3c45aa3f023a93a1","789e5210de191f9b2c090a2acc40b4d8a1e86e02626cf05cbc6b60079b132f3c","3fbbbf2353edb06efd1d6d25286c1cc267f3346dd7a73424e35606ef0fc04eb9","f8bd8ba1c9d155a5a5543a28f8b483a2a66718ed4320402a5a4c4441628ca0c6","12f01407b6072b7e3a195c5c8e6148a2ac2bb0b355e78c6c5aa6284d99c4fa11",{"version":"e79eab381e519df4a338ac92944482de36fbd094b1ca674b8934bc55c92b25b2","signature":"78fd9f116c4a198c60620ad7374fbf67fdc41baa66b8da57db3b80cb6b23098a"},"acb0f18b8895dc2544741426df1c542d8441deea13b0fa5445d83a423dfcc4de","a50db966163020665ec8a68d0ecd79d8a9fb0d059c0f4d25ba53bdcd7e43cd75",{"version":"5880d909fcd7aa478c019c0916f68012f10427b2d90d203a9060517bb9ce4de5","signature":"109a47009ff1ea87255fbb9bd75f5f3a918ac7ab44ed123b521028f580aff53b"},{"version":"707a9214ca48e106978cf001b80c3f53e77ce04dd6b447dbc0b9c3b53faea3e0","signature":"8146af3e7bf09f628b043910cfa6b72b86f8109aa7b06167b9eb51a3f0a75da4"},"d97193507f74ca55d696adf4c7bf4dcaa581cc38da8993320385450a4837b988",{"version":"b7426a25a7942fd04027ef39d6e57d3652de5850a59c04b7a3b74ad2f335db99","signature":"ed09ce0bd7cf961caa2bbaa0265743b1a22acb59fe82fc227698550a7f0b1e14"},{"version":"d7e15183b1073666220cad96a18914084528dc05dc1e2af175c863afa3023e07","signature":"7afb481364c9e976ea5c55b9b02006f2496e68cc009eedc57f38264121a77836"},{"version":"b56e4b6d8d241dc9428b20e7be5d13487de4d263c5999f91d547983fffd8bed9","signature":"91ee1b220ead097d3cc5b596db9be622f0dccd9c05e4f4cf069f2e1db077511a"},{"version":"c5391ca708a239529f9f132919def5d73d4cd67786f87536da7e539d247bf149","signature":"44248c8a13f35779d07d3168c64fe9a1040ea2e66bfc4ff92567095c5b243e55"},{"version":"7fd84794a97f879f03f3067cc042ac622063d821e7b60b27100ce300bc65d833","signature":"78361a8f013fc8aea9c04034475febbc49998b63c3fe09f56dacba1e1c73f8fe"},{"version":"eaae5968ffd536215d143ee0c4a295cc4ab730c6306c0ff39da500a259fffe48","signature":"11fa086538a611fe1a99a34d1378e2579a4de6eac405ad7fb9eeaa51836977c0"},{"version":"169d8b256bea5a05efb2049b4bf5b8d916d986a97fe9000ad3af60c1804deb62","signature":"6840721f787baca46b15289facff041cf00967e6d746b6e2fcf657881b6e6c5d"},{"version":"0ad029b491ecca9c3bc7994015f376562f9fe7196e2c7815a7e7914545fcdb65","signature":"87d3a353f4a5033a14c02bebecb39e225f521c82a998c294c33481b9c5198271"},{"version":"505f10cf78d9caaf7df503e3c495055785de4c93e0286843574106d787d9f97a","signature":"4929cd61e267755bf505ff0a66adda55af5d318b84798ab1b46ead808203bc59"},{"version":"cb431697c9e94cf9faf8cb15dc79c36f21d951f2ae68a6cfa106b93edc373044","signature":"d21e563fc29f32dab8756bf5797d4c39b98ff0828fe02f8b766a8cd0f2130729"},"3c5a7c91728aa49db1d5eadc0e9f0d724dbb50b01ac203b8c577781846962d23",{"version":"c5e33bc47d97c9161f3cf286f89238e4097589e4dc86632a8a575135353883d7","signature":"bc3dc9e5cb7a7493571d35b9b2fa5a1f39cc7ad76f998ad62e7a98b56fb8df6c"},{"version":"a422e96804615648c7cfcaf2e23d5353ce5dbd305ef5f5467c7fff7ab39f5bdf","signature":"063c721b1237aa52f454a374210ba793cc38a5267af12e5f937c7f36bb33b6c6"},"0ef1d6c0063b12f4dea951dc976267bd8e11aca63fddb3aa10213ebd2abedf04","a1d10e7fa181933ae7eeb34361f76d99ad2872cf6da8542528df84e4311da86d","db4c881c4d0036d8676e76f60ff17c6fcf240dbea3e48b5961e17d9a3b73831c","938a30c9758bf74e9cc7471ce79996502c99446ad8c1c06d1c86634584ba939f","c82897568bbe3658edfc608ed84b0615593c444b3dfe66fb884fe1f6f9ea0254","163c58b665bd8dd47661e39af68de9f625b3fdfe912b4d3dfb9eb55012a6ab92","cc1c0d6d5a958523960410c45f1e15874e8d8091120d3d7ef90f6d510b00438f","47670cf66cb61194eb75ce6154b416f839364bced965df413b466ddfd00d099e","e4446e6fb84112ca5eb1da220fa4a2b59fc834a162499e81e1f016a9f3e64707",{"version":"20fdd22451018bcdf123b42bcf8f3607b54ec5bfc1a40ce6f3aa195114fee50d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77aa32ef7822978656a1cf7a8955056e16072d0b6b3c71c8fe81998678532695","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2bd3da8d18b5839c651f5dfffc391a3f583de5e4a3d7f856d908a60f47b04ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c840fa95d2134e19a21130e67e75c7d75715d95f35921d62b1d50262d7e34cf0",{"version":"28e48ac60dcc1bacd1d2ff442848e81673dc6e93012853ca87f3ab0784ec1ab6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"19f9e9dd7641c80df2f21391d85a5aeee1d5d729dcb599f89034977bedc50b3a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c8bb1a6ad7f07b0c3af284d80c5b76724ec9b6c2dbc1720d1af4018b571cabe7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5585e289b764f82b17802e044380f2f72b584c02f0a9e5e5f9994fa14079179","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fc40f72f1c03ad660c6ad52cc2ec092594bd05e49bc5c960a4b0d30620dc55c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"968339f16a5177a5ee35cf9b77108d92938ec1da02bd41e361585030b4f00da4",{"version":"bdcb2c9e692ee3ee605a7704fdb479fa10ef6d4271ff6b9ff995d355d40e2206","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"044ad589837559611012aad8bd6a946acdc485aef131a351c8a01c1bcfad9db4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"53a1989193c0f9f558c62b7eee59b3ecf57cc7c3bea2fdd469ed4fa2aafeb0fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5af67615072b85cf169a9b15a5bc2f54f874f32ff594fc80135b0229d46ed148","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2b3d3b1134cd416e62ad730ba82293706888320b0ab860aa34a61c02aa48789","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"eef952dea22ec228c085f41b939f8824d7a8a9d5d53edf570d0fd162be862e8b","41c362e66d4ade6b4727a2d3dcf1a3249ab336e6812cd51cad747284736a3610","c83c8f01896aed99315ae67c6fb0a5c948bada628c8f7b19665a228711c2d340","03c10706dc050b16e0ab8f3c5adde2d44fd9c4510394ded88c1254b29614bcf4",{"version":"14b18c1e8cf5b7d1f6209fec9b448effce1cb2b878b6f9a818fe26276a315778","signature":"cce85e3b51a75019f9eba99a92879e5f990efeffcaa2706e38b8d56d9efd0a0a"},"0fc0b0f4d12ca9e700c29966067ae7625216994587ef69f173c56df4e531e166","9d2e9036f50ec7b8066dc9536bd50a76f1a2e503c4fa7ee1c92725b694600d94","6f13ff7ba32304eb4b4bd18abf9374b3b25a49146bb8b4b2ad801712dc384708","cf6a54f50ebe9b1fa179e3ae972e17bb5132bc1dddc612dfc2d868ca309999d5","6b3d4163477739b98bbda0e3722c1df15427f4fcdbcc044d4ae093622fc07691","c4af4eaa49b5afdd70def3eb9ee71b509fa90dec11ea33591f7a1b1822400fd1","920205f073d9e7a7cec8b42adeda1b66e3287253232703d75676ae0ee280ce5b","8e209754fc98efbd0db41c1da78eb4f46d55d6da176ad9c6988b5947c02ea59c","0bd708369bc7263c061b5ad5ae31194cc55010bb069d87ece21a0d54d2ec4e73","cc0c38dbb4436cef6d4ad0462c0b9230363a23303589e36042685c1132f33696","9a999f9be568ad3a72ecf729bcd348b4bcee26719790f21290a16b5bc7dfe839","fb37ccdae5fa6b7325f5aaf5d1b28caaea4c148957839827fc5b1f7ab2b2e2d1","177cba3134e2dee9afd65d1d508127f10141c81769cef693f3493e5f691892b3","b54e6b18ed48a74d2d6129ca2ddda0aff1c30d2c46e7640113d2fe6669a5974f","ab95e9dce490b100c486fcc8da962a6155125f7f98f2b8fe34e53e68cea378f8","6b34a6f3217920c7cab89d81ea67dc64b48ebca1b4fe06e38852af49ea78068b","8a354ab401139f416005ba61675a503152089ecad7ac237da3d508779c29957b","76b797eb5bc8fe7158378f9ae1a16f98a76f4963b2a6eb40e1afce5f7574dc6f","32b69c9d97c045cde841e4cc73b29d8a79076b995f19dacd96d0525a1c46a35d","fdc0244b111f72144b4b5ffeb4be73d77985a2c9839d87630366739702a7d069","2c7d3b8e7fac27fcfcce5e3b5a0043f58baf786e20e951be19087e05956faf52","feec6c48848e9e9fd2cc1dce253451511a02574223035461557a4bb97f173c2d","213c42f7d5367619fc1f7a022520c0cfaf8828c3dd910d8abb68b229c44f97ed","fe4805a16fec6d9ceaa3834ce1ab4d8d3ec80c3c41ad093c2d09e7d7a00fe81b","ebc3418849ce69c3e4935c9c8ae98abd05c1bda372d9ad08cd259635d6bcf475","29775ef79bb6d19d569a24c59922476271b093a1afffb1678254d66e938e6980","9e44fa125a873ec1319bf8efe11fc6c79ea5d692b7fb5d628f79bbb14dc03e0a",{"version":"bbffefcf2d2194e3c9cae686f981935765cee13a5f390c97363fed32cad90d63","signature":"c10afa01e312d1ec1d2e455117340bd869610913a3ddee3e1903060237b2d330"},"57771e45f6bcbfb36dac19742e8984372065cb0ca9d5339ea982668171da36f1","754e504a4f7e802cc110ec7cfab158438903677b6d1e5320e3d06b4215f42d7c","a3e35d26f2d2ba764a55bf9af3fb0c22806c238a222679a83b40c838c30c7499","4d1729c06dd03ddd24e92985fec6aa5863373fdce658884e07eb4827df021f67","b80c68e22c6ef3a8c82b3e48dece693fd7b4e628542ac28b02dff88b31385882",{"version":"22f5bdac2994c065f821a3c19074445873b02b4c89c5c4d26f95fb7319bd7298","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"da3a4a4651c78a13301492b76a30f6b89aff6919a14ce20dc48fab5473b99bd0","516af411d9621dcbf6547314236500360c2076b4b2fc61a593b09bebe1ba6e1a","9ffd818baa22a5a4a3494bda2daf646849c2635ad622ea25e34f4ee2c9a8f400","3efe902a8539920b21bd44d2d0bed08ef8a95d3c4601ede6848a192af8563536","0bbcbcd6dd929d9dad0cf660bb39c2c578888071f5a7d80db51857ebc1c57923",{"version":"b82cecf3c9b8434c1ecc9158fa74592c9429a053696756ca6b63f53b042b98cd","signature":"7199bac5eac9213b52fe3a6d9481a0d20ab76d2bb99cbdafaf6ead4e5914e7a1"},"001ec942abc451470202c4baf55abb69d0ba41c1e6f4cbcf39aee73608dc16d7","1ff67eb52f40826c7d5512f924be11f6bec373c92896df0df557a13a8658f693",{"version":"7025dad7d78fd9ad96f064ff669d353f930ddddbb39aa3c4984144fc6760118a","signature":"7d3b48b39ec46eacc882956538307aeec6db56edc31f31be7d6289ec2c92a385"},"8a9404494ea982c2bff41003f8de1daf258f83a54e917a6df73e6a6201862cbd","b1f167490ed130cf9c920ee60fb21e9dd2ea9e601e9567e457f609a61f2f062d","786aa97ed22b1c1aadb445ee997a12785c863377f4dd4a45365a1a90e1bdfe98","01f31174c59202f69635b2957a7a556a01c2ea194906befae45997b6d3c470b2","6abbb171efa9fad3d88c9320ec5eccb199b726f832482379414fd55bdd485a66","08bcfa6546d768789b5134c6344f18ae851abd4513e3431e91b5e955f07d7eb9","d9bfe44b7126fd3ce4741db90af68d24cf8a56104826770276ee19f133496d37","ae6be9a07e940a6f4b0743220077f33259542ae908744ab349a1a70d22f723f5","70442efc456b60235ae408e157a6f0caa8ceacfac13f5af9d27265a6a3f57dc8","31409dc7d6946f1566b501934ee84e4d61916cb6893791c2da2731b12ef24b89","36c6b6a3bbee15a10e445a9aad4f1287d7d1039b6b58224a01524fc62446e533","11fe25d3baf912c908f4a63d2e05da668fd9a9838f258130cca57cc5be4744d2","ebaecc11e0bd3f3451f11514cc0ca76bb2c763d10240b59cb187e587b9e01f66","035e2335298c2061e077d0ce080b69b4047792fb02338310ac848c334c590b5e","635713a99868407271583323a9aaca2958b2abe2ddd43d7f2ea987160f6ff89f","7c3c3e194f59da1a744d6d5c1090d144769d025c068b2f84524dbae0fd481d97",{"version":"fb888ca2d1491a87202204b095f2816e2e8041f8a0dc67718d21e3e963afeaf2","signature":"f6eb9961bbb1fc5507f52b8081d6e82eb3ef5eab264a5d926518296dc4244127"},"1d34e8a8581252ad585019e14595e44c1100a88d1b586cafedf89153b71177e5","63b2318993b6e0dcf67bc21cc8aa94e41c7de4936bc0d33feed3828d589d33ac",{"version":"454b346ab7c6e6fea1963daa8abc997bf20a61e172018cc3fb4f3da99adc3ec1","signature":"29c3c744e646ac31f51cb4ae4b0cf912d8d251972c6a958b100df797025a94ac"},{"version":"fc357aeb6dafb0b0088062750d702118459f2385d31434c8ce94ed1c1e7914be","signature":"995cd4a56687721b9ebcde8c6499921201e7bdae56f437f08f6a2ec2b1e1ca0a"},"6ba0e711d73e317b739a4b0b083a109fc3fd294985c81e7f3c284ce4bc6427d4","bdbdf92aecef77ec1ce77d842bad71821d8c11bb84335c99cbab6e6519885583",{"version":"33c2eaf9f2216da640acf1814500bdbc10e08241fd17d8e19eaa203c50152815","signature":"155a0ce1ae5dea70b7c62b88bad1d252f860edbe6973cfb381851ae84fbc57b0"},{"version":"f46250a3d3cd3bd34994208caa7d245088a529540bdf459e7225f4752509085b","signature":"4c117079aad8524348f5b782625f9663c24202732204ab321cc56f0913c99318"},"2fc8086bb1e429d2786b7d38419c2dd195328c42631f4c03800ba8b7d691fc6a","d68e4544ac349d3775adff756e88da503e34879d7993769da9b7c93f90f3a1ef","c15e4b4deaf1fb4877793b7cf7d89f6254a54419ce5357ef98a2f800c97825c4","df70517f2532151afcebc39b9984bfa3c5ee4677c6e9938df86d17dcbe6a8222","b6bcd22d528966ac3b3226ce4368fa2548b9d27086496f200acb6778b4be9e37","c5dd07118defee6b0126f06b654506b726f4c3ee059fd5373f474c2364f0002a","d99e261147a8ca0295f772e82edaae16711db8d30e2511b98c59131f6583c216","caa900f1d326dfd6bc47d123e685680bcc21d4462bcda44c92ca7cb4318efcbe","80f6f4419b10ac52e19081d625d5c87e296a4911d9079bc92b46eb68f39dcd94","963e29c03860a04f42f2ca7723bf2f6c8aabcce3c2aed54a011716e20c1d65df",{"version":"3571219af6edabae2c952146660e1734804bad8169857f4b8ebe6433463ec3a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5f610e2b5a184bf4fa504123d543d6c34a35afa82f6a58cde23d70942c8d77d9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"b6a64100f55b037a2788401e6a59d3850ce656c85f3e4a0a8eaf66a750c6ed0d","f371e54f31a872850cd31df8f6580dd22e8a08a6ae55fbc1647fb650384550f6","16ed5d4e6d9bf022f732e27adf9081604d593b3ec37e9c7a2094c67d115d6e51","ba2e7bb3085f0acf77f6e173b0318d0db592580a32aed9c1d9a4bee49693996c","dc529f36460fcb82d608cbf7dfca17bf60caa2efcfa2ffc62dae265cf1eedc81","0cded9960cabc4a947c3bf0036b1e4cb71f157413ff8ff3955b38e1f1ea3a310","7dafd83200a4776fbc6fd2bbda38b6bf4743cd754535adc2d0ac4a5cae258aca","b3f296dacd56947df11418f474b12eb09c180449cc833fbbb203c13e657b96bb","8ca12e1da31b750904a7e9c542da66d01d735bbe9b798bcfbf9753bc9451aa66","4e64be35164e01cacc75bdc277a3412e76636435de76c0b193b3f3c5d4290d48","90faa6c0944d21ee0222ae9b66b39c9f18e1225f9acaf9c4b83b1d4a43b26769","2d4000626b78819a6a26c46ab8fd01ea13296c078a8ac19ca144933e47826a28","2bdab51bbfcea17d53fdf5cc1ed29d56e98a64a9f54f568dbaf327c25d2677b0","1cf312f6363cab7b3a2e6c67480fa5a5924eb6488bb43b766244327a6ab75db3","dcbd3305da0fa9f9bfa2b6775179a16b18185d820810dd243be17ca852b7bd99","da3e0ab10454bff69d784689a6017755f62f51f9270bc5ca33a780d8f1effed6","ab5b2fe21848a9ebf39408f8e5faa4a3fcce9eb6580fe9b2919990f573f70591","3370266542d151b96946fe6b140f046a0f1c98a99c2ee2f74b9c7f8e6c7c56a5","402e78c9fc8f2d232f0ba377e70c2ebba520dfde76cdf4cf3d71e28515c8f33c","9279672d35d72514a5d65cb870ae38fc12b87f6e814f1c8f60769021d49629be","77d640a224467919d1eaecefed3e3bddbcdd6ed34ae045f4c6c879b03ea8552c","35751d934bda8baf8801ff32ba94d394350eabdfaede494dd1651a99cace6f90",{"version":"6e9a6151390e4f86224464e69c92b3caf0f5af8dfc53cc5c93abbabf638a2592","signature":"570c73d45cc72509d98f043168119c5ad36e6b716e2441189f8f875b78b7d309"},"83d32bb6c68c36dc2c27d16caf470e429254d3de8c5c8f9ef91134d33299aae5","bacdd6d5210d35dc960527ea72f595feb0bf54996c092239a22b1e443f419a00",{"version":"f2e84d431acea5d959cec667fbc5a2410ed31a23a0e104f5d442db92d11b2492","signature":"8490537159f5b3a3fd14f628b32e977a351e70a3bd09b890fae5616aaf894cca"},"68644ec645837f18a23be76bb3f4a66f5812bb9c347e23f6c25fe93e7ce8d7c9",{"version":"b26b234c627799e3b90925ea36bd4cec5e57e26e3eb4b95d598b0ee19d1fdedc","signature":"a476746c1a3430e74dc7d6764252eb4efaab3f931bb8ed285d82185b2efb30ba"},"ae3c82b6b88fe1315f8e92eb498df792923d5da97e77376a8e6434ac660bbc62","5d1a09f5ab37a76ea0dbcc20cd08dd4dc224e263389aeac1c61ff592cc825690","d89ff4c66bb8ce9ecce1e47d62c2a11000e5cb57d27604af1ee22374cc7d6a32",{"version":"7e488d2c1064204830ff271a055a09dc04d15aa208f1c2aa19be88ba19f57bde","signature":"2a346de3340c2b612e54eaf8f283d10e06620b02d5ad511a26faa5178f473b06"},"6a52477ffa08adc8d4bd84879ac20a5436f46333996cde7ca4a2e53e4e2f1776","fdfee9e401a2707036f47501a7759d3e3d9ef181ee6efda7a9cc9539c17e8638","c2932a793359f3b09586284f89843b49ba29859791693df7e3713f5c169ada20","c11954f6c73d0bfdcafe0036d47648d71e8ca4f1a70b1ae88c815a703fa9ab80","fc9adafb376ae31c4ea9501ef266f0faaf29de7d76aefde50ec9c6ceb67655fe","af03f35fd7d1a7a1b59e8fefab3d87aa8fc45501cde4d5f42657a0af2dbd3b85","765ccc4e3f7042c4bf9a0288838c93f3841d85e2c3fd10e15a17ef5da7e348a3","bc705423f5e581231d5ab8472659fb37060aa3ade1a052f301ccfaa5eb836748","23f751ecd2c2b9ce4449c843400093a3359bd77b541c50c815b3f3bb234ddbcc","c1f283a5f8e29c8def3e16de0233029b469cb0c493d586c737e4d9c373e7cffa","301400d715a0763a26cde374da3440a1d4269254f6438f90f63b92e2ecb904f0","566e62da419b55e2c0504baa8b1e36b7af570e68ff1efecd7db3fdbd67d75984","f00ab38948981d4a7ee14b6d84a96edc3d50d3ac4412e4fa879210a4f34d251b","7fdf1eb6c97f1f98cc7cbbc310c8ed4ac840346236053e0453ba33a58b141735","ff310bdb1d2c5121653e826dd2e72cd137c909bb92fbbcaa12d612e6008eca9d","ca5e466b1cd54a780167ecb1b23e6be6ebb99ccd3e500bdb6909343f4eb08e70","86bebba5823cd4c0c8c264ab1e5ca89532125029e01b3701365d7c8b57ff7b03","15c6a3bcc2ccaba6a79ea23cc968005bd86ae7c98e1851abbddacda91561027f","ba697cec2494efd4491a9b92cf45a2b453b381938429db999e6b0ad8eb91b607","3b9f374fb01fb21e7d3dc1ac1bda5a6ca485e8a42d80c5857c0a907fb1d56d9e","4056fa415788fb428681ff6d118600c813bae18a8939c0997e3a3a0eebbd462b","701d18960c7fdb3d53f81c7081a871759da5846297845a6df470e448c1ee46ff",{"version":"178b68ad3da8447deb3fa36b903515c68a878693390dce2c3c51887138a4d358","signature":"d80742a6a41d9f569db06f2a6f1ce341e38a8023e1f172dfc596ebf59b320d89"},"2fe04834987c803287fedee95429f29ed93194477634301a80acea18732b0584","64e96839e33ffc472581904d3d5f5101ea95a39fdac17d42bf3b8080ed452416","c632abd896e5fc858119334bc27fe15d828dd2ecb2efa72b19ade831564e4a56","ad56f09ec02b513928021933ba8ccb5322184a5f145211adbb54bec8ab7c939e","4d9ac1ae59eaa55088de16aa37191db82e512889ea2e3475c923acb0fb5dd1ec","2b207ad5750863999cb3b248b98e29d8cf15b832e77bee46c23dd5c712094bcc","8a5547b3ef575d99d9981c3f2cec446e2b348ed27d728e3758ce04518afe2236","04aa306d9eee3d2db5ee5663ba1503459ebf0895272569c8b85b9ac10947c453","4219b873be82b7bea21e2c107b5a377780307fb4ff00dc949d086f13a2f0866b","a2709ecb4b779ee385bdfbe5ca4d5d7d6a77527d0d7cab0d286f18d935e8b4f8","3b40021cf5c4b492aa5cd8fa0871ab438f0da413ca344de421849513e4332ba7","9a6a75a9d4cbcfe725e96855f3af3803559790aa6b7e48a6314be4497e3aeb8c","579925bdfaa8ffdf328f0aaf7a2b98a43acd6c7e56f4902c31f81cb93597fb98","f749d4843e5fa4533b0e8bae2784314231b4404e332e8d56abc2692272965212","71c8ad895db3c65dfbefa63d75e779b2ce821e8badb100fdcdc6bc241f2f4544","25ff64eed6d319715fece8d041173a27719a7616837f57626e812d1ec3c6faa1","4e66ff6829096c09cab4b63dd3b1963525319b75bc885f40a82980d024253c88","90b2c1b62ad1584dc7a33d91850fc92996bcaec77e8dd5f582c4906f6039a7cf","2006492de4323a0166b032c02a1f8f5f6433b5e9756bfde1d98f7902aad7643a","38dfac0e60c6379a3276ffe33739a19e2c81f3359a73f80370b7dbd615239da2","7ecfb0fe515e7462466e1fffddf551ab3cbbb0120b9c60d8b0882da1184d719b","2d6e05af866b9a2fba0ca03edd8461339787f298085067eac7179ff66826fbf6",{"version":"c7a40c6af045ffba5250fd4b2805c5e57e5f7ce518690f180c83b65018840f3a","signature":"cf231aee194a0a458e33d6b2a8017c04c869079c965b00b9d294016e5f331617"},"fa4fd1a6c106daad4d2048e50518a1707039d574d96f2282addc0157b2143b29","22c9076e33fb7efff1af1d8c1a9c3e38eb017a1a07e62d78b8b64ab33664f2d3",{"version":"78baac76996d1d214302749ad18c6424d1952fc441004bc8b1ff78e16ae94f2a","signature":"e0fa0f834bef15145ff38c4f94b555e406815bff1d72c3cc4b911bed38024c17"},{"version":"a41f813b81e3ee6f2fe6051c05f77671ef035853004832795377479c61cbcb81","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e374ce00606b66ae99a8dd321694504f11749fa9f407bcc445dd4eb6c6b3b5f4","signature":"6a2a0e9055a691ef8a292a143dd336005e96f4cfed93373adb6d1fb2f7d67cee"},{"version":"bc8339d6590cff26e515017178e6a430e53c0fe8f4f858355180bc22278a1bcb","signature":"0c25e09a2b6916bfd4fb6138feb16d394bfedda3d5fce6464478918e2f3a32ef"},"50a97612fd2557f947fff7bc53118552d9eeae0f349814c410151cc6dc305f81","93b01da13427e2d00a8d73767869b61102d6ced5b8e6ef297006047e7a36b63a",{"version":"4ed34dcbd916c8746407bbe31966464ba2a40992a7d3eafc7b89fe9487322e0f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"980ff563c04a7ee054838de6d5581a1c74f879aa573e49083b767661eb497b06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"073d7d72dada0f47cf563f302854c2f4a56a0fbdb4ca0bb02878abb996b14c71","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"81f5e70b2efef928bafed1f1ed517b5f3d7dbe1bbae734620b1680b8cdd0bc66",{"version":"2f9876fe775220881f9a1dc662c4d45a1fc6c69dcbdf3394d4dfa7d38e7abf08","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"957c4489f92b096c32fbd8a1ff11729f1dbe37174d0e02792a253a195a2a8ba8","86c4fb8a79f66576d0dbe6189315842ca38029afe2c6ebe5b69d720ae7204d6d","23a5597b552c4fa9218e4ff75c6fde15c735a495a7eba796b21a28102ab16307",{"version":"23fa3382c09d278365b7a211300808076300a0d16e6b7a7aceb22bbd6a5e2850","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32890338fb3db8ba265d19c7192bfa9a11bc5ee4c15154a4db81a4ddf1c8b38a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebefa20d8e7844bf717e29dea823d72e0e3851abec67bd7442f18d5e1c929979","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"4cfb9e24d12ee634464b2e685f0e830f3871b28e0173cc89558416f194d49f73","ef7f54e0c441529398e2666a264256395d244f143f2f97ce5737b8ba12f9dfb3","9a2f242d01ce2d89d7afdfd1fd83653b8d751731fe8484472e55caff6fca829c","dec2391eb73f6d626e7679f9c1a15a5a3939f799b408ee2ace519ebb16802d9a","6c8520dc79618d2ef97bd41bd2d9f9615e8d7c31289ad6ff40202de2520d8a0d","d7c9c2e7dc4c35e0a79a12add067b79cb96493da0593a7e063db435257c7ece0","125a82f0749289343dae5c1ebf6a992bd166e0eaf1c885f53cb8224734877a97","ed8ce303eb9c07bea6cfa724060c049d83421b0a03c671040208438adcc1ddd0","bc2358aa66dfb3288e71f8568e09cbf493eb412a7ec67ffa33cdc24b0eac922a","71c4bc806bfef481e0a6ad07ad37d0be53ac5d8b0d19fb843e6a9549080dcefb","3e133c38d7312361e47e684f52022933865ca28b6d5d1bac3fa6e306c64e54e6","bf92a5c54601a670a6c8b9c02336b7a63a05b0cb9a05cf290d1cfaa95f28f284","e6160352cc574ab341489fcec7150515a9565817b60ab0a003d6c1444fca17b9","15601602390502326a32314bdea6c1331b340ccc19d41e82a71e69e7521f9b2d","feabcc3b9de397321d2fbbecfe8069975c10ca8f7d210bdb8fb1fb2ca06a2996","6b55ad93c7c4c1b77f78a46e1d78564d3dae464706a767f3d25ffa5e3dcec0cd","ee249a2e5c93e9110ec235c1e89cfde32b81e509c667abb08fe9c1f2e324a810",{"version":"8f33490c1ff132d5483cf4e1eb80e6e6495eeee76803ad9e0bf039c16f6214f0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9530414f935f2d4311ff2b25d6d8fe9b119e40eb052183336306fc8be3c84e88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"1ec05ea95ea33b0711f491a431916c731d2791aa389add26b4b0ae1fae5de7b2",{"version":"6ad131fba9f64b1c6efecc01403b93c63b294fca637e29d8d515eef286d78348","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"f69bf166c44feb49a246356afb2fe5b9ef6eef32567ba98fdef5572be707ed11","a376d7cc82fea71186921ef0f2779295f1ae28d8685f2dcf5aecebd6ed897e7a",{"version":"0c9b5c4082f748ded869361cbb3f97d405998ff5512bff4ec98ea95213085ae9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"b3f09f17c91d57b6a841936dd215929d1ddb25b6cc36e2d5af8c2ad22efaea57","c61a278f15af8373e1c5dc59fcef735e0a67d0ec68e0bb39993cf421922d79f7","4ffba69cef9d354ab21efcc26daafa01e3426d6ce70629064bc121269544e2f0",{"version":"7b62e0df27e53f8b9b32da0dcd5b818882e5952125b5d0e4fcf618cf2e3231d7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"3ccafcfd1f83fa4242ada464cd0cce589e03570b8d32806ea0ee8f66bbc75ee4",{"version":"83780a3b4577d40f2094e631b3929043444b0bb16097fcb8c7eca08dcb3c1427","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c0acf3d7f2a5d62332da4fc79bcf475ec142934b00b1b0c8bfd3893f64bd1c24","70adbc536de0f2152a13491e0c1e76777e59ea9abd4217cb54cc7084f8574cb9","d767afd9e2f82e7e899edc3775e1d86e5acb4c7e6268acfa95c551fc7c02d676","34a2803e9127b665802f3808b668a5474c0e95e2efa58720312bed19f4461187","f1669908a2919eaaca00a2d247943b171e70beedf9ebcc743ccf6572392a26c3",{"version":"c582809c6b259123d3e999f8fc54040732e9047ad51e968d35de9c9e7b23475f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c6c4b346b7396de0a88562c85f142a3e6c71f0f0c3a51d8956d9d3d656bece75","17428ad5e6272b4958e99bad33e44b2c65c554fc5a4511c5ca18f6ee88277296","443177f481983e2dc6ed086301cafca403fec7d0b5f97d65658b79b7b37e11a0","87525aac3b68b128ede1c21fe4f43b896ffb651c5507ec5bf554021789f0ec68","2b52485c59bb8f5f8ecebc36f9eebd5bb9e839006267e67f14f40bf57c21e545","6230518eb3bb41f00853f984b9208154c9180a11639ac532d115aa34daf08a4c","63e54be11fb7b740bfdeadd63e8f451830470fb4add677af84ca53813253f593","41222d3dac2c14b6a1f71e0b5105f2e3f860186aa3db1aff6ec4d95f833bf6ba","59d51e5e8361f7051ead0c29c8a03483e6929dbb6cefc3b77c2c497f2d895762","34bd75b6379933f0a0371170d95905d43f72c8a3a2ee431fba5129470947bc84","4ac0ebe63335c8cf5fd698cefa7904ccccca2f9e5d27dc9e0e18ae1cbb5ba066","44da1db5f81f80f935eb95e20e3c925d71d68ab43379c478ef6aea748a3a0b92",{"version":"bd6074dbcef6177b94d63a539d60447a71cc249f93982528095f888e24d1fd9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"405ab3515b5d2f07531943438c5ecf082bd61434adbf4860e3f83cea145175dd","7356415ae2693e3f94e126d3fb31d42990d0efd882d063661d8a588124fecb67","d0dd8957db84d11780ab6f4fa208bc3827c49b5986f0b5efd5bb98171bb5a944","25028aa767cb234fb49871cb5dd6784ad018d94609a519cdc5334f590085d21a","28f03986bd300c037d0dfaa877d4e1ec84e84f56f87e6a28354988c4dd313325","94af03723f5fc0766c58bed116f2d53102c1f48eaebed8a5f0d8af8d6f38682b","6dd4c595d7c2e50ef87da5a03626aa375407f05af9a1edfee1556ff27eb68ccf","871e55ec9de2b9c46582e36d93f3ae0b8f9414bce0438125de318a235d0293e1","ad3602cf898d906862e748a847deed9392213387659382a47bda93090c24d2e7",{"version":"47beeb485aff1ad4e1fbd8ac6e8d36a24e150de10a30b6899840faa301655414","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"487344aba34994c6bea6db3099ad923d847a27e7c900106a17d5273f8cccaa06","a8a2e0a62736c86aafb6bdfb9d640a79dcab172ad24a4ea1c0032e28a44359fe",{"version":"0568baac3cdb203dd85282b137b649bbf8171ae2f1a61264cfdfdc1dd5f6c1ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"2d05701eea88d40fa2962c3e988e6e8c751892445eeceaebb8f76bf10d8fb47e","fadf95731f4678454817d68abb0951550e2873b96d0e549fc0e46e8b9ca303a6","cbe51fafda1456e3f033e37684ff3dec49b3c11097453e460cf494d612abbf36",{"version":"a5f1a3661ab26f668a8195833f02b4085991113b44a0bc7f790e9c9af257f07a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8fbf898b003bf3d70416df534552735d946ee7c578766469039551b5b5989a16","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d43aea7abe28c92b8494f0fdd74762bc1d3ec18b972711d2a883ede1ca8ae628","598384c7786700c7d6208cac6007b37f123131de52f69441e496d3086f01599d","8237ed9ac77a0e6c957c04ae1939d077b1eb214150e0f2ee2330dcc698ebdb6e","9db5db65827dce6c3005c0ab5feb8dbc60776a2767d1f3779e4e56b6ac0eee26","750e7f25638270d4fba9ee9fa59e79d2d97cc88e655bc8bf27573dce9ecf52d1","745001d456418763f9801cd2f8e00a519d597d29efac153f41db8ca2b4cb5cbe",{"version":"32552e3b687bed279decc2ff81ba12b6d194998a102b5592537ef0a7bc246ed0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"17000b2a7cbc8febc1c38e79ca4aff5a824bca523973aa7b5c4be0313c10278c","ae3187226d80dbd2906f54a87fe586f0b33961a92b99f74baddf23943ddf197b","6290d7ae201e2cb37a3462e8f0474823749c74478df2c024483ba0f66b9201b7","c3ffb3ab371ef4a1c49f3d70e6cf58152abbcf97f79b87b81fcecf0e349c9e47",{"version":"43c903a3a3e6bd110c6e1e0edf3f119bc3863e25f534de171957fceb9373b791","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"6060795a11dbdc1b053619d909275140681f310638413d7f75dae71c0698a0fc","9b536f09a14585f7a60c6198eb73475cfda55bdb6eb7982562b14d9745ab3f58",{"version":"7f21b2e5f827fd2bfa35959f943d5f7c38bd76195247f63a7e00c35c869583c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c93a6690c5ddf530c35ab275c70a4a15ac6ca4a74275d3a0205d1acdc8f99d2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"5a1d4f7b55a0f4585ce971998ad5602b25f56fa82c105750c8f770fd89f61fdb",{"version":"f9f0996b794816a3dbaee1dc3e8d20e19845f48e94a28b86ba71cd7dfd7bd4c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"71cf715fe7ac9cbda3398c62715e9e41e205bb9f66c14db522c05d33d9bed871","c170d6a07e32644b63485cb4fec95a7b4210c95b0106bf604f77f60be4590609","7e8ad08f464b4d38665019d1a2e7abcf8431a2fafd4af65bcd93e71e9defe276","284afc03d292b1476a7abafc7a199b1374eece1304d742dfa2fffe29d1ef0c25","724ba566f050f9a5c9d59f094d43c5986a190bc913ea545fadd79e99201c1cb7","63c289c6931d3546d36c0cb59ea38f2d22ce5df282547200bf86dadb4cf442aa","e9a831e721e46b46f371bea50434b366775486045b009ed500a273a0c87cbc6f","220c41cb6d922f9df023fc9633b25d3f277be8ca0b6959d35510aa0ce0d7f435",{"version":"fcf4e15889b48532a4fab0550d27792e595ac7e53b656745561bbf0499175c37","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"9f93119e73d9aae89eb4897d9fcacebfc8131e4fd6add6bd0af2f085efbc1b5d",{"version":"bed5a24b28678ac3060e6247e7f1028d52c3cd0a5da6f8de620813357bef52ac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"8e8323f9bf61781a5c665b85254c338ac0bc879cf252c408a9155fcde6d3926d","58da354bb341bfae058822830e25841c7d4e322f2c01b523533d976788288a79","7cd11c824b38c56c3331454c55bce8d8c965e483bef9c7889d44f06fd0a3778b",{"version":"9657559845b4561279a2fbfcbfd17fb71629ef81d05c3faf1856ddd14977c8bc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"de10d6cc7a07ce5c5d961316be25ee61e38b528aefc5b78bf4890f24c0749f6b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"569beb54f189db6412e1bd14225b3c003cb7ea7a8b8ac9d2bb4a98d443a1202a","2bcf3b15c705b78d2624ca829055672f638ce38a4ec0bb25d7f776265ac833c6","d7a0f90adedfb247320507bc1f490cbff7e5c0236bf52363e4dcfae1219bb9d4","b2a4650779610aa8626f855bdead2a9ee445074ac77f0df56d4c3d74d471ac27","1a138b16a062718039f7b4a0189c173d4612c918f1391c15a13ff9d74d76c0cb","2177c2f515fe8ca0aff425dee0fa1300d9f8012e341a74dd4923378a60175136","d6e42e08867a127f7976389a59ccddd411a8a00653ebbc5d4f4d7a7cbf36dc36","570d900e54c02bb666819963695f97ab355d3a10137e4c90d48647fbef5a8bf1","c51737d123042bf7b78e65abf4f684cf71693261300d9689a5e35906b94f9120",{"version":"0f84ee74cf2a11b56a047e162e43e73678cca0e17a2a7c750939d80964fbaceb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"eb45916ddfa4b3ca5ef6dafdfc7ed7923ce2da5b6716632275ad31ebc4e628b7","6c3b470578a5bd66ef16829c185d96ccefc3d2a3377d9976410f500610ab9628","f13b437a12555b0ec040c3d4f6d3aed3eda3ac447ef37cdfb0e458b697a97b8f","972a8cd8b3335703b18119089e6d0ea65460a6b0502350734fdb77941bb0762d",{"version":"b5700efc19c70a9da0041f25525ecbf96491839b2e4dc8dab32f8e662721ab4a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27a14d107fa2f36104dddfe0d0f3ad259d6a5a8cf3ff91cce99b5e493f9395c6","signature":"c754e6829c741e6b805b1868f57d8dccbecec8f04c2bea49c8fd3906a9b4bb9c"},"931a84417d61b614170fb2398ce6996a3413ce2e44b8e8778f68944f2e90cd87","adb0c4e652e7e2fe0de47ad7ff507a8d633122926d15e2196cc45ee94ea1c574",{"version":"b03d1c836d3624a6ab8fd8395bcd1df2106a4c7da12ad82bbc7fe448968e7f41","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20d1bd40bc36713f75dde61ff02bdda74cee057be3c13af6ee23fecdae565d53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"02cc4901ed1607eec674547e981beef06f1af8120dae3797ba9f19220246bc63",{"version":"5f26bbb408f078078d1bbca7f13884b9b9849023484395cd135394d4fa8e62e6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"26085d6a7b985e91fad21164ed5cba66427dbeded7e0a672532ecff63d2e7c4c","87dbf0346d5746894eca4b429e98201f34a03e11331cf456d13e71c81212e426","dbee469d488b262f97f892153e62cd20ee4724dd8b7d253ba771770ac8114c67",{"version":"a3b5b2202cdedc66781da6676815f67ed036e5ae1ba2218dd9935a70e5b1db41","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"627f1ed82ab6a133fab304b936ad760a3e3099352c8aa96e0560e3417f063909","3a6dffbdf23ab002e31cedb2a1ab916c66a51a78a87771cec3ac596f12d82fa7","95658c91b67a72ec6af1ede02ccb5802d685bf848391710bb006f1ff1de9cc67","66db29e3c77173b1a53f6d0f07474d50b9b21bd20e5427bf4a70015fdd2df3ac","baa9f93cd885deed2211a1f17e2b64074d45217f6f95784d9d7db3b9adf39f7b",{"version":"cdd21ddbcdf8e5073e31fe7f730fe3c4023c66309625b87b5b9785fe140190db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cac72a71dd33cd4dcf93a4c06a34590d661d2ce406b9734cca33f567ddcc7208","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"508859ba82d5f926349e4a9d51add2f33fec2eb154fed40a6a80f12df4d99bec","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9fa26168f88bfa67f9b9f82b7cdc70c643822adc48535a76c320bc7d262ad78c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9f11d83ac8f4908d460984e703c13f43b69aca1572d2949292bc9b95ecb7a2b0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f98e81010dd1f0a168ccf0c28c53950048dab88a9aed8cd5cb1cc7790f883ac0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5458b79d513a3c28249bd399e109764da57de09097034437d65d13753035ec7b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abd6d932a4f5ffeb10ea89ddc43d53467aabd67f1424f03634d2bdb9e91bb39f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"e7484d772180a4fe32c9d12b3701087ec6479a1fb4027d02443b362d6748f265","d22df3d0d4a171faea1356d2ed06746654b7b54a6f134ad5ea64f2bbffbe282c","3b9a5b677b8ac9cfeaba131842398608331bd99d1b9a939cbcffa96c77b05f70",{"version":"6256390bc79dff5190177864fca522b99f1ff8c690ab411abb268d2660660479","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c871c193395edb4f0bc64f8dedd55c8d15a51a9519046dec95c4904242d7b2c6","signature":"3b5031a79ad3b873f4979dd714732927534e3a6d3ae7a9ec689c5725ca791ea6"},{"version":"3cd49322854ce1d737e709347cfd3aea195ff6e1b262d5958bb256c8beecfa0b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"41950adfe5fb33897a55572728896c2f93444277a234d432edadac80a0fa4e84","2c144fb7b835575d4eb400187da6e88cb37e0e58c7f2d430bfaa511f7f471fda",{"version":"ec563dab247f022b8527fe82436349f3792b975c4e939886ce128d095583abf0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f815436168a53475078bbd0aa903c756c66bca0ec8c468ff534ca4312eca4bb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"fa2aa9a0b964ca9bd71c8f1b2554010f338e89979fc0581e1f273a56897086f8","01655680390019da612e557fa6c87313dd411791e200ec4a960546fa1c73860b","79bdfee706e5a2f5afc91eff7c3a186da1c451fc3827038d6bcead0160ead42e","4f719002191cbec9176949717a9a57b621e3a1d307a74ede4cc94dcb78c249c8",{"version":"9cd6b43d528855022541993f9db3ff28d73c7183ab1043df00fc36073d6555d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc96489dddaa0e3e3c918fea22584a4823f0f5b0ea27df04311ca25224969acc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9e68ae5f6432e9c50c43e9d2835536cfa152255e680aceddeb3ba2c13b5a24b1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7fb4e3677e240e9cbc6268542e651d8d7142cafc9b716002ecb94db2923231f8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4fab0835f3f0569611b4185f74019264c8ac4338acdae9786c36fc5e73165f72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"095cc9d0709e52c5869e31a60f719773a43b60940f726480b689e301eb661d9c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6e4a5152f75e9d77ddeec6158887b08565816164545f301243fb653d7c57c47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"b850436d0a9d744cd3fb92eb3be65206791e3b4c21cd66fa1af395074ccf9520","5ae00f9eb7202bad96cf17277b37f8eb7ea8dea3e1d29766ca904c2b81f54043",{"version":"33195f2e0363a39a049cb3839f69891f3e92cdef82661f683823b7d4f2f3d3cc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1a91b7d838f633711b64538b4a4fdfa77eb8ffc9e1a5cad23d66a43cc9d1bbf5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e30d7ffd5f108b4f12429dca91377297ac7b070fa87b5680201b4c3da07ff6db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"6eaea317fe5b6bbceeddd6440306eb6dfe56c86796b5e90b0c86f03d98fff955","539ea0ca2bc54254c4751432472c80d8a6336e592b9701695ac473aa6c9b4001","0e7a4c03bedb6d5fe845344a08f7a3fbedc0831109d5b36facced86d3fd95d90","ce30fa93f285427c6251e073491cecdaf1e80751e13ebb7da419092fce4393ae","1eed9281109c026b9f052241336e80589c39df225980919ec591a01ae388f11b","c45bd057a7310603766ccd2d367916500bdc549f46285ba074bdaacc5b6d05e6","a0f194912ffe562a67d5570ee74538fc74e5b9ac3eda0c8188b314e72bc0b1a4",{"version":"9c63668af53291cbd777acdc086a76266b1f9c51e354ea2787619ffc3c10cd24","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eafd0838df9188d3b117c9fe53e0c77b707f5a985d3b8af99f664de7a4bbed33","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"83e6f2be1beac83f30dd5f1e56d42e907c7ce21c05ac72970b6ebd370e5432d7","4f59b8b9f9609eced7551d65f5a9d36c47c3e8e8f946304c4a9202d8748c87e4","ccc6e094800a0ec7e18a71109ed4efc28f2070b608838fb625afe4ccf0dc9b87","313a574dfe32f592b23877fc0677f33c8656ea9970e4af30ef78b96e17e0a032","3fcdf7901f8d9f9e77895e5b0743e77242c2710c17d8ac73beba8a79e433b57c","5a991ecc505e086074ced217226118fbee9bd97d37d94e9a73cbe73cefc82b23","3c91bf1628b9af6723816e7f06ec22cbf5627ea3c793e802eee02aea37406231",{"version":"ffdd7e9f674d0ec87a1da0853cde6df604b57b86982b95351262e9b2aa5cc88a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"09fd6ecf716a64bbd71674daee9e81ed726a6e716a66786508e12f95d4d47623","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca5a2320c781052b195b38bd95a7424e01468dc5e78ef946d8eae4d218437eb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"0dbc717d091c928ea25aac5b7118713c489b0b07f74b6ae3a57803d4d704c841","7fa3cf6e959f107cb2e099dbdd80e4d78f6aab3a0c012a77a7b0d1288917c2b6",{"version":"350701118c382c3cfe18b010184b8f3af5ce366f7f27a48c1892451ba12d3dcc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"2f0f520b1cab36fdc9f80c54b74f17e7189921be14e5e6384c9e76dd694c5df1","5f75a5464eb3481ed47aa72c7386a0bfa9e306ff570303a1ca2067a137e3cd15","9210170f2fa566053f02e2c5c3a77faed4e7e51d8366ec02adcce7953297fa56","b01dbe7929b0a92420ded501af329eacee87e3465038b6b1a0950bc7c8f90421","a09f8f187f3b0a161b4ac047191bfb07e8ef61816872267b882b311ebea87b2d","803ce94cdd49ca8ed653e63004ed3fcb16ef302b983ede0d5291257babef6bcd",{"version":"ebd54d09bcb969873eff5f50835348d41ed084785a1a53110b155cfe0875b7f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1d57277c40e6512296d6635e280c6228aeb08d789aff2eb4ef865eecf86cc78","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d71d12f9748990e5a21ef6fae3483650f1da187533e520785ba561f8e8f177af","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3c1c6e2d0c647263a8aee2d16655f525c930d6b9784eb6080c93ccac28a7c9b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a93fe9b1bfbc124b8f4777276537084f37469f91fb5ea6ba8637f62222f9d378","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"89e0c0b9430b0635a17c439eb81fe536ac9ad69c9229a832c1a661dab780a362",{"version":"81206a45e70c1954a56f3f56081b7161b80abd9048c0a6806deeb279b05b248d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"b57719fe1738f95cf28675ad0e55eb81a991bf372a7a5dda6c45b162bd094d96","fee02d6d186cd9b1dc4824242b05768bb2edc61614f01ad6207145744366a731","a83e4dd75c54300e6314ea2c0c5813b418d1a2244391acb001f263c9b1b37521",{"version":"27c25a73ab8e8e6ea25f0679d1ef24c446a929a7b9da8fc842af72349beb9ef1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0eef830593e6ca3e34d7c4af265fcbcd5d7ec2a6c980a442a8a395c98b7d872","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"752bc0bd543fc478323820a8595d137ea1fb8fd0d8ceb0ea05c3ded4bf1d3729","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4410ddaa3d6e3c1441fc5f669ea5c3e3390fd75f6127f06b1240625558160a9e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36919106e6e5c86f0628d2542222b4f6a09cf7955bd96c53a9f17a09b62f3903","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"f6be390877d224b02106db41d582eca38b6d52215c0843d3e6d78d210c956f95",{"version":"477bc6781d39427cdfb58b00ac6744fd72a76ddb9add5ee2b6fd7c0123e8c133","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d77a4e85ac4e7465ee559c7aa33e9b67794fb42eb006094e41de859e0f574567","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"efaba8801c46c71114040269fdbc963f3496d01a5b185ef05612d3d71f6c1fbe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b4f65e7dc37a2e488233381af3c9fdd7ca6e0171adc8a9cb2d49d2fa17cd7d67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3d6c9ea6ee8d52f0c4b758310bfa00f87f3a32eda7ac46b4f0b94f8f14038a3e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c293a5c9fca11fdc9f025e3a12b767b2eb7af7e2ee0c0bf815015a355d2d36d4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c120a6708c0c275899bcc98083090a85487ec866f85b6be29a50714dacdf73bc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4733417da9fe7eeed82209b53ddbf53bb76c0a7706f747945278a2c037ba2bcc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b78fa0476aae9c90f1ba345f48912c46ff37b4c95cd6242b75cb57efd8f2bc4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c9959681b8cffae14e821fbfdf3daac7759ccd92bd04413f45100301d8d08d20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"57ebd17c1bf0e09d222252bb67deb0cef31476b2476c680ea5ceeca29cfa3efe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2813a4305e2d3d23e4997d9a2f482fde783962eec4c66dcd112a3348a1b1f6a8","signature":"b84cea73e43cd5d152e01d2870e7736075b6c5ffd9355dfe2660b98078c17e9d"},{"version":"119ae1f4c43b80a86564573e397d49f6e19dcc54b96b3a513066a2e108e89c6a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"48392b4f5473115f4cbd2da11efb0fda7bb0610c15185a5838260c9c2b2e5745","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"931e404359129b88ae22b707a39298f2d8351f150a5ad6feabb975603272beeb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b522f77d275268e582dd53f3dc4f93082eb2f79a0022d066bcadb94a59b6c88b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a9c542d03e88c557c60e7dda6aaa2d71a05687764720b66dadf6cfe080888982","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c77cad2e3e80373964256a967f064b23ff95f5fc46788636eac8b765b2fea524","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fa264b7613bfa8f9e9f8b06198322e50d8e14692e44618cdb6feb7579e016919","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"02d2e27ee8ad6bff557165700f7a20e6dbb7816cfc60bca8c2613cfbd211bbe9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69b3922251aa575049849afbf72d429c17965f5827fcfc0b6636263d0a261779","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a23b58c3087d419c5d21fba70096b8a9eb42977ad61f22f6f7fba5e09e0e6ae3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"055e5ac1ac33ae174595f55c76aa1e371ca8819456cc5b97a69872037139ac72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"45f9d9bf9998e2ec7147ad27c7edc2e5ed387302c4018c9f4f7ff088eb22af8a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90e9e2b93e5bae19a5e66972efb5e6ec11dc1b50b9e8259f882055ccdb3d4aac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"f397adc5718aa5b8ea60ec16afed311eafe510111cdc0de0378994c629ff4eff",{"version":"7a83e8d50eecbdb731f8da23ee08494be2d247cb9e8e5c3857da7cd9e07fdc50","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"671265f64fc5c31cd317267ead0afc5c6c4634fb51204bfb54e3bac5d19d4db7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f1f4a3bf8d46ac603eaefa297ebfafb18a111a4854577d169bc3c0358bb373aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"89a4895e643533b7b76f782b52aa9b695d0961f2695ca7720dc50b48e9e55215",{"version":"dd12c9b4822755161ebb4ba65818948561a5982f5f493eca9f6f0db242a468b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"114445bc0794c2c9a3f03a42134748f545ea788a004e4667d7b9eff39211a61f",{"version":"8efdfaa6427be4a0852ac62cc450946e95bd551cb7c5b55dcc99675352e15362","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"09b4d72988c4682aa5713bc6a6df7892a7b8e2f10e1af3dc02985c6d4aef84ad","baefb08b615c9fc53a31c27981bf8899af8e01a0c9e2ab60c23cf0d324d77274",{"version":"465069c75ef1e4b084bce885c0a2ee70520c5ebb8f201fe6f85090a28fb34703","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e1d5c76c59c49b2b6f32b7065d0a95bfd229908da2db72526ebdb9312ed2cdd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"99eedc96ba7fa339e3ac82727c73628382af56287cc1219589004ea36e1b0c64","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"40510866633ba6c635e0495e40994f4e3f30d9378f23cc26887b3ff5e56391a3","291678a2a42b52881173274b55c7583807a9e94fe535b9bc84458d1fea33146e","288e930c1a2661f6345d07635585f1fe13c2deda86e2ccfc349413716c420555","b8e67d1c4879855a82071fc676a117355eec33a97bac9b727c13b728ebca825c","beabd1db71dc8e0911944d9400ced2cd02de425ffeb61c6ca0d2124cbe64d785",{"version":"945b34137fa3efb0ae22928275c1c42a722dbb81a26c57fb02f64f71e5daa3ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"294041d51d1910e6986cfe979cd3732a5f7eae7f329589ca4f2799248e5a7265","8f270a39bc647847500a6173ebd429406421cd10b2410d8cc0aed908f2bc47a0","8718ff0ade9fab90f46bcf4f55402132998e3c7b2b3f92154d7b85ccd91ac76d","322dc7275e83a2b413717c27f9dbb39f36372a09d4b694d8e0d18034765f8ed6","3828c70cef320027121c2ae0386e44385b937709ad0a1cfa4744a0a270b5b270","a7c931d7405b9910835cda95a3cd42684ebf92eb7bcc0d3649f90aa32a2d166b",{"version":"78228dfdc1a7024c9e4eabee22c057409886b9014ebd22319ee8a0a9ed31e799","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"18beabc03110b8f4e1d1eb5a556e6de09834d365995b2f10b17d26a574dba141","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70e8e8b44c2fca9791db4f3c06c3cb310556b19335d656bfe3cfe6aee8d65622","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"995394b9345e8eae0c2413b22ea07faa239769d45a58bb219b9222d86bf2d9b7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"04b5ec07def664c916b3a73a5b4b31f3930a626739ddb528569bdd33f0300456","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa82aeaf9be0588a652ec636bca8e6d7be86a81f85bb22e857b02a469e8ab2b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e4c50fe5f17db1977becce308656eff49a36eb1010b46ca295c27a77ee66a10d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4b54f4c0273db6878a1823ac888998ad7a0dd816f1c45a2fa24e0417702fc7c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32b1d470365f006b5fb2ad91d9097eefce0fad34bba93764e4501f2611104482","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"41b4c577be164a41be457fa1eff74c8923c8f08e8ba7e5e57d894424f48de2a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15c805dfe9b0eedb507e5a9d32ae6e321327d77673ba6181de4710f2c2634cc2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"5cc1c46b57a52eba565ad27fa54cf2e09d763de1f3412354357e6085e0d89ec4","b679abde8e957cce28fd0a30fda80cd7b9042fe9a9bb5a9369af5046d043fb2f",{"version":"47c0e01bceee2d7e95b691b2417954d55251167544413855e8440495dd67a5a8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2b1d972a2b83eb6f85a7d894c57331aa5be4e9b93d1b9b16d697112b52069bc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b98d50f326e42519bc139f429f5699b4049056f5a9eec6d276cd70c4dc7f7a27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"db11d9efd635982368df0b9378618005c6a8f2a32029b66723870bca3bf6c860","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"670eb13bccc2eb7b1754301c59f8fb33f5e30de44f17835fc8e1c741aa3f68ba",{"version":"35bd7fa89a59ca3b136c221ef604ba3a8e30cc88bb03cbc4d26fee0659d02ce7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d596725c15eee936539fe4bcb0ec9f08b2d8392f0e9bce03effb76ed734910ed","b08b6177c9234876e6836895b0bbf4465e14c9b64bbb7467da5b89b9b5b11d89",{"version":"90658f66263e4b168ed803e40c3ffe4ff81df88ce4c044dd96bf1c4ef278c33b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a85610ba8978234a59d37629fafe05c27fb2154cb62b6b775eb8119802e0a38e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"adfa78b8af8a8be5116202f634a2f113d7801ed20c47767339f1505f952ebcc1","f328b3203d26b5f709e5a082bc956c2e95fbd9fbdca6abee0802b59b633e0dc0","34fc137aa6085dd4f915e8b4a8c0d48d6b07d4fda84716ba91619d5ba39566bb","d54083855fcc2ed66dedb389bf2efc33b892dcf572829e43758309fe7bcaabb3",{"version":"f44df634432426f2b1249398b735f171a84c3902b4e0452ea2f7cc3d02568bd1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7952ed742b48930403868cdd2e09a9b5aa543c9adbed9f012618d6b58c289dff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ba0d14c18aebe4a5cba52b4a7b902247dd5a91106737e06d6e2112b1b4cbcacf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3b8983ef3b44f8779b91c7604f70379f8c40f88da3d6863e4bb7a5d7f95b2c98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a7b89eb149bb46fafba5e3eb85d5a9fa76013cfe937ed5c0b8898636a4eee533","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"23ccd9a0a59c200893dfa0ac3c539ac8f4416d0f43bce55501603f949ad1939c","ecaaff298281e8bd8bc234e03d4bc1ba565a804edb846005ea6566cbcc47fc73",{"version":"ceded34bed1c475b90671a320a8fd84a6a4a4d7c56c3f3f88d9a6804e933eba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6679ae1bd78dea53dc058ae235a3708f27ac7f87da929ddd38f7d4c222c18f9b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a40eaa7da4b2085746448671fad7ca6da6a84c58cb1d0e2ebfba17888d040a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"df815a5a142bc0b6b160b0735938321d8454a4a5fec0923bb6d7dea3f6c068ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"49d82eb2dfa6a10a2a6b59d85b09baec0b700ed3c9f43fcdc0b1ec58ab35a8fd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9815b675507e394e469b6bc395afbe8c63d6736cc7290a73f56cfaaca549b027","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0248daeeaac242de0ea72ac0f093a31b55e70b43020d40380dbf609803a45e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a31a79a9691f6153276381e906dd27e985f53c6920adab35199527cbfaeace8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3414d416e51f2846b21f4aa1492fd44bf9ffeaece26743ae1527154e3da6f7fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6309b32da582c7b3e5afdf30678bd7d456cd9a1118ea1c660dd73ee32770d683","signature":"4c372df16f354b44e6e653a4442eb9f26b95f2d43efcbaa75b59506276b92df7"},"26f213bee14ac8092e7a36473db58d1955fa4867bf5b091950ad8dfd31956809","4b332cfe58c80b9e5abef88dfe157a88f9170f64035fd2a83dc395b334c440fc",{"version":"262dc2495f719b674acd7919e678de874580311f4a0cb71f04c69995bf61650e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"c1161b186fb7ef72c0dbd14af1652937e6cb3453231dd6f56d396f43d46d638f","89ac6a7385062683575fc5ad85a18f77e6c9617a3786f49aba644d55ae277f4e",{"version":"3e33a62342fe8bc07fd5ffb6e870ed8f0d906f8021115bea5b4ef5cbd3632d04","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"dbbe19275d7ea098ce95e9ea65e45380eb8f80179cb14d0f2fb1196ffd9b98dd","b247732a1ae37a5e0307d4333ef15e3d5393951e3236be0287adeaa48d553b35","d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7","adf3ce64a58ecd81745769e79d346a3e6a827bb14dc6f81689449bfe4a97eb58",{"version":"b1538a92b9bae8d230267210c5db38c2eb6bdb352128a3ce3aa8c6acf9fc9622","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"89e326922cadcc2331d7e851011cf9f0456a681aaf3c95b48b81f8d80e8cdfba","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"7fa8d75d229eeaee235a801758d9c694e94405013fe77d5d1dd8e3201fc414f1","impliedFormat":1}],"root":[531,532,613,614,[616,620],[622,625],[1019,1023],[1025,1035],[1070,1087],[1092,1111],[1148,1189],[1266,1289],[1293,1320],[1323,1332],[1352,1393],[1415,1530],[1608,1610],[1616,1653],[1885,1908],[1910,1968],[1970,2005],[2009,2017],[2033,2070],[2215,2243],[2245,2256],[2259,2295],[2297,2317],[2576,2591],[2593,2603],2608,2610,2612,2613,2617,2619,2621,2623,2625,2627,2629,2630,[2635,2656],[2744,2884],[2962,3320],[3338,3340],[3408,4062]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[4061,1],[531,2],[4062,3],[532,4],[3406,5],[3354,6],[3352,7],[3355,8],[3359,9],[3348,10],[3358,11],[3371,12],[3407,13],[3341,2],[3370,14],[3369,2],[3346,2],[3353,15],[3349,16],[3347,17],[3357,18],[3345,19],[3356,20],[3350,21],[3379,22],[3380,23],[3376,24],[3375,25],[3396,26],[3399,27],[3398,28],[3400,26],[3397,29],[3395,30],[3365,31],[3381,32],[3364,33],[3402,34],[3360,35],[3361,36],[3394,37],[3382,38],[3366,35],[3368,39],[3367,40],[3378,41],[3383,42],[3401,43],[3362,35],[3384,44],[3387,45],[3386,46],[3385,47],[3390,48],[3389,49],[3388,36],[3363,35],[3391,35],[3393,50],[3392,51],[3403,52],[3405,53],[3374,54],[3372,55],[3373,56],[3377,57],[3404,35],[3351,2],[636,58],[640,59],[639,60],[635,61],[638,62],[631,63],[637,58],[692,64],[704,65],[703,66],[693,67],[701,68],[737,69],[736,70],[716,71],[728,72],[707,73],[714,71],[708,74],[740,75],[739,76],[742,77],[741,78],[738,72],[743,72],[744,79],[749,80],[750,81],[748,82],[747,83],[746,84],[745,80],[754,85],[753,86],[752,87],[633,88],[634,89],[751,90],[725,91],[722,92],[764,72],[763,72],[762,72],[718,92],[730,74],[731,72],[727,72],[726,72],[717,72],[767,93],[766,94],[758,71],[715,71],[761,92],[760,72],[756,95],[719,72],[724,96],[721,97],[723,91],[706,98],[755,73],[734,99],[735,2],[729,72],[720,72],[759,71],[757,74],[798,100],[797,101],[795,102],[773,103],[796,72],[799,104],[801,105],[800,106],[694,92],[695,72],[696,72],[803,107],[802,108],[697,109],[698,97],[691,110],[690,111],[689,112],[699,72],[700,113],[702,92],[805,114],[807,115],[806,116],[808,92],[809,72],[810,72],[811,72],[813,72],[812,72],[826,117],[825,118],[817,119],[818,97],[819,104],[815,120],[816,121],[820,122],[821,72],[822,113],[823,92],[824,104],[830,80],[829,95],[828,123],[834,124],[833,125],[832,95],[827,95],[712,126],[831,127],[838,128],[837,129],[836,72],[835,72],[680,130],[659,131],[662,132],[658,133],[678,134],[657,135],[673,136],[681,137],[663,135],[664,138],[682,135],[676,139],[665,135],[669,140],[670,135],[671,141],[668,142],[674,143],[683,144],[675,145],[684,146],[677,147],[679,148],[672,135],[667,149],[710,150],[711,151],[1014,152],[840,153],[839,154],[628,155],[804,74],[733,2],[709,156],[660,2],[957,72],[626,2],[627,157],[705,74],[666,2],[630,158],[661,159],[632,74],[768,91],[769,92],[777,92],[776,160],[779,72],[778,72],[794,161],[793,162],[780,72],[781,72],[782,96],[783,97],[784,91],[785,160],[787,92],[786,72],[775,163],[771,164],[774,165],[770,166],[789,167],[788,168],[792,72],[790,169],[791,72],[842,170],[841,160],[772,171],[844,172],[843,72],[851,173],[850,174],[847,175],[849,175],[845,72],[846,175],[848,175],[862,91],[860,92],[855,92],[864,72],[866,176],[865,177],[854,72],[863,72],[853,72],[861,178],[857,97],[858,91],[852,63],[856,72],[859,72],[871,179],[869,179],[870,179],[876,180],[875,181],[872,179],[868,182],[874,179],[873,179],[867,2],[881,183],[880,184],[879,185],[878,186],[877,2],[890,91],[891,92],[894,72],[893,72],[897,187],[896,188],[889,96],[887,97],[888,91],[885,189],[884,190],[883,191],[892,72],[886,192],[895,72],[906,91],[907,92],[910,193],[909,194],[905,178],[902,195],[904,91],[900,196],[899,197],[898,198],[903,199],[908,72],[917,200],[916,201],[913,202],[915,202],[911,72],[912,202],[914,202],[923,203],[922,80],[921,204],[920,205],[919,206],[918,95],[927,207],[929,72],[931,208],[930,209],[924,72],[926,207],[928,72],[925,207],[945,91],[938,92],[949,72],[948,72],[936,72],[951,210],[950,211],[943,92],[944,72],[942,72],[933,95],[941,72],[940,96],[937,97],[939,91],[932,98],[946,72],[947,72],[934,71],[935,72],[765,212],[732,72],[955,213],[961,214],[960,215],[959,213],[953,213],[952,80],[958,216],[956,213],[954,213],[965,217],[964,218],[962,219],[963,220],[972,221],[971,222],[968,223],[970,224],[969,225],[967,226],[966,224],[983,72],[985,91],[982,72],[979,72],[975,227],[980,72],[987,228],[986,229],[984,195],[973,230],[976,231],[978,232],[981,72],[974,233],[977,72],[991,234],[990,63],[989,235],[988,63],[995,236],[994,236],[999,237],[998,238],[997,236],[996,236],[993,72],[992,239],[1007,91],[1011,240],[1010,241],[1006,178],[1004,195],[1005,91],[1008,74],[1002,242],[1001,243],[1000,244],[1003,245],[1009,72],[629,246],[1013,247],[1012,159],[901,97],[688,248],[656,249],[687,250],[685,2],[686,251],[713,252],[814,74],[644,74],[642,253],[643,254],[649,255],[647,256],[645,2],[648,257],[646,258],[650,74],[882,2],[2605,259],[652,260],[654,261],[655,262],[651,2],[653,2],[1654,74],[1655,74],[1656,74],[1657,74],[1658,74],[1659,74],[1660,74],[1661,74],[1662,74],[1663,74],[1664,74],[1665,74],[1666,74],[1667,74],[1668,74],[1674,74],[1669,74],[1670,74],[1671,74],[1672,74],[1673,74],[1675,74],[1676,74],[1677,74],[1678,74],[1679,74],[1680,74],[1682,74],[1683,74],[1681,74],[1684,74],[1685,74],[1686,74],[1687,74],[1688,74],[1689,74],[1690,74],[1691,74],[1692,74],[1693,74],[1694,74],[1695,74],[1696,74],[1697,74],[1698,74],[1699,74],[1700,74],[1701,74],[1702,74],[1703,74],[1704,74],[1705,74],[1706,74],[1707,74],[1708,74],[1710,74],[1709,74],[1711,74],[1712,74],[1714,74],[1713,74],[1715,74],[1716,74],[1717,74],[1718,74],[1719,74],[1721,74],[1720,74],[1722,74],[1723,74],[1724,74],[1725,74],[1726,74],[1727,74],[1728,74],[1729,74],[1730,74],[1731,74],[1732,74],[1733,74],[1734,74],[1735,74],[1740,74],[1736,74],[1737,74],[1738,74],[1739,74],[1741,74],[1742,74],[1743,74],[1744,74],[1745,74],[1746,74],[1747,74],[1748,74],[1749,74],[1750,74],[1752,74],[1751,74],[1753,74],[1754,74],[1755,74],[1756,74],[1757,74],[1758,74],[1759,74],[1760,74],[1763,74],[1761,74],[1762,74],[1764,74],[1765,74],[1766,74],[1767,74],[1768,74],[1769,74],[1770,74],[1771,74],[1773,74],[1772,74],[1884,263],[1774,74],[1775,74],[1776,74],[1777,74],[1778,74],[1779,74],[1780,74],[1781,74],[1782,74],[1783,74],[1784,74],[1786,74],[1785,74],[1787,74],[1788,74],[1789,74],[1790,74],[1791,74],[1792,74],[1793,74],[1794,74],[1796,74],[1795,74],[1797,74],[1798,74],[1799,74],[1800,74],[1801,74],[1802,74],[1803,74],[1804,74],[1805,74],[1809,74],[1806,74],[1807,74],[1808,74],[1810,74],[1811,74],[1812,74],[1814,74],[1813,74],[1815,74],[1816,74],[1817,74],[1818,74],[1819,74],[1820,74],[1821,74],[1822,74],[1823,74],[1824,74],[1825,74],[1826,74],[1827,74],[1828,74],[1829,74],[1830,74],[1831,74],[1832,74],[1833,74],[1834,74],[1835,74],[1836,74],[1837,74],[1838,74],[1839,74],[1840,74],[1841,74],[1842,74],[1843,74],[1844,74],[1845,74],[1846,74],[1847,74],[1848,74],[1849,74],[1850,74],[1851,74],[1852,74],[1853,74],[1854,74],[1855,74],[1856,74],[1857,74],[1858,74],[1859,74],[1860,74],[1861,74],[1862,74],[1863,74],[1864,74],[1865,74],[1866,74],[1867,74],[1869,74],[1868,74],[1870,74],[1871,74],[1872,74],[1873,74],[1874,74],[1875,74],[1876,74],[1877,74],[1878,74],[1879,74],[1880,74],[1881,74],[1882,74],[1883,74],[2032,264],[2031,265],[405,2],[374,2],[2085,266],[2084,267],[1614,2],[1409,268],[1408,2],[1088,2],[1089,269],[1414,270],[1411,271],[1412,272],[1413,272],[1410,273],[1090,274],[1091,275],[1405,276],[1394,74],[1407,277],[1404,276],[1401,278],[1402,278],[1403,2],[1406,2],[1147,279],[1395,2],[1397,280],[1400,281],[1399,2],[1398,280],[1396,282],[1126,283],[1136,284],[1133,284],[1134,285],[1118,285],[1132,285],[1113,284],[1119,286],[1122,287],[1127,288],[1115,286],[1116,285],[1129,289],[1114,286],[1120,286],[1123,286],[1128,286],[1130,285],[1117,285],[1131,285],[1125,290],[1121,291],[1146,292],[1124,293],[1135,294],[1112,285],[1137,285],[1138,285],[1139,285],[1140,285],[1141,285],[1142,285],[1143,285],[1144,285],[1145,285],[1347,2],[1344,2],[1343,2],[1338,295],[1349,296],[1334,297],[1345,298],[1337,299],[1336,300],[1346,2],[1341,301],[1348,2],[1342,302],[1335,2],[2616,303],[2615,304],[2614,297],[1351,305],[1593,306],[1594,306],[1596,307],[1595,306],[1588,306],[1589,306],[1591,308],[1590,306],[1568,2],[1567,2],[1570,309],[1569,2],[1566,2],[1533,310],[1531,311],[1534,2],[1581,312],[1535,306],[1571,313],[1580,314],[1572,2],[1575,315],[1573,2],[1576,2],[1578,2],[1574,315],[1577,2],[1579,2],[1532,316],[1607,317],[1592,306],[1587,318],[1597,319],[1603,320],[1604,321],[1606,322],[1605,323],[1585,318],[1586,324],[1582,325],[1584,326],[1583,327],[1598,306],[1602,328],[1599,306],[1600,329],[1601,306],[1536,2],[1537,2],[1540,2],[1538,2],[1539,2],[1542,2],[1543,330],[1544,2],[1545,2],[1541,2],[1546,2],[1547,2],[1548,2],[1549,2],[1550,331],[1551,2],[1565,332],[1552,2],[1553,2],[1554,2],[1555,2],[1556,2],[1557,2],[1558,2],[1561,2],[1559,2],[1560,2],[1562,306],[1563,306],[1564,333],[1333,2],[602,334],[4063,2],[4064,2],[4065,2],[4066,335],[2094,2],[2072,336],[2095,337],[2071,2],[4067,2],[4069,338],[600,2],[4070,339],[546,2],[2658,340],[2604,2],[4071,2],[2668,340],[4068,2],[3343,2],[3344,341],[140,342],[141,342],[142,343],[97,344],[143,345],[144,346],[145,347],[92,2],[95,348],[93,2],[94,2],[146,349],[147,350],[148,351],[149,352],[150,353],[151,354],[152,354],[153,355],[154,356],[155,357],[156,358],[98,2],[96,2],[157,359],[158,360],[159,361],[191,362],[160,363],[161,364],[162,365],[163,366],[164,367],[165,368],[166,369],[167,370],[168,371],[169,372],[170,372],[171,373],[172,2],[173,374],[175,375],[174,376],[176,17],[177,377],[178,378],[179,379],[180,380],[181,381],[182,382],[183,383],[184,384],[185,385],[186,386],[187,387],[188,388],[99,2],[100,2],[101,2],[139,389],[189,390],[190,391],[1969,392],[1909,74],[195,393],[460,74],[196,394],[194,395],[462,396],[461,397],[1350,74],[1321,398],[192,399],[458,2],[193,400],[83,2],[85,401],[457,74],[226,74],[2657,2],[4072,2],[542,402],[589,403],[587,2],[588,2],[534,2],[584,404],[581,405],[582,406],[603,407],[594,2],[597,408],[596,409],[608,409],[595,410],[533,2],[541,411],[583,411],[536,412],[539,413],[590,412],[540,414],[535,2],[601,2],[1018,415],[1017,416],[1015,2],[84,2],[2406,417],[2385,418],[2482,2],[2386,419],[2322,417],[2323,417],[2324,417],[2325,417],[2326,417],[2327,417],[2328,417],[2329,417],[2330,417],[2331,417],[2332,417],[2333,417],[2334,417],[2335,417],[2336,417],[2337,417],[2338,417],[2339,417],[2318,2],[2340,417],[2341,417],[2342,2],[2343,417],[2344,417],[2346,417],[2345,417],[2347,417],[2348,417],[2349,417],[2350,417],[2351,417],[2352,417],[2353,417],[2354,417],[2355,417],[2356,417],[2357,417],[2358,417],[2359,417],[2360,417],[2361,417],[2362,417],[2363,417],[2364,417],[2365,417],[2367,417],[2368,417],[2369,417],[2366,417],[2370,417],[2371,417],[2372,417],[2373,417],[2374,417],[2375,417],[2376,417],[2377,417],[2378,417],[2379,417],[2380,417],[2381,417],[2382,417],[2383,417],[2384,417],[2387,420],[2388,417],[2389,417],[2390,421],[2391,422],[2392,417],[2393,417],[2394,417],[2395,417],[2398,417],[2396,417],[2397,417],[2320,2],[2399,417],[2400,417],[2401,417],[2402,417],[2403,417],[2404,417],[2405,417],[2407,423],[2408,417],[2409,417],[2410,417],[2412,417],[2411,417],[2413,417],[2414,417],[2415,417],[2416,417],[2417,417],[2418,417],[2419,417],[2420,417],[2421,417],[2422,417],[2424,417],[2423,417],[2425,417],[2426,2],[2427,2],[2428,2],[2575,424],[2429,417],[2430,417],[2431,417],[2432,417],[2433,417],[2434,417],[2435,2],[2436,417],[2437,2],[2438,417],[2439,417],[2440,417],[2441,417],[2442,417],[2443,417],[2444,417],[2445,417],[2446,417],[2447,417],[2448,417],[2449,417],[2450,417],[2451,417],[2452,417],[2453,417],[2454,417],[2455,417],[2456,417],[2457,417],[2458,417],[2459,417],[2460,417],[2461,417],[2462,417],[2463,417],[2464,417],[2465,417],[2466,417],[2467,417],[2468,417],[2469,417],[2470,2],[2471,417],[2472,417],[2473,417],[2474,417],[2475,417],[2476,417],[2477,417],[2478,417],[2479,417],[2480,417],[2481,417],[2483,425],[2319,417],[2484,417],[2485,417],[2486,2],[2487,2],[2488,2],[2489,417],[2490,2],[2491,2],[2492,2],[2493,2],[2494,2],[2495,417],[2496,417],[2497,417],[2498,417],[2499,417],[2500,417],[2501,417],[2502,417],[2507,426],[2505,427],[2506,428],[2504,429],[2503,417],[2508,417],[2509,417],[2510,417],[2511,417],[2512,417],[2513,417],[2514,417],[2515,417],[2516,417],[2517,417],[2518,2],[2519,2],[2520,417],[2521,417],[2522,2],[2523,2],[2524,2],[2525,417],[2526,417],[2527,417],[2528,417],[2529,423],[2530,417],[2531,417],[2532,417],[2533,417],[2534,417],[2535,417],[2536,417],[2537,417],[2538,417],[2539,417],[2540,417],[2541,417],[2542,417],[2543,417],[2544,417],[2545,417],[2546,417],[2547,417],[2548,417],[2549,417],[2550,417],[2551,417],[2552,417],[2553,417],[2554,417],[2555,417],[2556,417],[2557,417],[2558,417],[2559,417],[2560,417],[2561,417],[2562,417],[2563,417],[2564,417],[2565,417],[2566,417],[2567,417],[2568,417],[2569,417],[2570,417],[2321,430],[2571,2],[2572,2],[2573,2],[2574,2],[2008,431],[2007,432],[2006,2],[2592,433],[2207,2],[551,2],[2607,434],[2606,435],[1196,436],[1198,437],[1197,438],[1195,439],[1194,2],[3342,440],[2082,2],[621,2],[574,2],[576,441],[575,2],[1024,74],[2737,2],[2711,442],[2710,443],[2709,444],[2736,445],[2735,446],[2739,447],[2738,448],[2741,449],[2740,450],[2696,451],[2670,452],[2671,453],[2672,453],[2673,453],[2674,453],[2675,453],[2676,453],[2677,453],[2678,453],[2679,453],[2680,453],[2694,454],[2681,453],[2682,453],[2683,453],[2684,453],[2685,453],[2686,453],[2687,453],[2688,453],[2690,453],[2691,453],[2689,453],[2692,453],[2693,453],[2695,453],[2669,455],[2734,456],[2714,457],[2715,457],[2716,457],[2717,457],[2718,457],[2719,457],[2720,458],[2722,457],[2721,457],[2733,459],[2723,457],[2725,457],[2724,457],[2727,457],[2726,457],[2728,457],[2729,457],[2730,457],[2731,457],[2732,457],[2713,457],[2712,460],[2704,461],[2702,462],[2703,462],[2707,463],[2705,462],[2706,462],[2708,462],[2701,2],[2244,2],[1322,74],[483,464],[488,1],[495,465],[478,466],[230,2],[238,467],[378,468],[381,469],[353,2],[366,470],[373,471],[255,2],[355,2],[236,2],[352,472],[398,473],[237,2],[228,474],[380,475],[382,476],[383,477],[455,478],[347,479],[300,480],[360,481],[361,482],[359,483],[358,2],[354,484],[379,485],[239,486],[425,2],[426,487],[266,488],[240,489],[267,488],[303,488],[206,488],[376,490],[375,2],[365,491],[473,2],[215,2],[494,492],[433,493],[434,494],[430,495],[512,2],[330,2],[435,104],[431,496],[517,497],[516,498],[511,2],[281,2],[333,499],[332,2],[510,500],[432,74],[286,501],[293,502],[295,503],[285,2],[290,504],[292,505],[294,506],[289,507],[287,2],[291,508],[513,2],[509,2],[515,509],[514,2],[284,510],[504,511],[507,512],[274,513],[273,514],[272,515],[520,74],[271,516],[260,2],[522,2],[2632,517],[2631,2],[523,74],[524,518],[198,2],[362,519],[363,520],[364,521],[202,2],[367,2],[222,522],[197,2],[447,74],[204,523],[446,524],[445,525],[436,2],[437,2],[444,2],[439,2],[442,526],[438,2],[440,527],[443,528],[441,527],[235,2],[232,2],[233,488],[387,2],[392,529],[393,530],[391,531],[389,532],[390,533],[385,2],[453,104],[227,104],[482,534],[489,535],[493,536],[321,537],[320,2],[315,2],[469,538],[477,539],[348,540],[349,541],[428,542],[337,2],[451,543],[325,74],[342,544],[454,545],[338,2],[341,546],[339,2],[452,547],[449,548],[448,2],[450,2],[345,2],[424,549],[210,550],[323,551],[327,552],[343,553],[346,554],[335,555],[328,556],[476,557],[401,558],[319,559],[207,560],[475,561],[203,562],[394,563],[386,2],[395,564],[413,565],[384,2],[412,566],[91,2],[407,567],[231,2],[427,568],[402,2],[216,2],[218,2],[357,2],[411,569],[234,2],[258,570],[344,571],[264,572],[324,2],[410,2],[388,2],[415,573],[416,574],[356,2],[418,575],[420,576],[419,577],[368,2],[409,560],[422,578],[318,579],[408,580],[414,581],[243,2],[247,2],[246,2],[245,2],[250,2],[244,2],[253,2],[252,2],[249,2],[248,2],[251,2],[254,582],[242,2],[310,583],[309,2],[314,584],[311,585],[313,586],[316,584],[312,585],[223,587],[302,588],[472,589],[470,2],[499,590],[501,591],[465,592],[500,593],[211,594],[208,594],[241,2],[225,595],[224,596],[220,597],[221,598],[229,599],[257,599],[268,599],[304,600],[269,600],[213,601],[212,2],[308,602],[307,603],[306,604],[305,605],[214,606],[456,607],[256,608],[464,609],[429,610],[459,611],[463,612],[351,613],[350,614],[331,615],[317,616],[299,617],[301,618],[298,619],[421,620],[322,2],[487,2],[219,621],[423,622],[471,623],[329,2],[259,624],[336,625],[334,626],[261,627],[396,628],[466,2],[262,629],[397,629],[485,2],[484,2],[486,2],[468,2],[467,2],[399,630],[326,2],[296,631],[217,632],[275,2],[201,633],[263,2],[491,74],[200,2],[503,634],[283,74],[497,104],[282,635],[480,636],[280,634],[205,2],[505,637],[278,74],[279,74],[270,2],[199,2],[277,638],[276,639],[265,640],[340,371],[400,371],[417,2],[404,641],[403,2],[288,510],[209,2],[297,74],[474,522],[481,642],[86,74],[89,643],[90,644],[87,74],[88,2],[377,645],[372,646],[371,2],[370,647],[369,2],[479,648],[490,649],[492,650],[496,651],[2633,652],[498,653],[502,654],[530,655],[506,655],[529,656],[508,657],[518,658],[519,659],[521,660],[525,661],[528,522],[527,2],[526,662],[2634,663],[1613,663],[1612,664],[1611,74],[1615,665],[2886,2],[2892,666],[2885,2],[2889,2],[2891,667],[2888,668],[2961,669],[2955,669],[2916,670],[2912,671],[2927,672],[2917,673],[2924,674],[2911,675],[2925,2],[2923,676],[2920,677],[2921,678],[2918,679],[2926,680],[2893,668],[2956,681],[2907,682],[2904,683],[2905,684],[2906,685],[2895,686],[2914,687],[2933,688],[2929,689],[2928,690],[2932,691],[2930,692],[2931,692],[2908,693],[2910,694],[2909,695],[2913,696],[2957,697],[2915,698],[2897,699],[2958,700],[2896,701],[2959,702],[2898,703],[2936,704],[2934,683],[2935,705],[2899,692],[2940,706],[2938,707],[2939,708],[2900,709],[2943,710],[2942,711],[2945,712],[2944,713],[2948,714],[2946,713],[2947,715],[2941,716],[2937,717],[2949,716],[2901,692],[2960,718],[2902,713],[2903,692],[2919,719],[2922,720],[2894,2],[2950,692],[2951,721],[2953,722],[2952,723],[2954,724],[2887,725],[2890,726],[1291,727],[1292,728],[1290,2],[569,729],[567,730],[568,731],[556,732],[557,730],[564,733],[555,734],[560,735],[570,2],[561,736],[566,737],[572,738],[571,739],[554,740],[562,741],[563,742],[558,743],[565,729],[559,744],[1340,745],[1339,2],[1036,2],[1052,746],[1053,746],[1054,746],[1055,746],[1069,747],[1056,748],[1057,748],[1058,749],[1049,750],[1047,751],[1038,2],[1042,752],[1046,753],[1044,754],[1051,755],[1039,756],[1040,757],[1041,758],[1043,759],[1045,760],[1048,761],[1050,762],[1059,748],[1060,748],[1061,748],[1062,746],[1063,748],[1064,748],[1037,748],[1065,2],[1067,763],[1066,748],[1068,746],[2257,764],[2258,765],[2700,766],[2699,767],[2111,768],[2204,769],[2202,770],[2109,2],[2110,771],[2203,2],[2205,772],[2113,773],[2112,774],[2116,775],[2183,776],[2178,777],[2079,778],[2149,779],[2142,780],[2199,781],[2077,782],[2148,783],[2137,784],[2136,774],[2182,785],[2179,786],[2130,787],[2141,788],[2184,789],[2185,789],[2186,790],[2194,791],[2188,791],[2196,791],[2200,791],[2187,791],[2189,792],[2192,792],[2195,792],[2191,793],[2193,791],[2197,794],[2190,795],[2088,796],[2163,74],[2160,797],[2164,74],[2099,791],[2089,791],[2155,798],[2078,799],[2098,800],[2102,801],[2162,791],[2075,74],[2161,802],[2159,74],[2158,791],[2090,74],[2209,803],[2173,795],[2153,804],[2214,805],[2171,2],[2169,2],[2174,806],[2172,807],[2168,808],[2170,809],[2175,810],[2177,811],[2167,74],[2097,812],[2074,791],[2166,791],[2115,813],[2165,74],[2138,812],[2198,791],[2132,814],[2086,815],[2091,816],[2143,817],[2145,814],[2124,818],[2127,814],[2103,819],[2126,820],[2134,821],[2135,822],[2131,823],[2146,824],[2133,825],[2108,826],[2154,827],[2150,828],[2151,829],[2147,830],[2125,831],[2114,832],[2118,833],[2092,834],[2122,835],[2123,836],[2119,837],[2093,838],[2104,839],[2144,822],[2087,840],[2152,2],[2117,841],[2107,842],[2139,2],[2211,843],[2212,844],[2213,771],[2180,2],[2210,771],[2201,2],[2128,2],[2100,2],[2176,845],[2129,2],[2080,771],[2208,846],[2106,847],[2140,848],[2105,849],[2181,850],[2120,2],[2156,2],[2157,851],[2101,2],[2121,2],[2206,2],[2076,74],[2083,852],[2081,2],[2743,853],[2742,854],[2698,855],[2697,856],[641,2],[548,857],[547,339],[406,858],[615,74],[553,2],[1016,2],[604,2],[537,2],[538,859],[2665,860],[2664,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],[117,861],[127,862],[116,861],[137,863],[108,864],[107,865],[136,662],[130,866],[135,867],[110,868],[124,869],[109,870],[133,871],[105,872],[104,662],[134,873],[106,874],[111,875],[112,2],[115,875],[102,2],[138,876],[128,877],[119,878],[120,879],[122,880],[118,881],[121,882],[131,662],[113,883],[114,884],[123,885],[103,886],[126,877],[125,875],[129,2],[132,887],[2667,888],[2663,2],[2666,889],[3337,890],[3321,2],[3322,2],[3324,891],[3325,2],[3323,2],[3326,891],[3327,891],[3329,892],[3328,891],[3330,891],[3331,892],[3332,891],[3333,2],[3334,891],[3335,2],[3336,2],[2660,893],[2659,340],[2662,894],[2661,895],[2073,896],[2096,897],[606,898],[592,899],[593,898],[591,2],[544,900],[580,901],[550,902],[545,900],[543,2],[549,903],[578,2],[573,2],[577,904],[552,2],[579,905],[612,906],[605,907],[598,908],[607,909],[586,910],[1191,911],[1192,912],[609,913],[1193,914],[610,915],[599,916],[1190,917],[611,918],[2609,919],[1199,920],[585,2],[2022,921],[2029,922],[2024,2],[2025,2],[2023,923],[2026,924],[2018,2],[2019,2],[2030,925],[2021,926],[2027,2],[2028,927],[2020,928],[1259,929],[1262,930],[1260,930],[1256,929],[1263,931],[1264,932],[1261,930],[1257,933],[1258,934],[1252,935],[1204,936],[1206,937],[1250,2],[1205,938],[1251,939],[1255,940],[1253,2],[1207,936],[1208,2],[1249,941],[1203,942],[1200,2],[1254,943],[1201,944],[1202,2],[1265,945],[1209,946],[1210,946],[1211,946],[1212,946],[1213,946],[1214,946],[1215,946],[1216,946],[1217,946],[1218,946],[1219,946],[1221,946],[1220,946],[1222,946],[1223,946],[1224,946],[1248,947],[1225,946],[1226,946],[1227,946],[1228,946],[1229,946],[1230,946],[1231,946],[1232,946],[1233,946],[1235,946],[1234,946],[1236,946],[1237,946],[1238,946],[1239,946],[1240,946],[1241,946],[1242,946],[1243,946],[1244,946],[1245,946],[1246,946],[1247,946],[2618,948],[2620,267],[2622,267],[2624,267],[2626,267],[2628,267],[2611,267],[2794,949],[2785,950],[1268,951],[1267,952],[1266,953],[2791,954],[2784,955],[2782,956],[2793,957],[2783,958],[2792,959],[2788,960],[2787,961],[2786,962],[1189,267],[2789,963],[2819,964],[2817,965],[2818,966],[2746,967],[2835,968],[2836,968],[2825,969],[2837,970],[2823,971],[1269,267],[2838,972],[2827,973],[1271,974],[1270,975],[2822,976],[2839,977],[2840,978],[2828,979],[1273,980],[2841,981],[2826,982],[2820,983],[2833,984],[2831,985],[2834,986],[2830,987],[2829,988],[2821,989],[2824,990],[2832,991],[2842,992],[2778,993],[2843,994],[2848,995],[2845,996],[2844,997],[2847,998],[2856,999],[2849,1000],[2857,1001],[2853,1002],[1275,1003],[1274,267],[2855,1004],[2851,1005],[2850,1006],[1276,267],[2858,1007],[2852,1008],[2854,1009],[2873,1010],[2870,1011],[2874,1012],[2860,1013],[2863,1014],[2862,1015],[1277,267],[1279,1016],[1278,1017],[2876,1018],[2877,1018],[2864,1019],[2875,1020],[2861,1021],[1280,267],[2866,1022],[2865,1023],[2878,1024],[2867,1025],[1282,1026],[1281,1027],[2879,1028],[2880,1029],[2868,1030],[1074,267],[2872,1031],[2869,1032],[2859,104],[2871,1033],[2653,1034],[1284,1035],[1283,1036],[2979,1037],[2976,1038],[2980,1039],[2971,1040],[1287,1041],[1286,1042],[2981,1043],[2982,1043],[2977,1044],[1289,1045],[1288,267],[2983,1046],[2972,1047],[2984,1048],[2883,1049],[2985,1050],[2974,1051],[2986,1052],[2975,1053],[2987,1054],[2882,1055],[1297,1056],[1296,1057],[2988,1058],[2989,1059],[1295,1060],[1299,1061],[1298,1062],[2978,1063],[2991,1064],[1310,1065],[2992,1066],[2993,1067],[1308,1068],[2994,1069],[2995,1069],[1330,1070],[2996,1071],[1325,1072],[1331,1073],[2999,1074],[1319,1075],[3000,1076],[1317,1077],[3001,1078],[1316,1079],[1355,1080],[1315,1081],[1311,1082],[1356,1083],[1318,1084],[2997,1085],[1307,1086],[1332,1087],[1326,1088],[2998,1089],[1309,1086],[1302,267],[1352,1090],[1329,1091],[1353,1092],[1327,1093],[1354,1094],[1328,1093],[2990,1095],[3073,1096],[3063,1097],[3075,1098],[3074,1099],[3076,1100],[3066,1101],[3077,1102],[3069,1103],[3078,1104],[3068,1105],[3079,1106],[3067,1107],[3072,1108],[3071,1109],[3040,1110],[3041,1111],[3018,1112],[1361,267],[3021,1113],[3051,1114],[3009,1115],[3007,1116],[3052,1117],[3010,1118],[3053,1119],[3022,1120],[3054,1121],[3023,1122],[3055,1123],[3056,1124],[3003,1125],[3057,1126],[3004,1127],[3006,1128],[3058,1129],[3002,1130],[3005,1113],[3059,1131],[1649,1132],[3060,1133],[3008,1134],[3061,1135],[1362,1136],[1363,1137],[3042,1138],[3030,1139],[3043,1140],[3028,1141],[1357,267],[1360,1142],[1359,1143],[3044,1144],[3029,1145],[3045,1146],[3046,1147],[3024,1148],[3047,1149],[1358,1150],[3012,1151],[3013,1152],[3048,1153],[3020,1154],[3011,1155],[3037,1156],[3032,1157],[3019,1158],[3034,1159],[3026,1160],[3035,1161],[3027,1162],[3036,1163],[3025,1164],[3014,1165],[3049,1166],[3015,1167],[3050,1168],[3016,1169],[3038,1170],[3039,1171],[3031,1172],[3062,1173],[3017,1174],[3033,1175],[1385,1176],[1386,1177],[1384,1178],[1387,1179],[1388,1179],[1390,1180],[1389,1181],[1098,1182],[1391,1183],[1393,1184],[1392,1185],[1417,1186],[1419,1187],[1418,1188],[1421,1189],[1420,1183],[1423,1190],[1422,1183],[1425,1191],[1424,1183],[1428,1192],[1427,1193],[1429,1194],[1092,267],[3081,1195],[1416,1196],[1430,975],[1432,1197],[1431,1198],[1433,1197],[1434,1199],[1436,1200],[1435,1201],[1438,1202],[1437,1203],[1440,1204],[1439,1201],[1441,1201],[1442,1182],[1444,1205],[1443,1201],[1446,1206],[1447,1207],[1445,1208],[1448,1209],[1450,1210],[1449,1209],[1451,1182],[1452,1211],[1453,1183],[1454,1201],[1455,1182],[1457,1212],[1456,1201],[1459,1213],[1458,1214],[1461,1215],[1460,1216],[1462,1216],[1464,1217],[1463,1182],[1465,1218],[1162,1201],[1467,1219],[1466,1220],[1468,1221],[1367,1201],[1471,1222],[1470,1223],[1473,1224],[1472,1223],[1475,1225],[1474,1226],[1476,1227],[1469,1178],[1478,1228],[1477,1223],[1480,1229],[1479,1182],[1482,1230],[1481,1201],[1380,1231],[1484,1232],[1483,1201],[1485,1183],[1487,1233],[1489,1234],[1488,1188],[1491,1235],[1490,1236],[1493,1237],[1492,1211],[1495,1238],[1494,1201],[1497,1239],[1496,1211],[1498,1240],[1500,1241],[1499,1242],[1502,1243],[1501,1244],[1504,1245],[1503,1246],[1505,1247],[1093,1182],[1508,1248],[1507,1249],[1509,1250],[1506,1182],[1511,1251],[1510,1182],[1364,1252],[1365,1253],[1094,1254],[1369,1255],[1371,1256],[1372,1256],[1374,1257],[1373,1256],[1376,1258],[1375,1256],[1377,1256],[1378,1259],[1368,1260],[1381,1261],[1513,1262],[1512,1182],[1515,1263],[1514,1201],[1517,1264],[1516,1178],[3080,1265],[1383,1266],[2750,1267],[2747,1268],[2745,1269],[3094,1270],[3114,1271],[3119,1272],[3159,1273],[3160,1274],[3139,1275],[1519,1276],[1518,1277],[1522,1278],[1521,1279],[3124,1280],[1524,1281],[1525,1282],[1523,1283],[3161,1284],[3136,1285],[3127,1286],[3157,1287],[3177,1288],[3140,1289],[3178,1290],[3129,1291],[3179,1292],[3148,1293],[3180,1294],[3128,1295],[3181,1296],[3143,1297],[3182,1298],[3183,1299],[3142,1300],[3184,1301],[3144,1302],[3185,1303],[3151,1304],[3186,1305],[3130,1306],[3187,1307],[3156,1308],[1527,1309],[1526,1310],[3176,1311],[1528,1312],[3164,1313],[3162,1314],[3135,1315],[3163,1316],[3147,1317],[3165,1318],[3132,1319],[3166,1320],[3141,1321],[3167,1322],[3115,1323],[3116,1324],[3169,1325],[3118,1326],[3168,1327],[3117,1328],[1530,1329],[1529,1330],[3170,1331],[3122,1332],[3120,1333],[3134,1334],[3171,1335],[3133,1336],[3172,1337],[3125,1338],[3131,1339],[1608,1340],[3121,1341],[3126,1342],[3152,1343],[1610,1344],[1609,1345],[3173,1346],[3153,1347],[3174,1348],[3123,1349],[3175,1350],[3150,1351],[3188,1352],[1520,1323],[3158,1353],[3196,1354],[3189,1355],[3197,1356],[3190,1357],[3198,1358],[3192,1359],[3191,1360],[3199,1361],[3193,1362],[3195,1363],[3194,1364],[3218,1365],[3292,1366],[3245,1367],[3293,1368],[3244,1369],[1622,1370],[1621,1371],[3296,1372],[3252,1373],[3251,1374],[3250,1375],[1624,1376],[1623,267],[3294,1377],[3283,1378],[3243,1379],[3295,1380],[3288,1381],[1617,1382],[1616,1383],[3291,1384],[3290,1385],[3297,1386],[3262,1387],[3246,1388],[3253,1389],[3298,1390],[3282,1391],[3267,1392],[3286,1393],[3284,1394],[3278,1395],[3289,1396],[1618,1397],[1626,1398],[1625,267],[1188,975],[3303,1399],[3301,1400],[3302,1401],[3317,1402],[3315,1403],[3318,1404],[3314,1405],[3313,1406],[3308,1407],[3307,1408],[3316,1409],[2780,1410],[2779,1411],[3424,1412],[3446,1413],[3416,1414],[3447,1415],[3438,1416],[3448,1417],[3425,1418],[3449,1419],[3417,1420],[1628,1421],[3426,1422],[3418,1423],[3450,1424],[3419,1425],[3451,1426],[3433,1427],[3452,1428],[3437,1429],[3453,1430],[3427,1431],[3420,1432],[3454,1433],[3421,1434],[3455,1435],[3422,1436],[3456,1437],[3423,1438],[3457,1439],[3436,1440],[3431,1441],[3434,1423],[3430,1425],[3432,1442],[3435,1443],[1630,1444],[1629,267],[3458,1445],[3443,1446],[3459,1447],[3441,1448],[3460,1449],[3439,1450],[3461,1451],[3442,1452],[3463,1453],[3462,1454],[3464,1455],[3440,1456],[1633,1457],[1632,1458],[3320,1459],[1638,1460],[1637,1461],[1640,1462],[3340,1463],[3465,1464],[3408,1465],[3466,1466],[3409,1467],[3467,1468],[3410,1469],[3468,1470],[3411,1471],[1631,975],[3412,1469],[3413,1469],[3415,1471],[3445,1472],[3444,1473],[3489,1474],[3479,1475],[3490,1476],[3473,1477],[3491,1478],[3484,1479],[3487,1480],[3476,1481],[3475,1482],[1643,1483],[1642,1484],[3492,1485],[3482,1486],[3493,1487],[3474,1488],[3494,1489],[3477,1490],[3495,1491],[3485,1492],[3496,1493],[3471,1494],[3497,1495],[3472,1496],[3498,1497],[3481,1498],[3499,1499],[3480,1500],[3488,1501],[3470,1502],[3469,1503],[1645,1504],[1644,267],[3500,1505],[3483,1506],[3478,1132],[3486,1507],[3511,1508],[3506,1509],[3512,1510],[3505,1511],[3513,1512],[3504,1513],[3503,1514],[3516,1515],[3517,1516],[3501,1517],[3518,1518],[3519,1519],[3502,1520],[3520,1521],[1924,1522],[1646,953],[1926,1523],[1925,1524],[3514,1525],[3509,1526],[3515,1527],[3508,1528],[3507,1529],[3510,1530],[3549,1531],[3526,1532],[3550,1533],[3546,1534],[3545,1535],[3562,1536],[3535,1537],[3567,1538],[3540,1539],[3563,1540],[3536,1541],[3564,1542],[3539,1452],[3565,1543],[3537,1544],[1930,1545],[1931,1546],[3566,1547],[3534,1134],[3538,104],[3554,1548],[3532,1549],[3542,1550],[3544,1551],[3555,1552],[3529,1553],[3556,1554],[3524,1555],[3557,1556],[3528,1557],[3558,1558],[3533,1559],[3559,1560],[3541,1561],[3560,1562],[3530,1563],[1927,267],[1929,1564],[1928,1565],[3561,1566],[3543,1567],[3551,1568],[3525,1569],[3521,1570],[3548,1571],[3523,1572],[3522,1573],[3552,1574],[3527,1575],[3553,1576],[3531,1577],[3547,1578],[3569,1579],[2970,1580],[3568,1581],[3580,1582],[3581,1583],[3572,1584],[3578,1585],[3582,1586],[3570,1587],[1933,1588],[1932,267],[3586,1589],[3587,1589],[3577,1590],[3583,1591],[3574,1592],[3573,1593],[3584,1594],[3575,1595],[3585,1596],[3576,1597],[3571,267],[3579,1598],[3595,1599],[3588,1600],[3593,1601],[3591,1602],[3594,1603],[3590,1604],[3589,1605],[3592,1606],[3605,1607],[3599,1608],[3603,1609],[3600,1610],[3604,1611],[3596,1612],[3602,1613],[3598,1614],[3597,1615],[3601,1616],[3613,1617],[3620,1618],[3623,1619],[3622,1620],[3621,1621],[3626,1622],[3625,1623],[3624,1624],[3650,1625],[3634,1626],[3651,1627],[3635,1626],[3652,1628],[3636,1629],[3649,1630],[3637,1631],[3653,1632],[3641,1633],[1936,1634],[1938,1635],[1937,1636],[3654,1637],[3642,1638],[3655,1639],[3640,1640],[1935,1641],[1934,267],[3639,267],[3647,1642],[3643,1643],[3648,1644],[3645,1645],[3656,1646],[3644,1647],[1939,1648],[1294,1649],[3646,1650],[3667,1651],[3658,1652],[3670,1653],[3660,1654],[1942,1655],[1941,1656],[1943,1657],[1940,953],[3665,1658],[3668,1659],[3657,1660],[3669,1661],[3664,1662],[3672,1663],[3673,1664],[3663,1665],[3671,1666],[3662,1667],[3661,1668],[3666,1669],[3687,1670],[3688,1671],[3683,1672],[3689,1673],[3681,1674],[3680,1675],[3697,1676],[3685,1677],[1182,1678],[3690,1679],[1181,1680],[1180,1681],[3691,1682],[3682,1683],[3692,1684],[3684,1685],[3698,1686],[3699,1687],[3679,1688],[3693,1689],[3694,1690],[3677,1691],[3695,1692],[3676,1693],[3675,1694],[3696,1695],[3678,1696],[3686,1697],[3703,1698],[3702,1699],[3701,1700],[3700,1701],[3711,1702],[3713,1703],[3716,1704],[3705,1705],[3704,1706],[3718,1707],[3709,1708],[3708,1709],[3720,1710],[3722,1711],[3721,1712],[3724,1713],[3723,1714],[2638,1715],[3726,1716],[3727,1717],[3725,1718],[3728,1719],[3729,1720],[3730,1721],[3731,1722],[3733,1723],[3732,1724],[3737,1725],[3736,1726],[3738,1727],[3739,1728],[3735,1729],[3740,1730],[3734,1731],[3741,1732],[2296,267],[3761,1733],[3628,1734],[2034,1132],[1085,1735],[3864,1736],[3249,1737],[3260,267],[3856,1738],[3261,1739],[3866,1740],[3254,1741],[3867,1742],[3220,1743],[1619,267],[3857,1744],[3248,1745],[1994,1746],[1993,1747],[1996,1748],[1995,267],[1997,1749],[1110,1750],[3868,1751],[3224,1752],[1102,1753],[3858,1754],[1097,1755],[1998,1756],[1096,267],[1999,1757],[1078,1758],[2000,1759],[1108,1760],[3859,1761],[1106,1762],[3869,1763],[3255,1764],[1104,1765],[3247,1766],[3870,1767],[3257,1768],[2001,1769],[1100,267],[3860,1770],[1101,1771],[1109,1772],[3871,1773],[3256,1774],[3872,1775],[3258,1776],[2035,1132],[3873,1777],[3259,1778],[3861,1779],[2036,1780],[3862,1781],[1105,1782],[2002,1783],[1107,1784],[3863,1785],[1103,1786],[3762,1787],[3273,1788],[3874,1789],[1650,1790],[1272,1036],[3785,1791],[3200,1792],[3791,1793],[3201,1794],[3792,1795],[3203,1796],[3793,1797],[3205,1798],[3786,1799],[3202,1792],[3787,1800],[3217,1801],[3788,1802],[3206,1792],[3212,1803],[3789,1804],[3210,1805],[3790,1806],[3209,1807],[3084,1808],[3875,1809],[3083,1810],[3742,1811],[1950,1812],[3763,1813],[3659,1814],[1888,1150],[3706,1815],[2011,1816],[3876,1817],[2010,1818],[3877,1819],[3715,1820],[2009,1821],[3710,1822],[3878,1823],[3717,1824],[3879,1825],[3714,1826],[3880,1827],[3707,1828],[3712,1829],[2003,1830],[3719,1831],[2012,1832],[2004,1833],[3881,1834],[3215,1835],[3428,1836],[1627,267],[3882,1837],[3429,1838],[3883,1839],[1635,1840],[1636,1545],[2014,1841],[2013,1842],[3213,1843],[3211,1844],[1034,1036],[3764,1845],[3629,1846],[3794,1847],[3090,1848],[3795,1849],[3796,1850],[3087,1851],[3797,1852],[3085,1438],[3086,1853],[3798,1854],[3089,1855],[1962,1856],[1961,267],[3799,1857],[3800,1858],[3088,1859],[1426,267],[1324,1860],[1651,1861],[2759,1862],[1652,1021],[1072,1863],[3884,1864],[2752,1865],[3885,1866],[2760,1867],[2748,975],[3905,1868],[3304,1869],[3906,1870],[3305,1871],[3907,1872],[3306,1873],[2015,1312],[3908,1874],[3207,1875],[3909,1876],[3208,1877],[3886,1878],[1653,1879],[3887,1880],[2753,1881],[3888,1882],[2652,1883],[3238,1884],[3889,1885],[3231,1886],[3890,1887],[1886,1888],[3891,1889],[1885,1890],[3892,1891],[1071,1892],[3894,1893],[3893,1812],[3895,1894],[1904,1895],[3896,1896],[3272,1897],[1887,1790],[3271,1898],[3897,1899],[1891,1900],[1905,1901],[3898,1902],[1892,1903],[3899,1904],[1902,1905],[2017,1906],[2016,1907],[3900,1908],[2761,1909],[3902,1910],[1303,1911],[1903,1912],[3903,1913],[3638,1914],[3904,1915],[3228,1916],[3901,1917],[3630,1918],[2795,104],[3743,1919],[1911,1920],[3744,1921],[2649,1922],[3745,1923],[2654,1924],[3801,1925],[3097,1926],[3802,1927],[3096,1928],[3095,1929],[3803,1930],[3100,1931],[3804,1932],[3099,1933],[3098,1934],[3746,1935],[2846,1936],[2038,1937],[2039,1938],[2037,1939],[3910,1940],[2040,1941],[2041,1942],[1033,1943],[3765,1944],[3082,1945],[3805,1946],[1971,1947],[3806,1948],[1966,1949],[3807,1950],[1967,1951],[3808,1952],[1968,1953],[1973,1954],[1965,1955],[3809,1956],[1972,1957],[1974,1958],[1970,1959],[3911,1960],[2769,1961],[2042,267],[3747,1962],[3232,1963],[3064,1964],[3810,1965],[3065,104],[1975,267],[3748,1966],[1320,1537],[3766,1967],[2233,267],[1944,1968],[1170,267],[3912,1969],[1912,1970],[1913,1971],[3915,1972],[1081,975],[2045,1973],[2044,1974],[1032,1975],[3913,1976],[2043,1977],[1031,1978],[2047,1979],[2046,1980],[3914,1981],[1914,1982],[2049,1983],[2048,1984],[2051,1985],[2050,104],[3767,1986],[3268,1987],[3768,1988],[1958,1989],[3749,1990],[2656,1991],[3916,1992],[3319,1993],[1639,267],[3917,1994],[1082,1995],[2053,1996],[2052,1323],[3918,1997],[3414,1998],[3769,1999],[2762,2000],[2054,2001],[3919,2002],[1915,2003],[3920,2004],[1918,2005],[3921,2006],[3149,2007],[1075,267],[1917,2008],[3922,2009],[3338,2010],[3923,2011],[1073,267],[2056,2012],[2055,1088],[3924,2013],[3263,2014],[3925,2015],[3266,2016],[3926,2017],[3265,2018],[3264,2019],[3927,2020],[3221,2021],[3928,2022],[3281,2023],[3929,2024],[3280,2025],[3279,2026],[3930,2027],[3242,2028],[2057,267],[3204,2029],[3285,2030],[3770,2031],[3227,2032],[3225,2033],[3811,2034],[2781,2035],[1977,2036],[1976,267],[3931,2037],[3219,1873],[3932,2038],[1306,2039],[3933,2040],[2962,2041],[3771,2042],[2651,2043],[3813,2044],[2641,2045],[3814,2046],[2643,2047],[1978,2048],[1951,267],[1979,267],[3815,2049],[2644,2050],[3816,2051],[2650,2052],[3812,2053],[2646,2054],[3817,2055],[2648,2056],[1945,2057],[1187,2058],[3750,2059],[2655,2060],[625,1036],[2766,2061],[3772,2062],[1910,2063],[3936,2064],[3937,2065],[1923,2066],[2058,2067],[1921,2068],[3934,2069],[3935,2070],[2767,2071],[2060,2072],[2059,267],[2061,2073],[1922,267],[2064,2074],[2063,2075],[3939,2076],[3310,2077],[2066,2078],[2065,2079],[3940,2080],[3309,2081],[2062,953],[3938,2082],[3312,2083],[1946,267],[1960,2084],[1959,2085],[3773,2086],[3274,2087],[3818,2088],[3276,2089],[3275,2090],[3819,2091],[3277,2092],[3774,2093],[3632,2094],[2765,2095],[3941,2096],[2764,2097],[2763,2098],[3942,2099],[2770,2100],[1641,267],[3775,2101],[3287,2102],[3776,2103],[1305,2104],[3777,2105],[3216,2106],[3214,2107],[3778,2108],[3269,2109],[3779,2110],[3270,2111],[3948,2112],[2884,2113],[3943,2114],[1893,1134],[3944,2115],[1894,1134],[3945,2116],[1897,2117],[3946,2118],[1895,1021],[3947,2119],[1896,2120],[3951,2121],[2969,2122],[3949,2123],[2968,2124],[2068,2125],[2067,2126],[3950,2127],[2967,2128],[2966,2129],[2965,2130],[2069,267],[1486,267],[3751,2131],[2796,2132],[3952,2133],[3233,2134],[3780,2135],[3093,2136],[1980,267],[3820,2137],[2811,2138],[3821,2139],[2813,2140],[3822,2141],[2812,1438],[3823,2142],[2797,2143],[3824,2144],[3146,2145],[3825,2146],[3145,2147],[1982,2148],[1981,1471],[3826,2149],[2814,2150],[1983,953],[1984,1150],[3832,2151],[2800,2152],[3833,2153],[2799,2154],[3834,2155],[2801,2156],[3835,2157],[3836,2158],[2802,2159],[3827,2160],[2803,1873],[3828,2161],[2804,2162],[3829,2163],[2807,2164],[3830,2165],[2805,1438],[3831,2166],[2806,2167],[1986,2168],[1985,2169],[3837,2170],[2808,2171],[3838,2172],[2809,2173],[3839,2174],[2810,2175],[3840,2176],[3092,2177],[3091,2178],[1987,267],[3841,2179],[1901,2180],[3842,2181],[1898,2182],[3843,2183],[2963,2184],[1899,2185],[3845,2186],[2964,2187],[3844,2188],[1900,2189],[3070,104],[3968,2190],[2755,2191],[3953,2192],[1908,2193],[3954,2194],[3311,2195],[3969,2196],[3631,1731],[3977,2197],[2217,2198],[3978,2199],[2218,2198],[3979,2200],[2219,2201],[3980,2202],[2216,2203],[2070,267],[3981,2204],[2220,2198],[2222,2205],[3982,2206],[2221,2198],[3955,2207],[1952,1871],[3956,2208],[1919,2209],[1149,2210],[3970,2211],[3971,2212],[1153,2213],[3972,2214],[1155,2215],[3973,2216],[1152,2217],[3974,2218],[1157,2219],[3975,2220],[1160,2221],[3976,2222],[1159,2223],[1158,2224],[1161,2225],[1148,2226],[1964,267],[3957,2227],[1168,2228],[2768,267],[3983,2229],[1906,1892],[3229,2230],[3223,2231],[3958,2232],[1173,2233],[3959,2234],[1174,2235],[3960,2236],[1076,1132],[1889,1134],[3961,2237],[2749,2238],[3962,2239],[2973,2240],[3963,2241],[1648,2240],[3964,2242],[2881,2243],[2798,2244],[2757,2245],[3965,2246],[1026,1132],[3966,2247],[1949,2248],[2756,2249],[3984,2250],[1163,2251],[1164,2252],[3985,2253],[1165,2254],[3986,2255],[1167,2256],[3987,2257],[1169,2258],[1177,2259],[3988,2260],[1171,2261],[3989,2262],[1172,1636],[3990,2263],[1175,2264],[3991,2265],[1176,2266],[3967,2267],[2640,2268],[1647,2269],[3846,2270],[1955,2271],[3753,2272],[1957,2273],[3752,2274],[2815,2275],[3992,2276],[3339,2277],[623,267],[3993,2278],[3608,2279],[3607,2280],[3606,2281],[2772,2282],[3994,2283],[3995,2283],[3234,2284],[3230,2285],[3996,2286],[1890,2287],[4001,2288],[3236,2289],[2224,2290],[2223,267],[3997,2291],[3237,2292],[4002,2293],[3235,267],[2226,2294],[2225,267],[3998,2295],[3241,2296],[3999,2297],[3239,2298],[2228,2299],[2227,267],[4000,2300],[3240,2301],[2229,1211],[3755,2302],[3612,2303],[1989,2304],[1988,2305],[3847,2306],[3611,2307],[3610,2308],[3754,2309],[3609,2310],[2231,2311],[2230,267],[4006,2312],[2773,2313],[4007,2314],[4008,2315],[2774,2316],[2232,267],[2235,2317],[2234,2318],[2771,1903],[4003,2319],[2754,2320],[4004,2321],[4005,2322],[2758,2323],[3848,2324],[2647,2325],[3756,2326],[3615,2327],[3849,2328],[3614,2329],[3850,2330],[3618,2331],[1990,1183],[3851,2332],[3617,2333],[3852,2334],[3616,2335],[3757,2336],[3619,2337],[4009,2338],[1300,2339],[1907,2340],[4010,2341],[1953,2342],[4011,2343],[1099,2344],[4012,2345],[2639,997],[2642,2346],[4013,2347],[1020,2348],[1077,1875],[4014,2349],[2215,2350],[1156,2351],[1080,2352],[1025,2353],[1095,2354],[1314,2355],[4015,2356],[1029,2357],[2751,2358],[1023,2359],[1021,1875],[1027,1875],[1954,2360],[1083,2361],[4016,2362],[1948,2363],[4017,2364],[1030,2365],[1028,2366],[1154,2354],[1150,2367],[1084,2368],[2637,2369],[1079,2370],[1151,1875],[1301,2371],[1022,1875],[4018,2372],[1035,2373],[4019,2374],[1313,2375],[3758,2376],[2816,2377],[3759,2378],[3781,2379],[3222,2380],[3854,2381],[3300,2382],[3853,2383],[3627,2384],[1285,267],[1992,2385],[1991,267],[3782,2386],[3633,2387],[3783,2388],[2777,2389],[3760,2390],[2744,2391],[1111,267],[4020,2392],[1920,2393],[3784,2394],[3674,2395],[4032,2396],[3103,2397],[3104,2398],[4021,2399],[3102,2400],[3101,2401],[2242,267],[4022,2402],[2253,104],[2236,267],[4023,2403],[2252,2404],[2251,2405],[2240,2406],[4033,2407],[2239,104],[2249,2408],[2248,104],[4034,2409],[2250,2410],[4035,2411],[2247,104],[4028,2412],[4029,2412],[3113,2413],[4030,2414],[3105,2415],[2237,1383],[4036,2416],[2243,2417],[4037,2418],[2272,2419],[2241,267],[2245,2420],[4038,2421],[2275,2422],[2282,2423],[4039,2424],[2276,2425],[4040,2426],[2259,2427],[4041,2428],[2280,2429],[4042,2430],[2281,2431],[4043,2432],[2277,2433],[2269,267],[2270,2434],[4044,2435],[2279,2436],[4045,2437],[2278,2438],[4046,2439],[1183,2440],[4047,2441],[2271,2442],[4048,2443],[2274,2444],[4049,2445],[2273,2446],[4050,2447],[2256,267],[4051,2448],[2255,2449],[2246,2450],[2283,2451],[2260,267],[4031,2452],[3106,2453],[3107,2454],[4024,2455],[3108,2456],[4025,2457],[3112,2458],[3111,2459],[4026,2460],[3110,2461],[2263,2462],[2268,2463],[2264,2464],[2265,2465],[2266,2466],[4052,2467],[2267,2468],[2261,267],[2284,2469],[2262,2470],[4027,2471],[3109,267],[2238,2472],[2254,2473],[3226,267],[3299,2474],[2775,2475],[3855,2476],[2776,2477],[2635,2478],[2005,2479],[4053,2480],[2645,2481],[2636,2482],[1947,2483],[2290,2484],[2288,2484],[2287,2484],[2289,2485],[2286,2484],[2285,2484],[2291,975],[4057,2486],[2294,2487],[1312,104],[4054,2488],[3137,2489],[4055,2490],[1323,2491],[3138,2492],[4056,2493],[3154,2494],[3155,2495],[2292,104],[2293,2496],[2295,2497],[1304,2498],[2298,2499],[2297,2500],[2299,2501],[1019,2502],[2302,2503],[2301,2504],[2304,2505],[2303,267],[4058,2506],[2033,2507],[2305,2508],[2306,2508],[1293,2509],[2307,2510],[616,267],[2308,2511],[1184,267],[2309,2512],[1185,2513],[624,2],[1186,267],[2300,2514],[617,2515],[614,267],[2310,2516],[2311,2517],[1366,2518],[2312,2519],[620,2520],[2313,2521],[1166,2522],[1415,267],[1179,2523],[2314,267],[2316,2524],[2315,267],[2317,2525],[622,2526],[2577,2527],[2576,2528],[2579,2529],[2578,267],[2580,2530],[1956,267],[2581,2531],[1370,267],[2582,267],[2584,2532],[2583,267],[2585,2533],[619,2534],[2586,2535],[1916,267],[2587,2536],[1178,975],[2588,2537],[1620,2538],[2589,267],[2590,2539],[1634,267],[2591,2540],[1379,975],[2594,2541],[2593,2542],[2597,2543],[2596,2544],[2598,2545],[2595,267],[2599,2546],[1086,267],[2600,2547],[1087,975],[618,267],[2601,2548],[1382,2523],[2602,2549],[1963,1984],[2603,2550],[1070,267],[4059,2551],[2619,2552],[2621,2553],[2623,2554],[2625,2555],[2627,2556],[2629,2557],[2608,2558],[2610,2559],[2612,2560],[2630,2378],[3865,1310],[2613,2561],[2617,2562],[2790,2563],[4060,2564],[613,2565]],"semanticDiagnosticsPerFile":[[1444,[{"start":5247,"length":6,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'undefined'."}]],[1447,[{"start":1996,"length":15,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."},{"start":3425,"length":10,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."}]],[1495,[{"start":643,"length":6,"code":2739,"category":1,"messageText":"Type '{ google_client_id: string; google_client_secret: string; microsoft_client_id: string; microsoft_client_secret: string; microsoft_tenant: string; generic_client_id: string; generic_client_secret: string; ... 7 more ...; team_mappings: { ...; }; }' is missing the following properties from type 'SSOSettingsValues': saml_idp_metadata_url, saml_idp_metadata_xml, saml_sp_entity_id, saml_allow_unsolicited, generic_scope","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/sso/usessosettings.ts","start":1521,"length":6,"messageText":"The expected type comes from property 'values' which is declared here on type 'SSOSettingsResponse'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ google_client_id: string; google_client_secret: string; microsoft_client_id: string; microsoft_client_secret: string; microsoft_tenant: string; generic_client_id: string; generic_client_secret: string; ... 7 more ...; team_mappings: { ...; }; }' is not assignable to type 'SSOSettingsValues'."}},{"start":7416,"length":6,"code":2739,"category":1,"messageText":"Type '{ google_client_id: null; google_client_secret: null; microsoft_client_id: null; microsoft_client_secret: null; microsoft_tenant: null; generic_client_id: null; generic_client_secret: null; ... 7 more ...; team_mappings: { ...; }; }' is missing the following properties from type 'SSOSettingsValues': saml_idp_metadata_url, saml_idp_metadata_xml, saml_sp_entity_id, saml_allow_unsolicited, generic_scope","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/sso/usessosettings.ts","start":1521,"length":6,"messageText":"The expected type comes from property 'values' which is declared here on type 'SSOSettingsResponse'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ google_client_id: null; google_client_secret: null; microsoft_client_id: null; microsoft_client_secret: null; microsoft_tenant: null; generic_client_id: null; generic_client_secret: null; ... 7 more ...; team_mappings: { ...; }; }' is not assignable to type 'SSOSettingsValues'."}}]],[1517,[{"start":1402,"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":30475,"length":6,"messageText":"'models' is declared here.","category":3,"code":2728},{"file":"./src/components/networking.tsx","start":30782,"length":5,"messageText":"The expected type comes from property 'users' which is declared here on type 'UserListResponse'","category":3,"code":6500}]}]],[1929,[{"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'."}}]],[1939,[{"start":4983,"length":43,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type 'SpendMetrics' to type 'Record' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Index signature for type 'string' is missing in type 'SpendMetrics'.","category":1,"code":2329}]}}]],[1994,[{"start":497,"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":553,"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":681,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":729,"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":835,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":886,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":935,"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":1009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1135,"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":1374,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1431,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1486,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[1996,[{"start":425,"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":474,"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":690,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":955,"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":1264,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1463,"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":1694,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1795,"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":1851,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2039,"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":2311,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2503,"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":2770,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2871,"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":3139,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3482,"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":3755,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4094,"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":4354,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4619,"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":4691,"length":12,"messageText":"Parameter 'defaultModel' implicitly has an 'any' type.","category":1,"code":7006},{"start":4905,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[1997,[{"start":507,"length":10,"code":2739,"category":1,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: string[]; COMPLEX: string[]; REASONING: string[]; }; tierLabels: undefined; classifierType: \"heuristic\"; classifierLlmConfig: undefined; classifierContextWindowSize: undefined; ... 15 more ...; returnRawModelName: false; }' is missing the following properties from type 'BuildComplexityRouterConfigParams': defaultModel, planModeMinTier, heuristicFirstMaxTier","canonicalHead":{"code":2322,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: string[]; COMPLEX: string[]; REASONING: string[]; }; tierLabels: undefined; classifierType: \"heuristic\"; classifierLlmConfig: undefined; classifierContextWindowSize: undefined; ... 15 more ...; returnRawModelName: false; }' is not assignable to type 'BuildComplexityRouterConfigParams'."}},{"start":1221,"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":1271,"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":1435,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1639,"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":1863,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1956,"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":2147,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2204,"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":2450,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2543,"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":2803,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2851,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2950,"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":3246,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3309,"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":3756,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3815,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3883,"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":4294,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4361,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4434,"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":4780,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4847,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4920,"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":5311,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5375,"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":5830,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6087,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6150,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6217,"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":6637,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6694,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6768,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6820,"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":7266,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7328,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7380,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7437,"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":7542,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7604,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7664,"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":8384,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8584,"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":8908,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8953,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9006,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9064,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9123,"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":9277,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9340,"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":9541,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9594,"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":9805,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9859,"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":10014,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10072,"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":10382,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10422,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10496,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10549,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10604,"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":10949,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10989,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11051,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11116,"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":11159,"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":11223,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11307,"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":11380,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11548,"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":11636,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11824,"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":11960,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12133,"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":12200,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12328,"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":12450,"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":12533,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12683,"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":12748,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12918,"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":13006,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13177,"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":13260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13418,"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":13465,"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":13530,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13797,"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":13890,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14140,"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":14214,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14399,"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":14588,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":14596,"length":8,"messageText":"Parameter 'keywords' implicitly has an 'any' type.","category":1,"code":7006},{"start":14615,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14868,"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":14943,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14957,"length":24,"messageText":"Expected 2 arguments, but got 1.","category":1,"code":2554,"relatedInformation":[{"file":"./src/components/add_model/build_complexity_router_config.ts","start":8516,"length":24,"messageText":"An argument for 'rows' was not provided.","category":3,"code":6210}]},{"start":15302,"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":15391,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15546,"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":15790,"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":15972,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16051,"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":16277,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16357,"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":16616,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16700,"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":16826,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16912,"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":17147,"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":17556,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17654,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17737,"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":18067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18148,"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":18220,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18371,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18528,"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":18614,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18713,"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":18956,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19113,"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":19480,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19571,"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":19966,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20052,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20165,"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":20267,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20442,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20597,"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":20631,"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":20710,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20796,"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":21092,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21145,"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":21377,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21457,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21555,"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":21763,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21818,"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":21859,"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":21905,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21964,"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":22116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22176,"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":22265,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22359,"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":22458,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22552,"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":22631,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22722,"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":22794,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22886,"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":22977,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23049,"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":23089,"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":23160,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23223,"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":23265,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23389,"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":23559,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23638,"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":23688,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23760,"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":23830,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23886,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23951,"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":24432,"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":24575,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24633,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24698,"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":24735,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24824,"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":24927,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25035,"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":25140,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25249,"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":25414,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25584,"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":25744,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25806,"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":25882,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26055,"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":26100,"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":26292,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26358,"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":26496,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26556,"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":26747,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26815,"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":26955,"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":27005,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27116,"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":27172,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27282,"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":27383,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27506,"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":27574,"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":27656,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27755,"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":28006,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28047,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28208,"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":28254,"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":28340,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28427,"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":28523,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28676,"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":28729,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28882,"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":28987,"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":29049,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29159,"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":29237,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29415,"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":29520,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29700,"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":29805,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29952,"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":30345,"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":30464,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30524,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30589,"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":30771,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30865,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30924,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30987,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31054,"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":31330,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[1998,[{"start":196,"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":238,"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":501,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":595,"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":679,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":786,"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":890,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":976,"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":1134,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1208,"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":1349,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1425,"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":1463,"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":1587,"length":12,"messageText":"Parameter 'systemPrompt' implicitly has an 'any' type.","category":1,"code":7006},{"start":1601,"length":8,"messageText":"Parameter 'expected' implicitly has an 'any' type.","category":1,"code":7006},{"start":1620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1707,"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":1746,"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":1823,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1920,"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":2040,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2039,[{"start":2106,"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":2163,"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":2357,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2427,"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":2615,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2687,"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":2905,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2970,"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":3155,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3235,"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":3411,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3485,"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":3799,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2043,[{"start":1600,"length":17,"code":2322,"category":1,"messageText":{"messageText":"Type '{ budget_limit: number; time_period: string; } | { max_budget: number; budget_duration: string; }' is not assignable to type 'ModelBudgetConfig'.","category":1,"code":2322,"next":[{"messageText":"Type '{ max_budget: number; budget_duration: string; }' is missing the following properties from type 'ModelBudgetConfig': budget_limit, time_period","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ max_budget: number; budget_duration: string; }' is not assignable to type 'ModelBudgetConfig'."}}]}},{"start":2144,"length":12,"code":2322,"category":1,"messageText":{"messageText":"Type 'string | number' is not assignable to type 'number'.","category":1,"code":2322,"next":[{"messageText":"Type 'string' is not assignable to type 'number'.","category":1,"code":2322}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","start":506,"length":12,"messageText":"The expected type comes from property 'budget_limit' which is declared here on type 'ModelBudgetConfig'","category":3,"code":6500}]},{"start":2388,"length":12,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'number'.","relatedInformation":[{"file":"./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","start":506,"length":12,"messageText":"The expected type comes from property 'budget_limit' which is declared here on type 'ModelBudgetConfig'","category":3,"code":6500}]},{"start":3742,"length":8,"code":2739,"category":1,"messageText":"Type '{ max_budget: number; budget_duration: string; tpm_limit: number; }' is missing the following properties from type 'ModelBudgetConfig': budget_limit, time_period","canonicalHead":{"code":2322,"messageText":"Type '{ max_budget: number; budget_duration: string; tpm_limit: number; }' is not assignable to type 'ModelBudgetConfig'."}}]],[2298,[{"start":31656,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: ({ model_name: string; litellm_params: { reasoning_effort: string; temperature: number; }; } | { ...; })[]; }; classifier_type: \"heuristic\"; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: ({ model_name: string; litellm_params: { reasoning_effort: string; temperature: number; }; } | { ...; })[]; }; classifier_type: \"heuristic\"; }' is missing the following properties from type 'ComplexityRouterConfigPayload': session_affinity, deployment_affinity","category":1,"code":2739}]}}]],[2306,[{"start":3271,"length":4,"code":2322,"category":1,"messageText":{"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; } & {}'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key_alias: string; }' is missing the following properties from type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }': aliases, allowed_cache_controls, allowed_routes, auto_rotate, and 7 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }'."}}]},"relatedInformation":[{"file":"./node_modules/openapi-fetch/dist/index.d.mts","start":3474,"length":4,"messageText":"The expected type comes from property 'body' which is declared here on type '{ params?: { query?: undefined; header?: { \"litellm-changed-by\"?: string | null | undefined; } | undefined; path?: undefined; cookie?: undefined; } | undefined; } & { body: { access_group_ids?: string[] | ... 1 more ... | undefined; ... 50 more ...; user_id?: string | ... 1 more ... | undefined; } & {}; } & { ...; }...'","category":3,"code":6500}]},{"start":3928,"length":4,"code":2322,"category":1,"messageText":{"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; } & {}'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key_alias: string; }' is missing the following properties from type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }': aliases, allowed_cache_controls, allowed_routes, auto_rotate, and 7 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }'."}}]},"relatedInformation":[{"file":"./node_modules/openapi-fetch/dist/index.d.mts","start":3474,"length":4,"messageText":"The expected type comes from property 'body' which is declared here on type '{ params?: { query?: undefined; header?: { \"litellm-changed-by\"?: string | null | undefined; } | undefined; path?: undefined; cookie?: undefined; } | undefined; } & { body: { access_group_ids?: string[] | ... 1 more ... | undefined; ... 50 more ...; user_id?: string | ... 1 more ... | undefined; } & {}; } & { ...; }...'","category":3,"code":6500}]}]],[2307,[{"start":1322,"length":3,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":1327,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1491,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1616,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1943,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1987,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":2025,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":4549,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":4593,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048}]],[2599,[{"start":272,"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":354,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":907,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":1076,"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":1114,"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":1199,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1276,"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":1338,"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":1481,"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":1665,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1712,"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":1757,"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":1836,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1918,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1976,"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":2023,"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":2370,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2447,"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":2755,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2802,"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":2838,"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":2914,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2969,"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":3051,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3148,"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":3523,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3642,"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":3690,"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":4031,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4159,"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":4484,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4533,"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":4878,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4940,"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":4977,"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":5400,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5476,"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":6141,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6218,"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":6485,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6532,"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":6573,"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":6639,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6703,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6766,"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":6823,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6888,"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":7012,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7166,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7255,"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":7313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7379,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7452,"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":7497,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7552,"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":7599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7663,"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":7736,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7807,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7880,"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":7925,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8020,"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":8403,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8481,"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":8933,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9013,"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":9490,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9631,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9757,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9835,"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":9876,"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":10661,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10731,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10785,"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":11070,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11113,"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":11970,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12047,"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":12318,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2600,[{"start":3595,"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":785,"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":4010,"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":785,"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":4616,"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":785,"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":5031,"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":785,"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'."}}]],[2746,[{"start":3077,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":3083,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":3175,"length":4,"messageText":"'opts' is possibly 'undefined'.","category":1,"code":18048}]],[2979,[{"start":6376,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.","category":1,"code":2345,"next":[{"messageText":"Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, autocorrect, and 129 more.","category":1,"code":2740}]}},{"start":6442,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.","category":1,"code":2345,"next":[{"messageText":"Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, autocorrect, and 129 more.","category":1,"code":2740}]}}]],[3039,[{"start":2067,"length":42,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":2572,"length":41,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":3058,"length":34,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":3554,"length":8,"code":2322,"category":1,"messageText":"Type 'undefined' is not assignable to type 'string'.","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/useauthorized.ts","start":1740,"length":50,"messageText":"The expected type comes from property 'userRole' which is declared here on type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'","category":3,"code":6500}]},{"start":4033,"length":42,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":5026,"length":34,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3047,[{"start":3309,"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}]}}]],[3075,[{"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":1981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2069,"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":2260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2354,"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":2467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2569,"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":2908,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2988,"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":3401,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3480,"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":3592,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3750,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3076,[{"start":5828,"length":11,"code":2322,"category":1,"messageText":"Type 'null' is not assignable to type 'string | undefined'."}]],[3173,[{"start":2696,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":2826,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":3842,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."}]],[3182,[{"start":10763,"length":423,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type '{ status: \"healthy\"; last_health_check: string; health_check_error: null; teams: { team_id: string; }[]; allowed_tools: string[]; has_user_credential: true; approval_status: \"approved\"; submitted_by: string; ... 47 more ...; env_vars?: MCPEnvVar[] | null; }' to type 'MCPServer' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Types of property 'approval_status' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '\"approved\"' is not comparable to type '\"active\" | \"rejected\" | \"pending_review\" | null | undefined'.","category":1,"code":2678}]}]}}]],[3292,[{"start":4242,"length":15,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: boolean; isAuthorized: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: boolean; isAuthorized: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3296,[{"start":4842,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":11747,"length":24,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }[]' is not assignable to parameter of type '({ model_name: string; litellm_params: { model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; auto_router_config?: undefined; auto_router_default_model?: undefined; }; model_info: { ...; }; } | { ...; } | { ...; } | { ...; } | { ...; })[]'.","category":1,"code":2345,"next":[{"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; auto_router_config?: undefined; auto_router_default_model?: undefined; }; model_info: { ...; }; } | { ...; } | { ...; } | { ...; } | { ...; }'.","category":1,"code":2322,"next":[{"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }; model_info: { ...; }; }'.","category":1,"code":2322,"next":[{"messageText":"Types of property 'litellm_params' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }' is missing the following properties from type '{ model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }': auto_router_config, auto_router_default_model","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }; model_info: { ...; }; }'."}}]}]}]}]}}]],[3569,[{"start":2185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2365,"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":2415,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":2652,"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":3085,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3188,"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":3521,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3674,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3768,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3861,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3998,"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":4041,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":4130,"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":4412,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3603,[{"start":2516,"length":2,"code":2345,"category":1,"messageText":"Argument of type '{}' is not assignable to parameter of type 'void'."}]],[3648,[{"start":11320,"length":300,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":20146,"length":308,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":30967,"length":331,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":31850,"length":331,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3701,[{"start":3053,"length":46,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type '[url: string][]' to type '[string, RequestInit][]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Type '[url: string]' is not comparable to type '[string, RequestInit]'.","category":1,"code":2678,"next":[{"messageText":"Source has 1 element(s) but target requires 2.","category":1,"code":2618}]}]}}]],[3727,[{"start":9097,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304}]],[3745,[{"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}]],[3750,[{"start":792,"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":835,"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":1122,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1226,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1306,"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":1527,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"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":1913,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2009,"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":2261,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2311,"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":2510,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2560,"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":2731,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3760,[{"start":780,"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":813,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":891,"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":1067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1117,"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":1321,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1371,"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":1570,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1620,"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":1789,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1848,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2053,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2107,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2176,"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":2342,"length":8,"messageText":"Parameter 'severity' implicitly has an 'any' type.","category":1,"code":7006},{"start":2352,"length":9,"messageText":"Parameter 'iconClass' implicitly has an 'any' type.","category":1,"code":7006},{"start":2505,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2584,"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":2857,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3006,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3063,"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":3512,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3571,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3661,"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":4075,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3798,[{"start":2005,"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":2084,"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":[]}]],[3806,[{"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}]],[3807,[{"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}]],[3808,[{"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}]],[3809,[{"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},{"start":2584,"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":2872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2940,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3810,[{"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}]],[3815,[{"start":236,"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":276,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":330,"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":583,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":735,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":802,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":859,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":931,"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":1173,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1267,"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":1590,"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":2168,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2268,"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":2981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3839,[{"start":1201,"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":1242,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1294,"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":1434,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1517,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1627,"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":1810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1875,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1963,"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":2273,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2391,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2426,"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":2681,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2876,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2924,"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":3198,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3285,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3314,"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":3448,"length":8,"messageText":"Parameter 'severity' implicitly has an 'any' type.","category":1,"code":7006},{"start":3458,"length":5,"messageText":"Parameter 'label' implicitly has an 'any' type.","category":1,"code":7006},{"start":3609,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3847,[{"start":5180,"length":36,"messageText":"Object is possibly 'null'.","category":1,"code":2531}]],[3849,[{"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":1485,"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":1663,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1726,"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":1772,"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":1825,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1880,"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":1947,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1998,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3854,[{"start":1780,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":15138,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3855,[{"start":3323,"length":15,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."},{"start":3344,"length":7,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."}]],[3856,[{"start":3533,"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":785,"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":5267,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":5784,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":6718,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":7666,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":8613,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":9408,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10172,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10857,"length":56,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":12152,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3857,[{"start":1457,"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":1501,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1553,"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":1638,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1723,"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":2186,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2268,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2372,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2435,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2539,"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":3054,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3159,"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":3589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3689,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3858,[{"start":837,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1657,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1766,"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":1811,"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":2092,"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":2275,"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":2634,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2723,"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":3113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3206,"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":3312,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3386,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3463,"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":3836,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3910,"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":4103,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4162,"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":4476,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4653,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4712,"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":4861,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3859,[{"start":1381,"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":1426,"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":1526,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1614,"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":1736,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1801,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1866,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1932,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2005,"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":2133,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2193,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2273,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2362,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2439,"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":2651,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2734,"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":2953,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3019,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3090,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3161,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3232,"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":3438,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3523,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3604,"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":3946,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4061,"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":4799,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4862,"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":5432,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5502,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5568,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5633,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5706,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5769,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5866,"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":6429,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6614,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6700,"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":7224,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7307,"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":7925,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8037,"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":8581,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8658,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8754,"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":9214,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9314,"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":9598,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9686,"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":10263,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10308,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10403,"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":10686,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10765,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10862,"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":11597,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11714,"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":11924,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12008,"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":12354,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12411,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12475,"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":13196,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13276,"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":14029,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14131,"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":14357,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14433,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14528,"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":14937,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15019,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15143,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15231,"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":15704,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15831,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15869,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15948,"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":16636,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16760,"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":17286,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17347,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17494,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17562,"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":17847,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17926,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18001,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18085,"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":18365,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18434,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18512,"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":19051,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19118,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19147,"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":19592,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19677,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19759,"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":19893,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19979,"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":20448,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20537,"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":21001,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21096,"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":21415,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21499,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21573,"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":21944,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22017,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22092,"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":22244,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22340,"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":22583,"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":22865,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22961,"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":23334,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23372,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23449,"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":24046,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24171,"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":24474,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24562,"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":25250,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25330,"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":25818,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25912,"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":26391,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26429,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26502,"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":27053,"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":27458,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27533,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27630,"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":28208,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28253,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28302,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28384,"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":28621,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28682,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28783,"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":29078,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29123,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29172,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29251,"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":29494,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29580,"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":29883,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29928,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29985,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30075,"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":30326,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30419,"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":30780,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30896,"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":31033,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31143,"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":31378,"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":31556,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31683,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31833,"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":32005,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32092,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32187,"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":32472,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32556,"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":32875,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32913,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32986,"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":33150,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33249,"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":33390,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33482,"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":33723,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33782,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33845,"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":34207,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34324,"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":34384,"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":34589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34704,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34813,"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":35194,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35291,"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":35558,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35684,"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":35839,"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":36010,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36120,"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":36445,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36562,"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":36896,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36934,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37005,"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":37453,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37491,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37556,"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":37828,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37899,"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":38459,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38580,"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":39063,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39187,"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":39389,"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":39681,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39768,"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":40176,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40294,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40355,"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":40833,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40920,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41014,"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":41296,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41413,"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":41485,"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":41748,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41802,"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":42193,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42244,"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":42687,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42848,"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":43404,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43505,"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":43574,"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":43728,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44012,"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":44318,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44472,"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":44544,"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":44929,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45003,"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":45436,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45738,"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":46146,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46461,"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":46740,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46887,"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":47293,"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":47893,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":47901,"length":5,"messageText":"Parameter 'value' implicitly has an 'any' type.","category":1,"code":7006},{"start":47953,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48037,"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":48323,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3860,[{"start":10021,"length":28,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to parameter of type 'UseQueryResult'.","category":1,"code":2345,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverRefetchErrorResult | QueryObserverSuccessResult | QueryObserverPlaceholderResult<...>'.","category":1,"code":2322,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is missing the following properties from type 'QueryObserverPlaceholderResult': error, isLoading, isLoadingError, isRefetchError, and 18 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverPlaceholderResult'."}}]}]}},{"start":11180,"length":28,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to parameter of type 'UseQueryResult'.","category":1,"code":2345,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverRefetchErrorResult | QueryObserverSuccessResult | QueryObserverPlaceholderResult<...>'.","category":1,"code":2322,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is missing the following properties from type 'QueryObserverPlaceholderResult': error, isLoading, isLoadingError, isRefetchError, and 18 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverPlaceholderResult'."}}]}]}}]],[3862,[{"start":670,"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":995,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1089,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1162,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1222,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1294,"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":1454,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1549,"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":1753,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1842,"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":2056,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3864,[{"start":2791,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5175,"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":5214,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":5831,"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":5951,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6355,"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":6471,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6561,"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":6872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6961,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7052,"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":7185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7265,"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":7479,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7548,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7893,"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":8489,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8548,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8955,"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":9452,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9618,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9711,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9778,"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":10272,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10460,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10544,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10641,"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":11328,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11420,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11510,"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":12247,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12306,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12509,"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":13010,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13103,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13170,"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":13594,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13807,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13866,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14218,"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":14915,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14974,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15139,"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":15751,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15966,"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":16392,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16629,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16688,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16847,"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":17478,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17537,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17821,"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":18064,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18185,"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":18222,"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":18593,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18681,"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":19959,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20106,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20147,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20208,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20266,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20413,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20530,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20601,"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":21407,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21666,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21758,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21864,"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":21905,"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":22367,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22427,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22775,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22872,"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":23093,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23284,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23344,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23444,"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":23848,"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":24186,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24312,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24398,"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":24703,"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":25025,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25122,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25433,"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":25650,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25748,"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":26067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26242,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26599,"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":27132,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27193,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27512,"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":28056,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28117,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28891,"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":29352,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30027,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30195,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30240,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30296,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30371,"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":31051,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":31202,"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":31595,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31918,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32108,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32179,"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":32514,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32856,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33024,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33069,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33191,"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":33234,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":33335,"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":33793,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33854,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34019,"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":34699,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34760,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34937,"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":35734,"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":36114,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36204,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36307,"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":36717,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36856,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37138,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37199,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37653,"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":38067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38263,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38388,"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":38592,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":38600,"length":9,"messageText":"Parameter 'modelName' implicitly has an 'any' type.","category":1,"code":7006},{"start":38950,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39037,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39124,"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":39677,"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":40041,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40131,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40234,"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":40597,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40927,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40988,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41452,"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":41922,"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":41981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42104,"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":42203,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42368,"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":42520,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3867,[{"start":793,"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":840,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":892,"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":1224,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1269,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1342,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1419,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1501,"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":1794,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1869,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1948,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2022,"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":2531,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2612,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2703,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2889,"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":3304,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3889,[{"start":3670,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ metadata: { key: string; value?: string | undefined; }[]; }' is not assignable to parameter of type '{ metadata?: MetadataPair[] | undefined; }'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'metadata' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ key: string; value?: string | undefined; }[]' is not assignable to type 'MetadataPair[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key: string; value?: string | undefined; }' is not assignable to type 'MetadataPair'.","category":1,"code":2322,"next":[{"messageText":"Types of property 'value' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string | undefined' is not assignable to type 'string'.","category":1,"code":2322,"next":[{"messageText":"Type 'undefined' is not assignable to type 'string'.","category":1,"code":2322}],"canonicalHead":{"code":2322,"messageText":"Type '{ key: string; value?: string | undefined; }' is not assignable to type 'MetadataPair'."}}]}]}]}]}]}}]],[3895,[{"start":806,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1045,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1459,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1865,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":2328,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]}]],[3898,[{"start":221,"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":265,"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":376,"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":589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":667,"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":802,"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":1023,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1104,"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":1445,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3999,[{"start":2930,"length":304,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; userId: string; userRole: string; accessToken: string; token: string; userEmail: string; premiumUser: boolean; disabledPersonalKeyCreation: null; showSSOBanner: false; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; userId: string; userRole: string; accessToken: string; token: string; userEmail: string; premiumUser: boolean; disabledPersonalKeyCreation: null; showSSOBanner: false; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4006,[{"start":5233,"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, key_type, 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'."}}]],[4007,[{"start":5009,"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; isLoading: boolean; isAuthorized: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; 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; isLoading: boolean; isAuthorized: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10433,"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; isLoading: boolean; isAuthorized: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; 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; isLoading: boolean; isAuthorized: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4008,[{"start":3100,"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; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is missing the following properties from type 'KeyResponse': key_type, 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; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}},{"start":5501,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":6874,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":7548,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":7993,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":8654,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":9411,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10043,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":11330,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":12106,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":12901,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":13663,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":15005,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":16135,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":18669,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":19912,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":20358,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":20814,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":21298,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":22406,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":22827,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":23458,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":24088,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":24671,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":25867,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":26622,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":27508,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":28369,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":29570,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":32974,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":40532,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4060,[{"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","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":[4062,2618,2620,2622,2624,2626,2628,2611,2794,2785,1268,1267,1266,2791,2784,2782,2793,2783,2792,2788,2787,2786,1189,2789,2819,2817,2818,2746,2835,2836,2825,2837,2823,1269,2838,2827,1271,1270,2822,2839,2840,2828,1273,2841,2826,2820,2833,2831,2834,2830,2829,2821,2824,2832,2842,2778,2843,2848,2845,2844,2847,2856,2849,2857,2853,1275,1274,2855,2851,2850,1276,2858,2852,2854,2873,2870,2874,2860,2863,2862,1277,1279,1278,2876,2877,2864,2875,2861,1280,2866,2865,2878,2867,1282,1281,2879,2880,2868,1074,2872,2869,2859,2871,2653,1284,1283,2979,2976,2980,2971,1287,1286,2981,2982,2977,1289,1288,2983,2972,2984,2883,2985,2974,2986,2975,2987,2882,1297,1296,2988,2989,1295,1299,1298,2978,2991,1310,2992,2993,1308,2994,2995,1330,2996,1325,1331,2999,1319,3000,1317,3001,1316,1355,1315,1311,1356,1318,2997,1307,1332,1326,2998,1309,1302,1352,1329,1353,1327,1354,1328,2990,3073,3063,3075,3074,3076,3066,3077,3069,3078,3068,3079,3067,3072,3071,3040,3041,3018,1361,3021,3051,3009,3007,3052,3010,3053,3022,3054,3023,3055,3056,3003,3057,3004,3006,3058,3002,3005,3059,1649,3060,3008,3061,1362,1363,3042,3030,3043,3028,1357,1360,1359,3044,3029,3045,3046,3024,3047,1358,3012,3013,3048,3020,3011,3037,3032,3019,3034,3026,3035,3027,3036,3025,3014,3049,3015,3050,3016,3038,3039,3031,3062,3017,3033,1385,1386,1384,1387,1388,1390,1389,1098,1391,1393,1392,1417,1419,1418,1421,1420,1423,1422,1425,1424,1428,1427,1429,1092,3081,1416,1430,1432,1431,1433,1434,1436,1435,1438,1437,1440,1439,1441,1442,1444,1443,1446,1447,1445,1448,1450,1449,1451,1452,1453,1454,1455,1457,1456,1459,1458,1461,1460,1462,1464,1463,1465,1162,1467,1466,1468,1367,1471,1470,1473,1472,1475,1474,1476,1469,1478,1477,1480,1479,1482,1481,1380,1484,1483,1485,1487,1489,1488,1491,1490,1493,1492,1495,1494,1497,1496,1498,1500,1499,1502,1501,1504,1503,1505,1093,1508,1507,1509,1506,1511,1510,1364,1365,1094,1369,1371,1372,1374,1373,1376,1375,1377,1378,1368,1381,1513,1512,1515,1514,1517,1516,3080,1383,2750,2747,2745,3094,3114,3119,3159,3160,3139,1519,1518,1522,1521,3124,1524,1525,1523,3161,3136,3127,3157,3177,3140,3178,3129,3179,3148,3180,3128,3181,3143,3182,3183,3142,3184,3144,3185,3151,3186,3130,3187,3156,1527,1526,3176,1528,3164,3162,3135,3163,3147,3165,3132,3166,3141,3167,3115,3116,3169,3118,3168,3117,1530,1529,3170,3122,3120,3134,3171,3133,3172,3125,3131,1608,3121,3126,3152,1610,1609,3173,3153,3174,3123,3175,3150,3188,1520,3158,3196,3189,3197,3190,3198,3192,3191,3199,3193,3195,3194,3218,3292,3245,3293,3244,1622,1621,3296,3252,3251,3250,1624,1623,3294,3283,3243,3295,3288,1617,1616,3291,3290,3297,3262,3246,3253,3298,3282,3267,3286,3284,3278,3289,1618,1626,1625,1188,3303,3301,3302,3317,3315,3318,3314,3313,3308,3307,3316,2780,2779,3424,3446,3416,3447,3438,3448,3425,3449,3417,1628,3426,3418,3450,3419,3451,3433,3452,3437,3453,3427,3420,3454,3421,3455,3422,3456,3423,3457,3436,3431,3434,3430,3432,3435,1630,1629,3458,3443,3459,3441,3460,3439,3461,3442,3463,3462,3464,3440,1633,1632,3320,1638,1637,1640,3340,3465,3408,3466,3409,3467,3410,3468,3411,1631,3412,3413,3415,3445,3444,3489,3479,3490,3473,3491,3484,3487,3476,3475,1643,1642,3492,3482,3493,3474,3494,3477,3495,3485,3496,3471,3497,3472,3498,3481,3499,3480,3488,3470,3469,1645,1644,3500,3483,3478,3486,3511,3506,3512,3505,3513,3504,3503,3516,3517,3501,3518,3519,3502,3520,1924,1646,1926,1925,3514,3509,3515,3508,3507,3510,3549,3526,3550,3546,3545,3562,3535,3567,3540,3563,3536,3564,3539,3565,3537,1930,1931,3566,3534,3538,3554,3532,3542,3544,3555,3529,3556,3524,3557,3528,3558,3533,3559,3541,3560,3530,1927,1929,1928,3561,3543,3551,3525,3521,3548,3523,3522,3552,3527,3553,3531,3547,3569,2970,3568,3580,3581,3572,3578,3582,3570,1933,1932,3586,3587,3577,3583,3574,3573,3584,3575,3585,3576,3571,3579,3595,3588,3593,3591,3594,3590,3589,3592,3605,3599,3603,3600,3604,3596,3602,3598,3597,3601,3613,3620,3623,3622,3621,3626,3625,3624,3650,3634,3651,3635,3652,3636,3649,3637,3653,3641,1936,1938,1937,3654,3642,3655,3640,1935,1934,3639,3647,3643,3648,3645,3656,3644,1939,1294,3646,3667,3658,3670,3660,1942,1941,1943,1940,3665,3668,3657,3669,3664,3672,3673,3663,3671,3662,3661,3666,3687,3688,3683,3689,3681,3680,3697,3685,1182,3690,1181,1180,3691,3682,3692,3684,3698,3699,3679,3693,3694,3677,3695,3676,3675,3696,3678,3686,3703,3702,3701,3700,3711,3713,3716,3705,3704,3718,3709,3708,3720,3722,3721,3724,3723,2638,3726,3727,3725,3728,3729,3730,3731,3733,3732,3737,3736,3738,3739,3735,3740,3734,3741,3761,3628,2034,1085,3864,3249,3260,3856,3261,3866,3254,3867,3220,1619,3857,3248,1994,1993,1996,1995,1997,1110,3868,3224,1102,3858,1097,1998,1096,1999,1078,2000,1108,3859,1106,3869,3255,1104,3247,3870,3257,2001,1100,3860,1101,1109,3871,3256,3872,3258,2035,3873,3259,3861,2036,3862,1105,2002,1107,3863,1103,3762,3273,3874,1650,1272,3785,3200,3791,3201,3792,3203,3793,3205,3786,3202,3787,3217,3788,3206,3212,3789,3210,3790,3209,3084,3875,3083,3742,1950,3763,3659,1888,3706,2011,3876,2010,3877,3715,2009,3710,3878,3717,3879,3714,3880,3707,3712,2003,3719,2012,2004,3881,3215,3428,1627,3882,3429,3883,1635,1636,2014,2013,3213,3211,1034,3764,3629,3794,3090,3795,3796,3087,3797,3085,3086,3798,3089,1962,1961,3799,3800,3088,1426,1324,1651,2759,1652,1072,3884,2752,3885,2760,2748,3905,3304,3906,3305,3907,3306,2015,3908,3207,3909,3208,3886,1653,3887,2753,3888,2652,3238,3889,3231,3890,1886,3891,1885,3892,1071,3894,3893,3895,1904,3896,3272,1887,3271,3897,1891,1905,3898,1892,3899,1902,2017,2016,3900,2761,3902,1303,1903,3903,3638,3904,3228,3901,3630,2795,3743,1911,3744,2649,3745,2654,3801,3097,3802,3096,3095,3803,3100,3804,3099,3098,3746,2846,2038,2039,2037,3910,2040,2041,1033,3765,3082,3805,1971,3806,1966,3807,1967,3808,1968,1973,1965,3809,1972,1974,1970,3911,2769,2042,3747,3232,3064,3810,3065,1975,3748,1320,3766,2233,1944,1170,3912,1912,1913,3915,1081,2045,2044,1032,3913,2043,1031,2047,2046,3914,1914,2049,2048,2051,2050,3767,3268,3768,1958,3749,2656,3916,3319,1639,3917,1082,2053,2052,3918,3414,3769,2762,2054,3919,1915,3920,1918,3921,3149,1075,1917,3922,3338,3923,1073,2056,2055,3924,3263,3925,3266,3926,3265,3264,3927,3221,3928,3281,3929,3280,3279,3930,3242,2057,3204,3285,3770,3227,3225,3811,2781,1977,1976,3931,3219,3932,1306,3933,2962,3771,2651,3813,2641,3814,2643,1978,1951,1979,3815,2644,3816,2650,3812,2646,3817,2648,1945,1187,3750,2655,625,2766,3772,1910,3936,3937,1923,2058,1921,3934,3935,2767,2060,2059,2061,1922,2064,2063,3939,3310,2066,2065,3940,3309,2062,3938,3312,1946,1960,1959,3773,3274,3818,3276,3275,3819,3277,3774,3632,2765,3941,2764,2763,3942,2770,1641,3775,3287,3776,1305,3777,3216,3214,3778,3269,3779,3270,3948,2884,3943,1893,3944,1894,3945,1897,3946,1895,3947,1896,3951,2969,3949,2968,2068,2067,3950,2967,2966,2965,2069,1486,3751,2796,3952,3233,3780,3093,1980,3820,2811,3821,2813,3822,2812,3823,2797,3824,3146,3825,3145,1982,1981,3826,2814,1983,1984,3832,2800,3833,2799,3834,2801,3835,3836,2802,3827,2803,3828,2804,3829,2807,3830,2805,3831,2806,1986,1985,3837,2808,3838,2809,3839,2810,3840,3092,3091,1987,3841,1901,3842,1898,3843,2963,1899,3845,2964,3844,1900,3070,3968,2755,3953,1908,3954,3311,3969,3631,3977,2217,3978,2218,3979,2219,3980,2216,2070,3981,2220,2222,3982,2221,3955,1952,3956,1919,1149,3970,3971,1153,3972,1155,3973,1152,3974,1157,3975,1160,3976,1159,1158,1161,1148,1964,3957,1168,2768,3983,1906,3229,3223,3958,1173,3959,1174,3960,1076,1889,3961,2749,3962,2973,3963,1648,3964,2881,2798,2757,3965,1026,3966,1949,2756,3984,1163,1164,3985,1165,3986,1167,3987,1169,1177,3988,1171,3989,1172,3990,1175,3991,1176,3967,2640,1647,3846,1955,3753,1957,3752,2815,3992,3339,623,3993,3608,3607,3606,2772,3994,3995,3234,3230,3996,1890,4001,3236,2224,2223,3997,3237,4002,3235,2226,2225,3998,3241,3999,3239,2228,2227,4000,3240,2229,3755,3612,1989,1988,3847,3611,3610,3754,3609,2231,2230,4006,2773,4007,4008,2774,2232,2235,2234,2771,4003,2754,4004,4005,2758,3848,2647,3756,3615,3849,3614,3850,3618,1990,3851,3617,3852,3616,3757,3619,4009,1300,1907,4010,1953,4011,1099,4012,2639,2642,4013,1020,1077,4014,2215,1156,1080,1025,1095,1314,4015,1029,2751,1023,1021,1027,1954,1083,4016,1948,4017,1030,1028,1154,1150,1084,2637,1079,1151,1301,1022,4018,1035,4019,1313,3758,2816,3759,3781,3222,3854,3300,3853,3627,1285,1992,1991,3782,3633,3783,2777,3760,2744,1111,4020,1920,3784,3674,4032,3103,3104,4021,3102,3101,2242,4022,2253,2236,4023,2252,2251,2240,4033,2239,2249,2248,4034,2250,4035,2247,4028,4029,3113,4030,3105,2237,4036,2243,4037,2272,2241,2245,4038,2275,2282,4039,2276,4040,2259,4041,2280,4042,2281,4043,2277,2269,2270,4044,2279,4045,2278,4046,1183,4047,2271,4048,2274,4049,2273,4050,2256,4051,2255,2246,2283,2260,4031,3106,3107,4024,3108,4025,3112,3111,4026,3110,2263,2268,2264,2265,2266,4052,2267,2261,2284,2262,4027,3109,2238,2254,3226,3299,2775,3855,2776,2635,2005,4053,2645,2636,1947,2290,2288,2287,2289,2286,2285,2291,4057,2294,1312,4054,3137,4055,1323,3138,4056,3154,3155,2292,2293,2295,1304,2298,2297,2299,1019,2302,2301,2304,2303,4058,2033,2305,2306,1293,2307,616,2308,1184,2309,1185,1186,2300,617,614,2310,2311,1366,2312,620,2313,1166,1415,1179,2314,2316,2315,2317,622,2577,2576,2579,2578,2580,1956,2581,1370,2582,2584,2583,2585,619,2586,1916,2587,1178,2588,1620,2589,2590,1634,2591,1379,2594,2593,2597,2596,2598,2595,2599,1086,2600,1087,618,2601,1382,2602,1963,2603,1070,4059,2619,2621,2623,2625,2627,2629,2608,2610,2612,2630,3865,2613,2617,2790,4060,613],"version":"5.9.3"} \ No newline at end of file +{"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/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/@img/colour/index.d.ts","./node_modules/sharp/dist/index.d.mts","./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/@types/react/compiler-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/static.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","./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/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.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.bkdhh7zx.d.ts","./node_modules/vitest/dist/chunks/worker.d.cugipz9v.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.buron0i0.d.ts","./node_modules/vitest/dist/chunks/vite.d.bnoppc46.d.ts","./node_modules/vitest/dist/config.d.ts","./node_modules/vitest/config.d.ts","./vitest.config.ts","./src/types.ts","./node_modules/vitest/dist/chunks/worker.d.uzwscv9x.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/sonner/dist/index.d.mts","./src/lib/http/client.ts","./src/lib/toast.ts","./src/utils/securestorage.ts","./src/utils/mcptokenstore.ts","./src/utils/cookieutils.ts","./node_modules/jwt-decode/build/esm/index.d.ts","./src/utils/jwtutils.ts","./src/components/tag_management/types.tsx","./src/components/email_events/types.ts","./src/lib/http/schema.d.ts","./src/components/claude_code_plugins/types.ts","./node_modules/@base-ui/react/internals/reason-parts.d.mts","./node_modules/@base-ui/react/internals/reasons.d.mts","./node_modules/@base-ui/react/internals/createbaseuieventdetails.d.mts","./node_modules/@base-ui/react/types/index.d.mts","./node_modules/@base-ui/react/internals/types.d.mts","./node_modules/@base-ui/react/accordion/root/accordionroot.d.mts","./node_modules/@base-ui/react/internals/usetransitionstatus.d.mts","./node_modules/@base-ui/react/collapsible/root/collapsibleroot.d.mts","./node_modules/@base-ui/react/collapsible/root/usecollapsibleroot.d.mts","./node_modules/@base-ui/react/accordion/item/accordionitem.d.mts","./node_modules/@base-ui/react/accordion/header/accordionheader.d.mts","./node_modules/@base-ui/react/accordion/trigger/accordiontrigger.d.mts","./node_modules/@base-ui/react/accordion/panel/accordionpanel.d.mts","./node_modules/@base-ui/react/accordion/index.parts.d.mts","./node_modules/@base-ui/react/accordion/index.d.mts","./node_modules/reselect/dist/reselect.d.ts","./node_modules/@base-ui/utils/store/createselector.d.mts","./node_modules/@base-ui/utils/store/createselectormemoized.d.mts","./node_modules/@base-ui/utils/fasthooks.d.mts","./node_modules/@base-ui/utils/store/store.d.mts","./node_modules/@base-ui/utils/store/usestore.d.mts","./node_modules/@base-ui/utils/store/reactstore.d.mts","./node_modules/@base-ui/utils/store/storeinspector.d.mts","./node_modules/@base-ui/utils/store/index.d.mts","./node_modules/@base-ui/utils/useenhancedclickhandler.d.mts","./node_modules/@floating-ui/utils/dist/floating-ui.utils.d.mts","./node_modules/@floating-ui/core/dist/floating-ui.core.d.mts","./node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.d.mts","./node_modules/@floating-ui/dom/dist/floating-ui.dom.d.mts","./node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.d.mts","./node_modules/@base-ui/react/utils/popups/inlinerect.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingtreestore.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingrootstore.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingfocusmanager.d.mts","./node_modules/@base-ui/react/internals/getstateattributesprops.d.mts","./node_modules/@base-ui/react/internals/userenderelement.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingportal.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/useclientpoint.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usedismiss.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefocus.d.mts","./node_modules/@base-ui/react/internals/shadowdom.d.mts","./node_modules/@base-ui/react/floating-ui-react/utils/element.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehovershared.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehover.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehoverfloatinginteraction.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usehoverreferenceinteraction.d.mts","./node_modules/@base-ui/react/floating-ui-react/utils/composite.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/gridnavigation.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/uselistnavigation.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usetypeahead.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefloatingrootcontext.d.mts","./node_modules/@base-ui/react/floating-ui-react/safepolygon.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingtree.d.mts","./node_modules/@base-ui/react/floating-ui-react/types.d.mts","./node_modules/@base-ui/react/floating-ui-react/components/floatingdelaygroup.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/useclick.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usefloating.d.mts","./node_modules/@base-ui/react/floating-ui-react/hooks/usesyncedfloatingrootcontext.d.mts","./node_modules/@base-ui/react/floating-ui-react/index.d.mts","./node_modules/@base-ui/react/utils/popups/popuptriggermap.d.mts","./node_modules/@base-ui/react/utils/popups/store.d.mts","./node_modules/@base-ui/react/utils/popups/popupstoreutils.d.mts","./node_modules/@base-ui/react/utils/popups/index.d.mts","./node_modules/@base-ui/react/dialog/store/dialogstore.d.mts","./node_modules/@base-ui/react/dialog/store/dialoghandle.d.mts","./node_modules/@base-ui/react/dialog/root/dialogroot.d.mts","./node_modules/@base-ui/react/alert-dialog/handle.d.mts","./node_modules/@base-ui/react/alert-dialog/root/alertdialogroot.d.mts","./node_modules/@base-ui/react/dialog/backdrop/dialogbackdrop.d.mts","./node_modules/@base-ui/react/dialog/close/dialogclose.d.mts","./node_modules/@base-ui/react/dialog/description/dialogdescription.d.mts","./node_modules/@base-ui/react/dialog/popup/dialogpopup.d.mts","./node_modules/@base-ui/react/dialog/portal/dialogportal.d.mts","./node_modules/@base-ui/react/dialog/title/dialogtitle.d.mts","./node_modules/@base-ui/react/dialog/trigger/dialogtrigger.d.mts","./node_modules/@base-ui/react/alert-dialog/trigger/alertdialogtrigger.d.mts","./node_modules/@base-ui/react/dialog/viewport/dialogviewport.d.mts","./node_modules/@base-ui/react/alert-dialog/index.parts.d.mts","./node_modules/@base-ui/react/alert-dialog/index.d.mts","./node_modules/@base-ui/react/internals/resolvevaluelabel.d.mts","./node_modules/@base-ui/react/combobox/root/ariacombobox.d.mts","./node_modules/@base-ui/react/autocomplete/root/autocompleteroot.d.mts","./node_modules/@base-ui/react/autocomplete/value/autocompletevalue.d.mts","./node_modules/@base-ui/react/internals/form-context/formcontext.d.mts","./node_modules/@base-ui/react/form/form.d.mts","./node_modules/@base-ui/react/form/index.d.mts","./node_modules/@base-ui/react/field/root/fieldroot.d.mts","./node_modules/@base-ui/react/utils/useanchorpositioning.d.mts","./node_modules/@base-ui/react/autocomplete/trigger/autocompletetrigger.d.mts","./node_modules/@base-ui/react/combobox/input/comboboxinput.d.mts","./node_modules/@base-ui/react/autocomplete/input-group/autocompleteinputgroup.d.mts","./node_modules/@base-ui/react/combobox/icon/comboboxicon.d.mts","./node_modules/@base-ui/react/combobox/clear/comboboxclear.d.mts","./node_modules/@base-ui/react/combobox/list/comboboxlist.d.mts","./node_modules/@base-ui/react/combobox/status/comboboxstatus.d.mts","./node_modules/@base-ui/react/combobox/portal/comboboxportal.d.mts","./node_modules/@base-ui/react/combobox/backdrop/comboboxbackdrop.d.mts","./node_modules/@base-ui/react/combobox/positioner/comboboxpositioner.d.mts","./node_modules/@base-ui/react/combobox/popup/comboboxpopup.d.mts","./node_modules/@base-ui/react/combobox/arrow/comboboxarrow.d.mts","./node_modules/@base-ui/react/combobox/group/comboboxgroup.d.mts","./node_modules/@base-ui/react/combobox/group-label/comboboxgrouplabel.d.mts","./node_modules/@base-ui/react/autocomplete/item/autocompleteitem.d.mts","./node_modules/@base-ui/react/combobox/row/comboboxrow.d.mts","./node_modules/@base-ui/react/combobox/collection/comboboxcollection.d.mts","./node_modules/@base-ui/react/combobox/empty/comboboxempty.d.mts","./node_modules/@base-ui/react/separator/separator.d.mts","./node_modules/@base-ui/react/internals/filter.d.mts","./node_modules/@base-ui/react/combobox/root/utils/usefilter.d.mts","./node_modules/@base-ui/react/combobox/root/utils/usefiltereditems.d.mts","./node_modules/@base-ui/react/autocomplete/index.parts.d.mts","./node_modules/@base-ui/react/autocomplete/index.d.mts","./node_modules/@base-ui/react/avatar/root/avatarroot.d.mts","./node_modules/@base-ui/react/avatar/image/avatarimage.d.mts","./node_modules/@base-ui/react/avatar/fallback/avatarfallback.d.mts","./node_modules/@base-ui/react/avatar/index.parts.d.mts","./node_modules/@base-ui/react/avatar/index.d.mts","./node_modules/@base-ui/react/button/button.d.mts","./node_modules/@base-ui/react/button/index.d.mts","./node_modules/@base-ui/react/checkbox/root/checkboxroot.d.mts","./node_modules/@base-ui/react/checkbox/indicator/checkboxindicator.d.mts","./node_modules/@base-ui/react/checkbox/index.parts.d.mts","./node_modules/@base-ui/react/checkbox/index.d.mts","./node_modules/@base-ui/react/checkbox-group/checkboxgroup.d.mts","./node_modules/@base-ui/react/checkbox-group/index.d.mts","./node_modules/@base-ui/react/collapsible/trigger/collapsibletrigger.d.mts","./node_modules/@base-ui/react/collapsible/panel/collapsiblepanel.d.mts","./node_modules/@base-ui/react/collapsible/index.parts.d.mts","./node_modules/@base-ui/react/collapsible/index.d.mts","./node_modules/@base-ui/react/combobox/root/comboboxroot.d.mts","./node_modules/@base-ui/react/combobox/label/comboboxlabel.d.mts","./node_modules/@base-ui/react/combobox/value/comboboxvalue.d.mts","./node_modules/@base-ui/react/combobox/input-group/comboboxinputgroup.d.mts","./node_modules/@base-ui/react/combobox/trigger/comboboxtrigger.d.mts","./node_modules/@base-ui/react/combobox/item/comboboxitem.d.mts","./node_modules/@base-ui/react/combobox/item-indicator/comboboxitemindicator.d.mts","./node_modules/@base-ui/react/combobox/chips/comboboxchips.d.mts","./node_modules/@base-ui/react/combobox/chip/comboboxchip.d.mts","./node_modules/@base-ui/react/combobox/chip-remove/comboboxchipremove.d.mts","./node_modules/@base-ui/react/separator/index.d.mts","./node_modules/@base-ui/react/combobox/index.parts.d.mts","./node_modules/@base-ui/react/combobox/index.d.mts","./node_modules/@base-ui/react/menu/arrow/menuarrow.d.mts","./node_modules/@base-ui/react/menu/backdrop/menubackdrop.d.mts","./node_modules/@base-ui/react/menu/store/menustore.d.mts","./node_modules/@base-ui/react/menu/root/menurootcontext.d.mts","./node_modules/@base-ui/react/menubar/menubarcontext.d.mts","./node_modules/@base-ui/react/context-menu/root/contextmenurootcontext.d.mts","./node_modules/@base-ui/react/menu/store/menuhandle.d.mts","./node_modules/@base-ui/react/menu/root/menuroot.d.mts","./node_modules/@base-ui/react/menu/checkbox-item/menucheckboxitem.d.mts","./node_modules/@base-ui/react/menu/checkbox-item-indicator/menucheckboxitemindicator.d.mts","./node_modules/@base-ui/react/menu/group/menugroup.d.mts","./node_modules/@base-ui/react/menu/group-label/menugrouplabel.d.mts","./node_modules/@base-ui/react/menu/item/menuitem.d.mts","./node_modules/@base-ui/react/menu/link-item/menulinkitem.d.mts","./node_modules/@base-ui/react/menu/popup/menupopup.d.mts","./node_modules/@base-ui/react/menu/portal/menuportal.d.mts","./node_modules/@base-ui/react/menu/positioner/menupositioner.d.mts","./node_modules/@base-ui/react/menu/radio-group/menuradiogroup.d.mts","./node_modules/@base-ui/react/menu/radio-item/menuradioitem.d.mts","./node_modules/@base-ui/react/menu/radio-item-indicator/menuradioitemindicator.d.mts","./node_modules/@base-ui/react/menu/submenu-root/menusubmenurootcontext.d.mts","./node_modules/@base-ui/react/menu/submenu-root/menusubmenuroot.d.mts","./node_modules/@base-ui/react/menu/trigger/menutrigger.d.mts","./node_modules/@base-ui/react/menu/viewport/menuviewport.d.mts","./node_modules/@base-ui/react/menu/submenu-trigger/menusubmenutrigger.d.mts","./node_modules/@base-ui/react/menu/index.parts.d.mts","./node_modules/@base-ui/react/menu/index.d.mts","./node_modules/@base-ui/react/context-menu/root/contextmenuroot.d.mts","./node_modules/@base-ui/react/context-menu/trigger/contextmenutrigger.d.mts","./node_modules/@base-ui/react/context-menu/index.parts.d.mts","./node_modules/@base-ui/react/context-menu/index.d.mts","./node_modules/@base-ui/react/csp-provider/cspprovider.d.mts","./node_modules/@base-ui/react/csp-provider/index.parts.d.mts","./node_modules/@base-ui/react/csp-provider/index.d.mts","./node_modules/@base-ui/react/dialog/index.parts.d.mts","./node_modules/@base-ui/react/dialog/index.d.mts","./node_modules/@base-ui/react/internals/direction-context/directioncontext.d.mts","./node_modules/@base-ui/react/direction-provider/directionprovider.d.mts","./node_modules/@base-ui/react/direction-provider/index.parts.d.mts","./node_modules/@base-ui/react/direction-provider/index.d.mts","./node_modules/@base-ui/react/drawer/backdrop/drawerbackdrop.d.mts","./node_modules/@base-ui/react/drawer/close/drawerclose.d.mts","./node_modules/@base-ui/react/drawer/content/drawercontent.d.mts","./node_modules/@base-ui/react/drawer/description/drawerdescription.d.mts","./node_modules/@base-ui/react/drawer/indent/drawerindent.d.mts","./node_modules/@base-ui/react/drawer/indent-background/drawerindentbackground.d.mts","./node_modules/@base-ui/react/utils/useswipedismiss.d.mts","./node_modules/@base-ui/react/drawer/root/drawerroot.d.mts","./node_modules/@base-ui/react/drawer/root/drawerrootcontext.d.mts","./node_modules/@base-ui/react/drawer/popup/drawerpopup.d.mts","./node_modules/@base-ui/react/drawer/portal/drawerportal.d.mts","./node_modules/@base-ui/react/drawer/provider/drawerprovider.d.mts","./node_modules/@base-ui/react/drawer/swipe-area/drawerswipearea.d.mts","./node_modules/@base-ui/react/drawer/title/drawertitle.d.mts","./node_modules/@base-ui/react/drawer/trigger/drawertrigger.d.mts","./node_modules/@base-ui/react/drawer/viewport/drawerviewport.d.mts","./node_modules/@base-ui/react/drawer/virtual-keyboard-provider/drawervirtualkeyboardprovider.d.mts","./node_modules/@base-ui/react/drawer/index.parts.d.mts","./node_modules/@base-ui/react/drawer/index.d.mts","./node_modules/@base-ui/react/field/label/fieldlabel.d.mts","./node_modules/@base-ui/react/field/error/fielderror.d.mts","./node_modules/@base-ui/react/field/description/fielddescription.d.mts","./node_modules/@base-ui/react/field/control/fieldcontrol.d.mts","./node_modules/@base-ui/react/field/validity/fieldvalidity.d.mts","./node_modules/@base-ui/react/field/item/fielditem.d.mts","./node_modules/@base-ui/react/field/index.parts.d.mts","./node_modules/@base-ui/react/field/index.d.mts","./node_modules/@base-ui/react/fieldset/root/fieldsetroot.d.mts","./node_modules/@base-ui/react/fieldset/legend/fieldsetlegend.d.mts","./node_modules/@base-ui/react/fieldset/index.parts.d.mts","./node_modules/@base-ui/react/fieldset/index.d.mts","./node_modules/@base-ui/react/input/input.d.mts","./node_modules/@base-ui/react/input/index.d.mts","./node_modules/@base-ui/react/menubar/menubar.d.mts","./node_modules/@base-ui/react/menubar/index.d.mts","./node_modules/@base-ui/react/merge-props/mergeprops.d.mts","./node_modules/@base-ui/react/merge-props/index.d.mts","./node_modules/@base-ui/react/meter/root/meterroot.d.mts","./node_modules/@base-ui/react/meter/track/metertrack.d.mts","./node_modules/@base-ui/react/meter/indicator/meterindicator.d.mts","./node_modules/@base-ui/react/meter/value/metervalue.d.mts","./node_modules/@base-ui/react/meter/label/meterlabel.d.mts","./node_modules/@base-ui/react/meter/index.parts.d.mts","./node_modules/@base-ui/react/meter/index.d.mts","./node_modules/@base-ui/react/navigation-menu/root/navigationmenuroot.d.mts","./node_modules/@base-ui/react/navigation-menu/list/navigationmenulist.d.mts","./node_modules/@base-ui/react/navigation-menu/item/navigationmenuitem.d.mts","./node_modules/@base-ui/react/navigation-menu/content/navigationmenucontent.d.mts","./node_modules/@base-ui/react/navigation-menu/trigger/navigationmenutrigger.d.mts","./node_modules/@base-ui/react/navigation-menu/portal/navigationmenuportal.d.mts","./node_modules/@base-ui/react/navigation-menu/positioner/navigationmenupositioner.d.mts","./node_modules/@base-ui/react/navigation-menu/viewport/navigationmenuviewport.d.mts","./node_modules/@base-ui/react/navigation-menu/backdrop/navigationmenubackdrop.d.mts","./node_modules/@base-ui/react/navigation-menu/popup/navigationmenupopup.d.mts","./node_modules/@base-ui/react/navigation-menu/arrow/navigationmenuarrow.d.mts","./node_modules/@base-ui/react/navigation-menu/link/navigationmenulink.d.mts","./node_modules/@base-ui/react/navigation-menu/icon/navigationmenuicon.d.mts","./node_modules/@base-ui/react/navigation-menu/index.parts.d.mts","./node_modules/@base-ui/react/navigation-menu/index.d.mts","./node_modules/@base-ui/react/number-field/utils/types.d.mts","./node_modules/@base-ui/react/number-field/root/numberfieldroot.d.mts","./node_modules/@base-ui/react/number-field/group/numberfieldgroup.d.mts","./node_modules/@base-ui/react/number-field/increment/numberfieldincrement.d.mts","./node_modules/@base-ui/react/number-field/decrement/numberfielddecrement.d.mts","./node_modules/@base-ui/react/number-field/input/numberfieldinput.d.mts","./node_modules/@base-ui/react/number-field/scrub-area/numberfieldscrubarea.d.mts","./node_modules/@base-ui/react/number-field/scrub-area-cursor/numberfieldscrubareacursor.d.mts","./node_modules/@base-ui/react/number-field/index.parts.d.mts","./node_modules/@base-ui/react/number-field/index.d.mts","./node_modules/@base-ui/react/otp-field/utils/otp.d.mts","./node_modules/@base-ui/react/otp-field/root/otpfieldroot.d.mts","./node_modules/@base-ui/react/otp-field/input/otpfieldinput.d.mts","./node_modules/@base-ui/react/otp-field/index.parts.d.mts","./node_modules/@base-ui/react/otp-field/index.d.mts","./node_modules/@base-ui/utils/usetimeout.d.mts","./node_modules/@base-ui/react/popover/store/popoverstore.d.mts","./node_modules/@base-ui/react/popover/store/popoverhandle.d.mts","./node_modules/@base-ui/react/popover/root/popoverroot.d.mts","./node_modules/@base-ui/react/popover/trigger/popovertrigger.d.mts","./node_modules/@base-ui/react/popover/portal/popoverportal.d.mts","./node_modules/@base-ui/react/popover/positioner/popoverpositioner.d.mts","./node_modules/@base-ui/react/popover/popup/popoverpopup.d.mts","./node_modules/@base-ui/react/popover/arrow/popoverarrow.d.mts","./node_modules/@base-ui/react/popover/backdrop/popoverbackdrop.d.mts","./node_modules/@base-ui/react/popover/title/popovertitle.d.mts","./node_modules/@base-ui/react/popover/description/popoverdescription.d.mts","./node_modules/@base-ui/react/popover/close/popoverclose.d.mts","./node_modules/@base-ui/react/popover/viewport/popoverviewport.d.mts","./node_modules/@base-ui/react/popover/index.parts.d.mts","./node_modules/@base-ui/react/popover/index.d.mts","./node_modules/@base-ui/react/preview-card/store/previewcardstore.d.mts","./node_modules/@base-ui/react/preview-card/store/previewcardhandle.d.mts","./node_modules/@base-ui/react/preview-card/root/previewcardroot.d.mts","./node_modules/@base-ui/react/utils/floatingportallite.d.mts","./node_modules/@base-ui/react/preview-card/portal/previewcardportal.d.mts","./node_modules/@base-ui/react/preview-card/trigger/previewcardtrigger.d.mts","./node_modules/@base-ui/react/preview-card/positioner/previewcardpositioner.d.mts","./node_modules/@base-ui/react/preview-card/popup/previewcardpopup.d.mts","./node_modules/@base-ui/react/preview-card/arrow/previewcardarrow.d.mts","./node_modules/@base-ui/react/preview-card/backdrop/previewcardbackdrop.d.mts","./node_modules/@base-ui/react/preview-card/viewport/previewcardviewport.d.mts","./node_modules/@base-ui/react/preview-card/index.parts.d.mts","./node_modules/@base-ui/react/preview-card/index.d.mts","./node_modules/@base-ui/react/progress/root/progressroot.d.mts","./node_modules/@base-ui/react/progress/track/progresstrack.d.mts","./node_modules/@base-ui/react/progress/indicator/progressindicator.d.mts","./node_modules/@base-ui/react/progress/value/progressvalue.d.mts","./node_modules/@base-ui/react/progress/label/progresslabel.d.mts","./node_modules/@base-ui/react/progress/index.parts.d.mts","./node_modules/@base-ui/react/progress/index.d.mts","./node_modules/@base-ui/react/radio/root/radioroot.d.mts","./node_modules/@base-ui/react/radio/indicator/radioindicator.d.mts","./node_modules/@base-ui/react/radio/index.parts.d.mts","./node_modules/@base-ui/react/radio/index.d.mts","./node_modules/@base-ui/react/radio-group/radiogroup.d.mts","./node_modules/@base-ui/react/radio-group/index.d.mts","./node_modules/@base-ui/react/scroll-area/root/scrollarearoot.d.mts","./node_modules/@base-ui/react/scroll-area/viewport/scrollareaviewport.d.mts","./node_modules/@base-ui/react/scroll-area/scrollbar/scrollareascrollbar.d.mts","./node_modules/@base-ui/react/scroll-area/content/scrollareacontent.d.mts","./node_modules/@base-ui/react/scroll-area/thumb/scrollareathumb.d.mts","./node_modules/@base-ui/react/scroll-area/corner/scrollareacorner.d.mts","./node_modules/@base-ui/react/scroll-area/index.parts.d.mts","./node_modules/@base-ui/react/scroll-area/index.d.mts","./node_modules/@base-ui/react/select/root/selectroot.d.mts","./node_modules/@base-ui/react/select/label/selectlabel.d.mts","./node_modules/@base-ui/react/select/trigger/selecttrigger.d.mts","./node_modules/@base-ui/react/select/value/selectvalue.d.mts","./node_modules/@base-ui/react/select/icon/selecticon.d.mts","./node_modules/@base-ui/react/select/portal/selectportal.d.mts","./node_modules/@base-ui/react/select/backdrop/selectbackdrop.d.mts","./node_modules/@base-ui/react/select/positioner/selectpositioner.d.mts","./node_modules/@base-ui/react/select/popup/selectpopup.d.mts","./node_modules/@base-ui/react/select/list/selectlist.d.mts","./node_modules/@base-ui/react/select/item/selectitem.d.mts","./node_modules/@base-ui/react/select/item-indicator/selectitemindicator.d.mts","./node_modules/@base-ui/react/select/item-text/selectitemtext.d.mts","./node_modules/@base-ui/react/select/arrow/selectarrow.d.mts","./node_modules/@base-ui/react/select/scroll-down-arrow/selectscrolldownarrow.d.mts","./node_modules/@base-ui/react/select/scroll-up-arrow/selectscrolluparrow.d.mts","./node_modules/@base-ui/react/select/group/selectgroup.d.mts","./node_modules/@base-ui/react/select/group-label/selectgrouplabel.d.mts","./node_modules/@base-ui/react/select/index.parts.d.mts","./node_modules/@base-ui/react/select/index.d.mts","./node_modules/@base-ui/react/slider/root/sliderroot.d.mts","./node_modules/@base-ui/react/slider/label/sliderlabel.d.mts","./node_modules/@base-ui/react/slider/value/slidervalue.d.mts","./node_modules/@base-ui/react/slider/control/slidercontrol.d.mts","./node_modules/@base-ui/react/slider/track/slidertrack.d.mts","./node_modules/@base-ui/react/internals/labelable-provider/labelablecontext.d.mts","./node_modules/@base-ui/react/slider/thumb/sliderthumb.d.mts","./node_modules/@base-ui/react/slider/indicator/sliderindicator.d.mts","./node_modules/@base-ui/react/slider/index.parts.d.mts","./node_modules/@base-ui/react/slider/index.d.mts","./node_modules/@base-ui/react/switch/root/switchroot.d.mts","./node_modules/@base-ui/react/switch/thumb/switchthumb.d.mts","./node_modules/@base-ui/react/switch/index.parts.d.mts","./node_modules/@base-ui/react/switch/index.d.mts","./node_modules/@base-ui/react/tabs/tab/tabstab.d.mts","./node_modules/@base-ui/react/tabs/root/tabsroot.d.mts","./node_modules/@base-ui/react/tabs/indicator/tabsindicator.d.mts","./node_modules/@base-ui/react/tabs/panel/tabspanel.d.mts","./node_modules/@base-ui/react/tabs/list/tabslist.d.mts","./node_modules/@base-ui/react/tabs/index.parts.d.mts","./node_modules/@base-ui/react/tabs/index.d.mts","./node_modules/@base-ui/react/toast/positioner/toastpositioner.d.mts","./node_modules/@base-ui/react/toast/usetoastmanager.d.mts","./node_modules/@base-ui/react/toast/createtoastmanager.d.mts","./node_modules/@base-ui/react/toast/provider/toastprovider.d.mts","./node_modules/@base-ui/react/toast/viewport/toastviewport.d.mts","./node_modules/@base-ui/react/toast/root/toastroot.d.mts","./node_modules/@base-ui/react/toast/content/toastcontent.d.mts","./node_modules/@base-ui/react/toast/description/toastdescription.d.mts","./node_modules/@base-ui/react/toast/title/toasttitle.d.mts","./node_modules/@base-ui/react/toast/close/toastclose.d.mts","./node_modules/@base-ui/react/toast/action/toastaction.d.mts","./node_modules/@base-ui/react/toast/portal/toastportal.d.mts","./node_modules/@base-ui/react/toast/arrow/toastarrow.d.mts","./node_modules/@base-ui/react/toast/index.parts.d.mts","./node_modules/@base-ui/react/toast/index.d.mts","./node_modules/@base-ui/react/toggle/toggle.d.mts","./node_modules/@base-ui/react/toggle/index.d.mts","./node_modules/@base-ui/react/toggle-group/togglegroup.d.mts","./node_modules/@base-ui/react/toggle-group/index.d.mts","./node_modules/@base-ui/react/toolbar/separator/toolbarseparator.d.mts","./node_modules/@base-ui/react/toolbar/root/toolbarroot.d.mts","./node_modules/@base-ui/react/toolbar/group/toolbargroup.d.mts","./node_modules/@base-ui/react/toolbar/button/toolbarbutton.d.mts","./node_modules/@base-ui/react/toolbar/link/toolbarlink.d.mts","./node_modules/@base-ui/react/toolbar/input/toolbarinput.d.mts","./node_modules/@base-ui/react/toolbar/index.parts.d.mts","./node_modules/@base-ui/react/toolbar/index.d.mts","./node_modules/@base-ui/react/tooltip/store/tooltipstore.d.mts","./node_modules/@base-ui/react/tooltip/store/tooltiphandle.d.mts","./node_modules/@base-ui/react/tooltip/root/tooltiproot.d.mts","./node_modules/@base-ui/react/tooltip/trigger/tooltiptrigger.d.mts","./node_modules/@base-ui/react/tooltip/portal/tooltipportal.d.mts","./node_modules/@base-ui/react/tooltip/positioner/tooltippositioner.d.mts","./node_modules/@base-ui/react/tooltip/popup/tooltippopup.d.mts","./node_modules/@base-ui/react/tooltip/arrow/tooltiparrow.d.mts","./node_modules/@base-ui/react/tooltip/provider/tooltipprovider.d.mts","./node_modules/@base-ui/react/tooltip/viewport/tooltipviewport.d.mts","./node_modules/@base-ui/react/tooltip/index.parts.d.mts","./node_modules/@base-ui/react/tooltip/index.d.mts","./node_modules/@base-ui/react/use-render/userender.d.mts","./node_modules/@base-ui/react/use-render/index.d.mts","./node_modules/@base-ui/react/index.d.mts","./node_modules/clsx/clsx.d.mts","./node_modules/tailwind-merge/dist/types.d.ts","./node_modules/class-variance-authority/dist/types.d.ts","./node_modules/class-variance-authority/dist/index.d.ts","./src/lib/cva.config.ts","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/textarea.tsx","./src/components/ui/input-group.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/ui/combobox.tsx","./src/components/shared/searchselect.tsx","./src/components/ui/label.tsx","./src/components/ui/separator.tsx","./src/components/ui/field.tsx","./src/components/ui/select.tsx","./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","./src/components/object_permission_types.ts","./src/components/ui/tooltip.tsx","./node_modules/react-hook-form/dist/constants.d.ts","./node_modules/react-hook-form/dist/utils/createsubject.d.ts","./node_modules/react-hook-form/dist/types/events.d.ts","./node_modules/react-hook-form/dist/types/path/common.d.ts","./node_modules/react-hook-form/dist/types/path/eager.d.ts","./node_modules/react-hook-form/dist/types/path/index.d.ts","./node_modules/react-hook-form/dist/types/fieldarray.d.ts","./node_modules/react-hook-form/dist/types/resolvers.d.ts","./node_modules/react-hook-form/dist/types/form.d.ts","./node_modules/react-hook-form/dist/types/utils.d.ts","./node_modules/react-hook-form/dist/types/fields.d.ts","./node_modules/react-hook-form/dist/types/errors.d.ts","./node_modules/react-hook-form/dist/types/validator.d.ts","./node_modules/react-hook-form/dist/types/controller.d.ts","./node_modules/react-hook-form/dist/types/watch.d.ts","./node_modules/react-hook-form/dist/types/index.d.ts","./node_modules/react-hook-form/dist/controller.d.ts","./node_modules/react-hook-form/dist/fieldarray.d.ts","./node_modules/react-hook-form/dist/form.d.ts","./node_modules/react-hook-form/dist/formstatesubscribe.d.ts","./node_modules/react-hook-form/dist/logic/appenderrors.d.ts","./node_modules/react-hook-form/dist/logic/createformcontrol.d.ts","./node_modules/react-hook-form/dist/logic/index.d.ts","./node_modules/react-hook-form/dist/usecontroller.d.ts","./node_modules/react-hook-form/dist/usefieldarray.d.ts","./node_modules/react-hook-form/dist/useform.d.ts","./node_modules/react-hook-form/dist/useformcontext.d.ts","./node_modules/react-hook-form/dist/useformstate.d.ts","./node_modules/react-hook-form/dist/usewatch.d.ts","./node_modules/react-hook-form/dist/utils/get.d.ts","./node_modules/react-hook-form/dist/utils/set.d.ts","./node_modules/react-hook-form/dist/utils/index.d.ts","./node_modules/react-hook-form/dist/watch.d.ts","./node_modules/react-hook-form/dist/index.d.ts","./src/utils/textutils.ts","./src/components/common_components/mountedformfield.tsx","./src/components/common_components/check_openapi_schema.tsx","./src/components/mcp_tools/types.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/types.ts","./src/components/mcp_tools/constants.ts","./src/components/key_team_helpers/fetch_available_models_team_key.tsx","./src/components/llm_calls/fetch_models.tsx","./src/components/shared/multiselect.tsx","./src/components/ui/card.tsx","./src/components/add_model/complexity_router_keywords.ts","./src/components/ui/switch.tsx","./src/components/add_model/affinitycontrols.tsx","./src/components/add_model/nonreasoningtiertoggle.tsx","./src/components/add_model/tierrestrictions.tsx","./src/components/add_model/tierconfigintro.tsx","./src/components/add_model/tierrowselect.tsx","./src/components/add_model/modalityroutingcontrols.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/slider.tsx","./src/components/add_model/adaptiveroutingconfig.tsx","./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/components/ui/dialog.tsx","./src/components/add_model/classifierprompteditorstate.ts","./src/components/add_model/classifierprompteditor.tsx","./src/components/add_model/openingprompteditor.tsx","./src/app/(dashboard)/hooks/autorouter/usecomplexityscorerdefaults.ts","./src/components/ui/badge.tsx","./src/components/add_model/heuristic_scoring_knobs.ts","./src/components/add_model/heuristicscoringconfig.tsx","./src/components/add_model/classifierreasoningeffortselect.tsx","./src/components/add_model/classifiercircuitbreakerconfig.tsx","./src/components/add_model/classifiervisionconfig.tsx","./src/components/add_model/classificationmethodconfig.tsx","./src/components/add_model/contextwindowescalationconfig.tsx","./src/components/add_model/responseformatcontrols.tsx","./src/components/add_model/stallescalationconfig.tsx","./src/components/add_model/tier_set_actions.ts","./src/components/add_model/tiermodeleffortrows.tsx","./src/components/add_model/escalationkeywords.tsx","./src/components/add_model/semantickeywordmatching.tsx","./src/app/(dashboard)/hooks/guardrails/useguardrails.ts","./src/components/add_model/buildautoroutercompression.ts","./src/components/add_model/compressioncontrols.tsx","./src/components/add_model/complexityrouterconfig.tsx","./src/components/add_model/tier_rows.ts","./src/components/add_model/complexity_router_tiers.ts","./src/components/add_model/keywordtierrules.tsx","./src/components/add_model/build_complexity_router_config.ts","./src/lib/autorouter_presets.ts","./src/components/vector_store_management/types.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/shared/datatable/types.ts","./src/components/shared/datatable/columnmeta.ts","./src/components/ui/skeleton.tsx","./src/components/ui/table.tsx","./src/components/shared/datatable/datatablepagination.tsx","./src/components/shared/datatable/datatable.tsx","./src/components/ui/sheet.tsx","./src/components/shared/datatable/datatablefilterdrawer.tsx","./src/components/ui/checkbox.tsx","./src/components/shared/datatable/datatableselectioncolumn.tsx","./src/components/shared/datatable/datatableviewoptions.tsx","./src/components/shared/datatable/datatabletoolbar.tsx","./src/components/shared/datatable/datatablesortheader.tsx","./src/components/shared/datatable/index.ts","./src/app/(dashboard)/hooks/models/usemodels.ts","./src/components/shared/table_cells/autoroutertag.tsx","./src/components/shared/table_cells/cell_tooltip.tsx","./src/components/shared/table_cells/date_cell.tsx","./src/utils/datautils.ts","./src/components/shared/table_cells/id_cell.tsx","./src/components/shared/entitylink.tsx","./src/components/shared/table_cells/identity_cell.tsx","./src/components/key_scope.ts","./src/components/shared/table_cells/models_cell.tsx","./src/components/shared/table_cells/money_cell.tsx","./src/components/shared/inheritedbudgethint.tsx","./src/components/shared/meter.tsx","./src/components/shared/table_cells/spend_budget_cell.tsx","./src/components/shared/table_cells/status_badge.tsx","./src/components/shared/table_cells/index.ts","./src/lib/serverrootpath.ts","./src/utils/uihref.ts","./src/utils/entitylinks.ts","./src/app/(dashboard)/vector-stores/_components/indexestablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/indexestable.tsx","./src/app/(dashboard)/vector-stores/_components/indexestab.tsx","./src/components/view_logs/logdetailsdrawer/routingdecisioncard.tsx","./src/lib/http/resolveapibase.ts","./src/lib/http/runtime.ts","./src/components/networking.tsx","./src/components/key_team_helpers/key_list.tsx","./src/app/(dashboard)/networking.ts","./src/app/(dashboard)/hooks/teams/useteams.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.ts","./src/app/(dashboard)/hooks/useisorgadmin.ts","./src/app/(dashboard)/hooks/healthreadiness/usehealthreadinessdetails.ts","./src/utils/proxyutils.ts","./src/app/(dashboard)/hooks/proxysettings/useproxysettings.ts","./src/app/(dashboard)/hooks/uselogout.ts","./src/contexts/themecontext.tsx","./src/components/ui/scroll-area.tsx","./src/components/shared/sidebar.tsx","./src/utils/capabilities.ts","./src/utils/localstorageutils.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.ts","./src/components/betabadge.tsx","./src/app/(dashboard)/hooks/usedisableblogposts.ts","./src/app/(dashboard)/hooks/usedisablebouncingicon.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.ts","./src/components/navbar/navdisplayname.ts","./src/components/shared/copybutton.tsx","./src/components/ui/avatar.tsx","./src/components/ui/popover.tsx","./src/components/sidebaraccountmenu/sidebaraccountmenu.tsx","./src/app/(dashboard)/hooks/license/uselicenseinfo.ts","./src/utils/licenseutils.ts","./src/components/sidebarusagecard.tsx","./src/components/leftnav.tsx","./src/app/(dashboard)/legacypageroutes.ts","./src/app/(dashboard)/legacypageroutes.test.ts","./src/app/(dashboard)/access-groups/_components/types.ts","./node_modules/zod/v4/core/standard-schema.d.cts","./node_modules/zod/v4/core/util.d.cts","./node_modules/zod/v4/core/versions.d.cts","./node_modules/zod/v4/core/schemas.d.cts","./node_modules/zod/v4/core/checks.d.cts","./node_modules/zod/v4/core/errors.d.cts","./node_modules/zod/v4/core/core.d.cts","./node_modules/zod/v4/core/parse.d.cts","./node_modules/zod/v4/core/regexes.d.cts","./node_modules/zod/v4/locales/ar.d.cts","./node_modules/zod/v4/locales/az.d.cts","./node_modules/zod/v4/locales/be.d.cts","./node_modules/zod/v4/locales/ca.d.cts","./node_modules/zod/v4/locales/cs.d.cts","./node_modules/zod/v4/locales/de.d.cts","./node_modules/zod/v4/locales/en.d.cts","./node_modules/zod/v4/locales/eo.d.cts","./node_modules/zod/v4/locales/es.d.cts","./node_modules/zod/v4/locales/fa.d.cts","./node_modules/zod/v4/locales/fi.d.cts","./node_modules/zod/v4/locales/fr.d.cts","./node_modules/zod/v4/locales/fr-ca.d.cts","./node_modules/zod/v4/locales/he.d.cts","./node_modules/zod/v4/locales/hu.d.cts","./node_modules/zod/v4/locales/id.d.cts","./node_modules/zod/v4/locales/it.d.cts","./node_modules/zod/v4/locales/ja.d.cts","./node_modules/zod/v4/locales/kh.d.cts","./node_modules/zod/v4/locales/ko.d.cts","./node_modules/zod/v4/locales/mk.d.cts","./node_modules/zod/v4/locales/ms.d.cts","./node_modules/zod/v4/locales/nl.d.cts","./node_modules/zod/v4/locales/no.d.cts","./node_modules/zod/v4/locales/ota.d.cts","./node_modules/zod/v4/locales/ps.d.cts","./node_modules/zod/v4/locales/pl.d.cts","./node_modules/zod/v4/locales/pt.d.cts","./node_modules/zod/v4/locales/ru.d.cts","./node_modules/zod/v4/locales/sl.d.cts","./node_modules/zod/v4/locales/sv.d.cts","./node_modules/zod/v4/locales/ta.d.cts","./node_modules/zod/v4/locales/th.d.cts","./node_modules/zod/v4/locales/tr.d.cts","./node_modules/zod/v4/locales/ua.d.cts","./node_modules/zod/v4/locales/ur.d.cts","./node_modules/zod/v4/locales/vi.d.cts","./node_modules/zod/v4/locales/zh-cn.d.cts","./node_modules/zod/v4/locales/zh-tw.d.cts","./node_modules/zod/v4/locales/index.d.cts","./node_modules/zod/v4/core/registries.d.cts","./node_modules/zod/v4/core/doc.d.cts","./node_modules/zod/v4/core/function.d.cts","./node_modules/zod/v4/core/api.d.cts","./node_modules/zod/v4/core/json-schema.d.cts","./node_modules/zod/v4/core/to-json-schema.d.cts","./node_modules/zod/v4/core/index.d.cts","./node_modules/zod/v4/classic/errors.d.cts","./node_modules/zod/v4/classic/parse.d.cts","./node_modules/zod/v4/classic/schemas.d.cts","./node_modules/zod/v4/classic/checks.d.cts","./node_modules/zod/v4/classic/compat.d.cts","./node_modules/zod/v4/classic/iso.d.cts","./node_modules/zod/v4/classic/coerce.d.cts","./node_modules/zod/v4/classic/external.d.cts","./node_modules/zod/v4/classic/index.d.cts","./node_modules/zod/v4/index.d.cts","./src/app/(dashboard)/access-groups/_components/access-group-create/schema.ts","./src/app/(dashboard)/access-groups/_components/access-group-create/mapper.ts","./src/app/(dashboard)/access-groups/_components/access-group-create/mapper.test.ts","./src/app/(dashboard)/agents/_components/agent_config.ts","./src/app/(dashboard)/agents/_components/agent_discovery_utils.ts","./src/app/(dashboard)/agents/_components/agent_discovery_utils.test.ts","./src/components/agents/types.ts","./src/app/(dashboard)/agents/_components/agent_type_utils.ts","./src/app/(dashboard)/agents/_components/agent_type_utils.test.ts","./src/app/(dashboard)/budgets/_components/budgetprecision.ts","./src/app/(dashboard)/budgets/_components/budgetprecision.test.ts","./src/app/(dashboard)/budgets/_components/constants.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsfields.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsutils.ts","./src/app/(dashboard)/caching/_components/cache_settings/cachesettingsutils.test.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisfields.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisutils.ts","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisutils.test.ts","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarks.ts","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarks.test.ts","./src/components/usagepage/types.ts","./src/app/(dashboard)/cost-optimization/_components/costoptimizationutils.ts","./src/app/(dashboard)/cost-optimization/_components/costoptimizationutils.test.ts","./src/app/(dashboard)/cost-optimization/_components/helpers.ts","./src/app/(dashboard)/cost-optimization/_components/helpers.test.ts","./node_modules/openapi-typescript-helpers/dist/index.d.mts","./node_modules/openapi-fetch/dist/index.d.mts","./node_modules/openapi-react-query/dist/index.d.mts","./src/lib/http/api.ts","./src/app/(dashboard)/usage/_components/hooks/usepaginateddailyactivity.ts","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.ts","./src/app/(dashboard)/cost-optimization/_components/useautorouterbenchmarks.ts","./src/app/(dashboard)/cost-optimization/_components/useautorouterbenchmarks.test.ts","./src/app/(dashboard)/cost-optimization/_components/useshadoweval.ts","./src/app/(dashboard)/cost-optimization/_components/useshadoweval.test.ts","./src/components/ui/alert-dialog.tsx","./src/components/ui/tabs.tsx","./src/app/(dashboard)/cost-tracking/_components/types.ts","./src/components/common_components/simple_table.tsx","./src/lib/assetpaths.ts","./src/components/provider_info_helpers.tsx","./src/lib/logotreatments.ts","./src/components/molecules/logo/logo.tsx","./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/hooks/use-safe-layout-effect.ts","./src/components/ui/ui-loading-spinner.tsx","./src/components/ui/dropdown-menu.tsx","./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","./src/components/helplink.tsx","./node_modules/@types/react-syntax-highlighter/index.d.ts","./node_modules/next-themes/dist/index.d.ts","./src/hooks/usesyntaxtheme.ts","./src/components/codeblock.tsx","./src/app/(dashboard)/cost-tracking/_components/how_it_works.tsx","./src/app/(dashboard)/cost-tracking/_components/provider_display_helpers.ts","./src/app/(dashboard)/cost-tracking/_components/use_discount_config.ts","./src/app/(dashboard)/cost-tracking/_components/use_margin_config.ts","./src/app/(dashboard)/cost-tracking/_components/use_block_unpriced_config.ts","./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","./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","./src/app/(dashboard)/cost-tracking/_components/use_block_unpriced_config.test.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","./src/app/(dashboard)/guardrails/_components/guardrail_garden_configs.ts","./src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts","./src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts","./src/app/(dashboard)/guardrails/_components/content_filter/action_options.ts","./src/app/(dashboard)/guardrails/_components/custom_code/customcodemodal.tsx","./src/app/(dashboard)/guardrails/_components/custom_code/index.ts","./src/app/(dashboard)/hooks/useauthorized.serverrootpath.test.ts","./src/app/(dashboard)/hooks/useauthorized.test.ts","./src/app/(dashboard)/hooks/usecan.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.test.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.test.ts","./src/app/(dashboard)/hooks/usehideautorouterannouncement.ts","./src/app/(dashboard)/hooks/useisorgadmin.test.ts","./src/utils/tabroutes.ts","./src/app/(dashboard)/hooks/usetabrouting.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/usedeleteaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/useeditaccessgroup.ts","./src/app/(dashboard)/hooks/agents/useagents.ts","./src/app/(dashboard)/hooks/agents/useagents.test.ts","./src/app/(dashboard)/hooks/autorouter/useautorouterpresets.ts","./src/app/(dashboard)/hooks/blogposts/useblogposts.ts","./src/app/(dashboard)/hooks/budgets/budgetfilters.ts","./src/app/(dashboard)/hooks/budgets/budgetfilters.test.ts","./node_modules/@tanstack/react-store/dist/createstorecontext.d.ts","./node_modules/@tanstack/store/dist/alien.d.ts","./node_modules/@tanstack/store/dist/types.d.ts","./node_modules/@tanstack/store/dist/atom.d.ts","./node_modules/@tanstack/store/dist/store.d.ts","./node_modules/@tanstack/store/dist/shallow.d.ts","./node_modules/@tanstack/store/dist/index.d.ts","./node_modules/@tanstack/react-store/dist/usecreateatom.d.ts","./node_modules/@tanstack/react-store/dist/usecreatestore.d.ts","./node_modules/@tanstack/react-store/dist/useselector.d.ts","./node_modules/@tanstack/react-store/dist/useatom.d.ts","./node_modules/@tanstack/react-store/dist/_usestore.d.ts","./node_modules/@tanstack/react-store/dist/usestore.d.ts","./node_modules/@tanstack/react-store/dist/index.d.ts","./node_modules/@tanstack/pacer/dist/types.d.ts","./node_modules/@tanstack/pacer/dist/debouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedcallback.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedstate.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/usedebouncedvalue.d.ts","./node_modules/@tanstack/react-pacer/dist/debouncer/index.d.ts","./src/utils/debounceconstants.ts","./src/app/(dashboard)/hooks/common/useresourcelist.ts","./src/app/(dashboard)/hooks/budgets/usebudgets.ts","./src/app/(dashboard)/hooks/caching/usecacheactivity.ts","./src/app/(dashboard)/hooks/caching/usecacheactivity.test.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/cyberarkapi.ts","./src/app/(dashboard)/hooks/configoverrides/hashicorpvaultapi.ts","./src/app/(dashboard)/hooks/configoverrides/usecyberarkconfig.ts","./src/app/(dashboard)/hooks/configoverrides/usedeletecyberarkconfig.ts","./src/app/(dashboard)/hooks/configoverrides/usehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/usedeletehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/useupdatecyberarkconfig.ts","./src/app/(dashboard)/hooks/configoverrides/useupdatehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/coordinationredis/usecoordinationredissettings.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.test.ts","./src/app/(dashboard)/hooks/guardrails/useguardrailsusage.ts","./src/app/(dashboard)/hooks/guardrails/useguardrailsusage.test.ts","./src/app/(dashboard)/hooks/guardrails/useregisterguardrail.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/usekeyinfo.ts","./src/app/(dashboard)/hooks/keys/usekeys.test.ts","./src/app/(dashboard)/hooks/keys/useresetkeyspend.ts","./src/app/(dashboard)/hooks/keys/usesetkeyblockedstate.ts","./src/app/(dashboard)/hooks/keys/usesetkeyblockedstate.test.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/mcptoolsearchsettings/usemcptoolsearchsettings.ts","./src/app/(dashboard)/hooks/modelaccessgroups/usemodelaccessgroups.ts","./src/app/(dashboard)/hooks/modelaccessgroups/usedeletemodelaccessgroupbudget.ts","./src/app/(dashboard)/hooks/modelaccessgroups/usesetmodelaccessgroupbudget.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.test.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.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/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/spendlogs/usespendlogendusers.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogendusers.test.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogusers.ts","./src/app/(dashboard)/hooks/spendlogs/usespendlogusers.test.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/useteammetadataschema.ts","./src/app/(dashboard)/hooks/teams/useteammetadataschema.test.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/useptucostattributionenabled.ts","./src/app/(dashboard)/hooks/uisettings/useptucostattributionenabled.test.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/userbanner/useuserbanner.ts","./src/app/(dashboard)/hooks/userbanner/useupdateuserbanner.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)/mcp-servers/_components/createoauthuistate.ts","./src/app/(dashboard)/mcp-servers/_components/createoauthuistate.test.ts","./src/app/(dashboard)/mcp-servers/_components/utils.tsx","./src/app/(dashboard)/mcp-servers/_components/createserverpayload.ts","./src/app/(dashboard)/mcp-servers/_components/createserverpayload.test.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.differential.cases.ts","./src/app/(dashboard)/mcp-servers/_components/editserverpayload.differential.test.ts","./src/app/(dashboard)/mcp-servers/_components/importconnectorconfig.ts","./src/app/(dashboard)/mcp-servers/_components/importconnectorconfig.test.ts","./src/app/(dashboard)/mcp-servers/_components/mcpfieldrules.ts","./src/app/(dashboard)/mcp-servers/_components/mcpfieldrules.test.ts","./src/app/(dashboard)/mcp-servers/_components/mcpformstore.ts","./src/app/(dashboard)/mcp-servers/_components/mountedserverfields.ts","./src/app/(dashboard)/mcp-servers/_components/mountedserverfields.test.ts","./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","./src/app/(dashboard)/mcp-servers/_components/testutils.ts","./src/app/(dashboard)/mcp-servers/_components/toolcallarguments.ts","./src/app/(dashboard)/mcp-servers/_components/toolcallarguments.test.ts","./node_modules/nuqs/dist/defs-butbdnwx.d.ts","./node_modules/nuqs/dist/context-3xask51n.d.ts","./node_modules/nuqs/dist/adapters/testing.d.ts","./node_modules/@standard-schema/spec/dist/index.d.ts","./node_modules/nuqs/dist/index.d.ts","./src/app/(dashboard)/models-and-endpoints/detailnavigation.ts","./src/app/(dashboard)/models-and-endpoints/detailnavigation.test.ts","./src/app/(dashboard)/models-and-endpoints/usemodeldashboarddata.ts","./src/app/(dashboard)/models-and-endpoints/components/accessgroupbudgetpayload.ts","./src/app/(dashboard)/models-and-endpoints/components/accessgroupbudgetpayload.test.ts","./src/components/add_model/auto_router_strategies.ts","./src/utils/modelpermissions.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterrows.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterrows.test.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/fitpills.ts","./src/app/(dashboard)/models-and-endpoints/components/autorouters/fitpills.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/components/chat_ui/uploadvalidation.ts","./src/app/(dashboard)/playground/components/chat_ui/uploadvalidation.test.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/utils/promptcacheusage.ts","./src/components/chat_ui/responsemetrics.tsx","./src/components/chat_ui/types.ts","./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/components/policies/types.ts","./src/app/(dashboard)/policies/_components/build_attachment_data.ts","./src/app/(dashboard)/policies/_components/build_attachment_data.test.ts","./src/app/(dashboard)/policies/_components/scope_validation.ts","./src/app/(dashboard)/policies/_components/scope_validation.test.ts","./src/app/(dashboard)/projects/_components/projectmodals/projectformschema.ts","./src/components/shared/usepaginatedcombobox.ts","./src/components/shared/paginatedsearchselect.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/tagsinput.tsx","./src/components/agent_management/agentselector.tsx","./src/components/common_components/accessgroupselector.tsx","./src/components/common_components/budget_duration_dropdown.tsx","./src/components/common_components/keylifecyclesettings.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","./src/components/common_components/modelselector.tsx","./src/components/common_components/modelaliasmanager.tsx","./src/components/common_components/passthroughroutesselector.tsx","./src/components/callback_info_helpers.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","./src/components/common_components/team_dropdown.tsx","./src/components/common_components/organizationdropdown.tsx","./src/components/common_components/projectdropdown.tsx","./src/components/shared/form/formfield.tsx","./src/components/ui/alert.tsx","./src/components/shared/alert.tsx","./node_modules/@types/react-copy-to-clipboard/index.d.ts","./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/key_team_helpers/tagratelimiteditor.tsx","./src/components/mcp_server_management/mcpserverselector.tsx","./src/utils/mcptoolcrudclassification.ts","./src/components/mcp_tools/mcpcrudpermissionpanel.tsx","./src/components/mcp_server_management/effectivemcpservers.ts","./src/components/mcp_server_management/mcptoolpermissions.tsx","./src/components/shared/createdkeydisplay.tsx","./src/components/vector_store_management/vectorstoreselector.tsx","./src/components/organisms/createkeypayload.ts","./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/app/(dashboard)/search-tools/_components/types.tsx","./src/app/(dashboard)/search-tools/_components/searchtoolpayload.ts","./src/app/(dashboard)/search-tools/_components/searchtoolpayload.test.ts","./src/app/(dashboard)/usage/_components/components/gatewayactivity.ts","./src/app/(dashboard)/usage/_components/components/gatewayactivity.test.ts","./src/components/usagepage/keyactivitylabel.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusageaggregations.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusagesummary.ts","./src/app/(dashboard)/usage/_components/components/entityusage/entityusagesummary.test.ts","./node_modules/@types/papaparse/index.d.ts","./src/app/(dashboard)/usage/_components/components/entityusage/teamuserspend.ts","./src/app/(dashboard)/usage/_components/components/entityusage/teamuserspend.test.ts","./src/app/(dashboard)/usage/_components/hooks/usepaginateddailyactivity.test.ts","./src/app/(dashboard)/users/_components/default-user-settings/schema.ts","./src/app/(dashboard)/users/_components/default-user-settings/mapper.ts","./src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts","./src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts","./src/components/key_scope.test.ts","./src/components/networking.test.ts","./src/components/page_metadata.ts","./src/components/page_utils.ts","./src/components/page_utils.test.ts","./src/components/cloudzerocosttracking/cloudzeropayload.ts","./src/components/cloudzerocosttracking/cloudzeropayload.test.ts","./src/utils/teamutils.ts","./src/components/shared/date_picker_types.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/guardrailsmonitor/usageunits.ts","./src/components/guardrailsmonitor/usageunits.test.ts","./src/components/modelselect/modelutils.ts","./src/components/modelselect/modelutils.test.ts","./src/components/navbar/navdisplayname.test.ts","./src/components/navbar/navproductlinkclass.ts","./src/components/settings/adminsettings/cyberark/constants.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/mcptoolsearchsettings/toolsearchform.ts","./src/components/settings/adminsettings/mcptoolsearchsettings/toolsearchform.test.ts","./src/components/settings/adminsettings/pluginsettings/schema.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/teamspage/teamscsvexport.ts","./src/components/teamspage/teamscsvexport.test.ts","./src/components/toolpolicies/toolpoliciesqueries.ts","./src/components/usagepage/keyactivitylabel.test.ts","./src/components/usagepage/utils/value_formatters.tsx","./src/components/usagepage/utils/value_formatters.test.ts","./src/components/add_model/auto_setup.ts","./src/components/add_model/auto_setup.test.ts","./src/components/add_model/buildautoroutercompression.test.ts","./src/components/add_model/build_auto_router_routing_test_request.ts","./src/components/add_model/build_auto_router_routing_test_request.test.ts","./src/components/add_model/build_auto_router_test_targets.ts","./src/components/add_model/build_auto_router_test_targets.test.ts","./src/components/add_model/build_complexity_router_config.test.ts","./src/components/add_model/classifierprompteditorstate.test.ts","./src/components/add_model/complexity_router_keywords.test.ts","./src/components/add_model/complexity_router_tiers.test.ts","./src/components/add_model/heuristic_scoring_knobs.test.ts","./src/components/add_model/tier_rows.test.ts","./src/components/add_model/tier_set_actions.test.ts","./src/components/chat/types.ts","./src/components/chat/usechathistory.ts","./src/contexts/chatshellcontext.tsx","./node_modules/dayjs/locale/types.d.ts","./node_modules/dayjs/locale/index.d.ts","./node_modules/dayjs/index.d.ts","./src/components/chat/conversationlist.tsx","./src/components/chat/chatshell.tsx","./src/components/chat/chatshell.serverrootpath.test.ts","./src/components/chat/usechathistory.test.ts","./src/components/claude_code_plugins/helpers.ts","./src/components/claude_code_plugins/helpers.test.ts","./src/components/common_components/formrules.ts","./src/components/common_components/routersettingspayload.ts","./src/components/common_components/routersettingspayload.test.ts","./node_modules/zod/v3/helpers/typealiases.d.cts","./node_modules/zod/v3/helpers/util.d.cts","./node_modules/zod/v3/zoderror.d.cts","./node_modules/zod/v3/locales/en.d.cts","./node_modules/zod/v3/errors.d.cts","./node_modules/zod/v3/helpers/parseutil.d.cts","./node_modules/zod/v3/helpers/enumutil.d.cts","./node_modules/zod/v3/helpers/errorutil.d.cts","./node_modules/zod/v3/helpers/partialutil.d.cts","./node_modules/zod/v3/standard-schema.d.cts","./node_modules/zod/v3/types.d.cts","./node_modules/zod/v3/external.d.cts","./node_modules/zod/v3/index.d.cts","./node_modules/@hookform/resolvers/zod/dist/zod.d.ts","./node_modules/@hookform/resolvers/zod/dist/index.d.ts","./src/lib/forms/usezodform.ts","./src/components/add_model/accessgrouptagscombobox.tsx","./src/components/add_model/modelchoicecombobox.tsx","./src/components/add_model/routerconfigbuilder.tsx","./src/components/edit_auto_router/edit_auto_router_modal.tsx","./src/components/edit_auto_router/build_updated_complexity_router_config.test.ts","./src/components/edit_auto_router/edit_auto_router_modal.test.ts","./src/components/email_events/email_event_settings.tsx","./src/components/email_events/index.ts","./src/components/guardrails/types.ts","./src/components/key_team_helpers/modelmaxbudgeteditor.test.ts","./src/components/key_team_helpers/filter_helpers.ts","./src/components/key_team_helpers/filter_helpers.test.ts","./src/components/key_team_helpers/modelmaxbudgetpayload.ts","./src/components/key_team_helpers/modelmaxbudgetpayload.test.ts","./src/components/key_team_helpers/transform_key_info.tsx","./src/components/key_team_helpers/transform_key_info.test.ts","./src/components/key_team_helpers/useseededstate.ts","./src/components/key_team_helpers/usemodelmaxbudgetfield.ts","./src/components/llm_calls/mcp_tool_blocks.ts","./src/components/llm_calls/mcp_tool_blocks.test.ts","./src/components/mcp_server_management/effectivemcpservers.test.ts","./src/components/mcp_server_management/mcpentitlement.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/organisms/createkeypayload.test.ts","./src/components/organisms/regeneratekeypayload.ts","./src/components/organisms/regeneratekeypayload.test.ts","./src/components/organisms/utils.test.ts","./src/components/organization/org-settings/schema.ts","./src/components/organization/org-create/mapper.ts","./src/components/organization/org-create/mapper.test.ts","./src/components/organization/org-settings/mapper.ts","./src/components/organization/org-settings/mapper.test.ts","./src/components/team/teammodelaccess.ts","./src/components/permissions/inheritedgrants.ts","./src/components/permissions/inheritedgrants.test.ts","./src/components/publicmodelhub/publicmodelhubfilters.ts","./src/components/publicmodelhub/publicmodelhubfilters.test.ts","./src/components/publicmodelhubtablecolumns.tsx","./src/components/publicmodelhub/usepublicmodelhublist.ts","./src/components/publicmodelhub/usepublicmodelhubfacets.ts","./src/components/routing_groups/routinggrouppayload.ts","./src/components/routing_groups/routinggrouppayload.test.ts","./src/components/routing_groups/strategy.ts","./src/components/shared/charts/colors.ts","./node_modules/@types/d3-time/index.d.ts","./node_modules/@types/d3-scale/index.d.ts","./node_modules/victory-vendor/d3-scale.d.ts","./node_modules/recharts/types/shape/dot.d.ts","./node_modules/recharts/types/component/text.d.ts","./node_modules/recharts/types/zindex/zindexlayer.d.ts","./node_modules/recharts/types/cartesian/getcartesianposition.d.ts","./node_modules/recharts/types/component/label.d.ts","./node_modules/recharts/types/cartesian/cartesianaxis.d.ts","./node_modules/recharts/types/util/scale/customscaledefinition.d.ts","./node_modules/redux/dist/redux.d.ts","./node_modules/immer/dist/immer.d.ts","./node_modules/redux-thunk/dist/redux-thunk.d.ts","./node_modules/@reduxjs/toolkit/dist/uncheckedindexed.ts","./node_modules/@reduxjs/toolkit/dist/index.d.mts","./node_modules/recharts/types/state/cartesianaxisslice.d.ts","./node_modules/recharts/types/synchronisation/types.d.ts","./node_modules/recharts/types/chart/types.d.ts","./node_modules/recharts/types/component/defaulttooltipcontent.d.ts","./node_modules/recharts/types/context/brushupdatecontext.d.ts","./node_modules/recharts/types/state/chartdataslice.d.ts","./node_modules/recharts/types/state/types/linesettings.d.ts","./node_modules/recharts/types/state/types/scattersettings.d.ts","./node_modules/@types/d3-path/index.d.ts","./node_modules/@types/d3-shape/index.d.ts","./node_modules/victory-vendor/d3-shape.d.ts","./node_modules/recharts/types/shape/curve.d.ts","./node_modules/recharts/types/component/labellist.d.ts","./node_modules/recharts/types/component/defaultlegendcontent.d.ts","./node_modules/recharts/types/util/payload/getuniqpayload.d.ts","./node_modules/recharts/types/util/useelementoffset.d.ts","./node_modules/recharts/types/component/legend.d.ts","./node_modules/recharts/types/state/legendslice.d.ts","./node_modules/recharts/types/state/types/stackedgraphicalitem.d.ts","./node_modules/recharts/types/util/stacks/stacktypes.d.ts","./node_modules/recharts/types/util/scale/rechartsscale.d.ts","./node_modules/recharts/types/util/chartutils.d.ts","./node_modules/recharts/types/state/selectors/areaselectors.d.ts","./node_modules/recharts/types/animation/easing.d.ts","./node_modules/recharts/types/animation/matchby.d.ts","./node_modules/recharts/types/animation/animateditems.d.ts","./node_modules/recharts/types/cartesian/arearevealshape.d.ts","./node_modules/recharts/types/cartesian/area.d.ts","./node_modules/recharts/types/state/types/areasettings.d.ts","./node_modules/recharts/types/shape/rectangle.d.ts","./node_modules/recharts/types/cartesian/bar.d.ts","./node_modules/recharts/types/util/barutils.d.ts","./node_modules/recharts/types/state/types/barsettings.d.ts","./node_modules/recharts/types/state/types/radialbarsettings.d.ts","./node_modules/recharts/types/util/svgpropertiesnoevents.d.ts","./node_modules/recharts/types/util/useuniqueid.d.ts","./node_modules/recharts/types/state/types/piesettings.d.ts","./node_modules/recharts/types/state/types/radarsettings.d.ts","./node_modules/recharts/types/state/graphicalitemsslice.d.ts","./node_modules/recharts/types/state/tooltipslice.d.ts","./node_modules/recharts/types/state/optionsslice.d.ts","./node_modules/recharts/types/state/layoutslice.d.ts","./node_modules/recharts/types/util/ifoverflow.d.ts","./node_modules/recharts/types/util/resolvedefaultprops.d.ts","./node_modules/recharts/types/cartesian/referenceline.d.ts","./node_modules/recharts/types/state/referenceelementsslice.d.ts","./node_modules/recharts/types/state/brushslice.d.ts","./node_modules/recharts/types/state/rootpropsslice.d.ts","./node_modules/recharts/types/state/polaraxisslice.d.ts","./node_modules/recharts/types/state/polaroptionsslice.d.ts","./node_modules/recharts/types/cartesian/linedrawshape.d.ts","./node_modules/recharts/types/cartesian/line.d.ts","./node_modules/recharts/types/shape/symbols.d.ts","./node_modules/recharts/types/util/constants.d.ts","./node_modules/recharts/types/util/scatterutils.d.ts","./node_modules/recharts/types/cartesian/scatter.d.ts","./node_modules/recharts/types/cartesian/errorbar.d.ts","./node_modules/recharts/types/state/errorbarslice.d.ts","./node_modules/recharts/types/state/zindexslice.d.ts","./node_modules/recharts/types/state/eventsettingsslice.d.ts","./node_modules/recharts/types/state/renderedticksslice.d.ts","./node_modules/recharts/types/state/store.d.ts","./node_modules/recharts/types/cartesian/getticks.d.ts","./node_modules/recharts/types/cartesian/cartesiangrid.d.ts","./node_modules/recharts/types/state/selectors/combiners/combinedisplayedstackeddata.d.ts","./node_modules/recharts/types/state/selectors/selecttooltipaxistype.d.ts","./node_modules/recharts/types/types.d.ts","./node_modules/recharts/types/hooks.d.ts","./node_modules/recharts/types/state/selectors/axisselectors.d.ts","./node_modules/recharts/types/component/dots.d.ts","./node_modules/recharts/types/util/typeddatakey.d.ts","./node_modules/recharts/types/util/types.d.ts","./node_modules/recharts/types/container/surface.d.ts","./node_modules/recharts/types/container/layer.d.ts","./node_modules/recharts/types/component/cursor.d.ts","./node_modules/recharts/types/component/tooltip.d.ts","./node_modules/recharts/types/component/responsivecontainer.d.ts","./node_modules/recharts/types/component/cell.d.ts","./node_modules/recharts/types/component/customized.d.ts","./node_modules/recharts/types/shape/sector.d.ts","./node_modules/recharts/types/shape/polygon.d.ts","./node_modules/recharts/types/shape/cross.d.ts","./node_modules/recharts/types/polar/polargrid.d.ts","./node_modules/recharts/types/polar/defaultpolarradiusaxisprops.d.ts","./node_modules/recharts/types/polar/polarradiusaxis.d.ts","./node_modules/recharts/types/polar/defaultpolarangleaxisprops.d.ts","./node_modules/recharts/types/polar/polarangleaxis.d.ts","./node_modules/recharts/types/context/tooltipcontext.d.ts","./node_modules/recharts/types/polar/pie.d.ts","./node_modules/recharts/types/polar/radar.d.ts","./node_modules/recharts/types/util/radialbarutils.d.ts","./node_modules/recharts/types/polar/radialbar.d.ts","./node_modules/recharts/types/cartesian/brush.d.ts","./node_modules/recharts/types/cartesian/referencedot.d.ts","./node_modules/recharts/types/util/excludeeventprops.d.ts","./node_modules/recharts/types/util/svgpropertiesandevents.d.ts","./node_modules/recharts/types/cartesian/referencearea.d.ts","./node_modules/recharts/types/cartesian/barstack.d.ts","./node_modules/recharts/types/cartesian/xaxis.d.ts","./node_modules/recharts/types/cartesian/yaxis.d.ts","./node_modules/recharts/types/cartesian/zaxis.d.ts","./node_modules/recharts/types/chart/linechart.d.ts","./node_modules/recharts/types/chart/barchart.d.ts","./node_modules/recharts/types/chart/piechart.d.ts","./node_modules/recharts/types/chart/treemap.d.ts","./node_modules/recharts/types/chart/sankey.d.ts","./node_modules/recharts/types/chart/radarchart.d.ts","./node_modules/recharts/types/chart/scatterchart.d.ts","./node_modules/recharts/types/chart/areachart.d.ts","./node_modules/recharts/types/chart/radialbarchart.d.ts","./node_modules/recharts/types/chart/composedchart.d.ts","./node_modules/recharts/types/chart/sunburstchart.d.ts","./node_modules/recharts/types/shape/trapezoid.d.ts","./node_modules/recharts/types/cartesian/funnel.d.ts","./node_modules/recharts/types/chart/funnelchart.d.ts","./node_modules/recharts/types/util/global.d.ts","./node_modules/recharts/types/animation/animationhandle.d.ts","./node_modules/recharts/types/animation/timeoutcontroller.d.ts","./node_modules/recharts/types/animation/animationcontroller.d.ts","./node_modules/recharts/types/animation/useanimationcontroller.d.ts","./node_modules/recharts/types/zindex/defaultzindexes.d.ts","./node_modules/decimal.js-light/decimal.d.ts","./node_modules/recharts/types/util/scale/getnicetickvalues.d.ts","./node_modules/recharts/types/context/chartlayoutcontext.d.ts","./node_modules/recharts/types/util/getrelativecoordinate.d.ts","./node_modules/recharts/types/util/createcartesiancharts.d.ts","./node_modules/recharts/types/util/createpolarcharts.d.ts","./node_modules/recharts/types/util/datautils.d.ts","./node_modules/recharts/types/index.d.ts","./src/components/ui/chart.tsx","./src/components/shared/charts/chart_tooltip.tsx","./src/components/shared/charts/area_chart.tsx","./src/components/shared/charts/bar_chart.tsx","./src/components/shared/charts/chart_legend.tsx","./src/components/shared/charts/donut_chart.tsx","./src/components/shared/charts/line_chart.tsx","./src/components/shared/charts/index.ts","./src/components/team/memberformvalues.ts","./src/components/team/memberformvalues.test.ts","./src/components/team/tabvisibilityutils.ts","./src/components/team/tabvisibilityutils.test.ts","./src/components/team/teammodelaccess.test.ts","./src/components/team/usemyteammember.ts","./src/components/templates/estimatedoutputtokens.ts","./src/components/templates/estimatedoutputtokens.test.ts","./src/components/templates/keyeditfieldnormalizers.ts","./src/components/key_info_utils.tsx","./src/components/templates/keyeditformvalues.ts","./src/components/templates/keyeditformvalues.test.ts","./src/components/view_logs/constants.ts","./src/components/view_logs/logdetailrouting.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/columns.tsx","./src/components/view_logs/logdetailsdrawer/classifytag.tsx","./node_modules/moment/ts3.1-typings/moment.d.ts","./src/components/view_logs/logdetailsdrawer/sidebartoggle.tsx","./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/logdetailsdrawer/utils.test.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/usevisitedtabs.ts","./src/hooks/useworker.ts","./src/hooks/policies/usedeletepolicyattachment.ts","./src/lib/assetpaths.test.ts","./tests/mocks/autorouterpresets.ts","./src/lib/autorouter_presets.test.ts","./src/lib/cva.config.test.ts","./src/lib/logotreatments.test.ts","./src/lib/toast.test.ts","./src/lib/forms/pickdirty.ts","./src/lib/forms/pickdirty.test.ts","./src/lib/forms/urlvalidation.ts","./src/lib/forms/urlvalidation.test.ts","./src/lib/http/api.sameorigin.test.ts","./src/lib/http/api.test.ts","./src/lib/http/client.test.ts","./src/lib/http/resolveapibase.test.ts","./src/lib/http/runtime.test.ts","./src/lib/http/searchtooltypes.test-d.ts","./src/utils/budgetutils.ts","./src/utils/capabilities.test.ts","./src/utils/cookieutils.test.ts","./src/utils/datautils.test.ts","./src/utils/entitylinks.test.ts","./src/utils/errorpatterns.ts","./src/utils/errorutils.ts","./src/utils/errorutils.test.ts","./src/utils/jwtutils.test.ts","./node_modules/date-fns/constants.d.ts","./node_modules/date-fns/locale/types.d.ts","./node_modules/date-fns/fp/types.d.ts","./node_modules/date-fns/types.d.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.ts","./src/utils/keyexpiryutils.ts","./src/utils/keyexpiryutils.test.ts","./src/utils/keyupdateutils.ts","./src/utils/keyupdateutils.test.ts","./src/utils/licenseutils.test.ts","./src/utils/localstorageutils.test.ts","./src/utils/maskedsecretutils.ts","./src/utils/mcpheaderutils.ts","./src/utils/mcpheaderutils.test.ts","./src/utils/mcptokenstore.test.ts","./src/utils/mcptoolcrudclassification.test.ts","./src/utils/modelpermissions.test.ts","./src/utils/pkce.ts","./src/utils/promptcacheusage.test.ts","./src/utils/proxyutils.test.ts","./node_modules/dayjs/plugin/utc.d.ts","./src/utils/ptudatetime.ts","./src/utils/ptudatetime.test.ts","./src/utils/ptuvalidation.ts","./src/utils/ptumodelinfo.ts","./src/utils/ptumodelinfo.test.ts","./src/utils/ptuvalidation.test.ts","./src/utils/returnurlutils.test.ts","./src/utils/roles.test.ts","./src/utils/searchutils.ts","./src/utils/searchutils.test.ts","./src/utils/tabroutes.test.ts","./src/utils/teamutils.test.ts","./src/utils/textutils.test.ts","./src/utils/uihref.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","./tests/fieldorientation.ts","./node_modules/vitest/dist/environments.d.ts","./tests/jsdomfetchenv.ts","./scripts/lint-budget-lib.mjs","./tests/lint-budget-lib.test.ts","./tests/setup.unit.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","./scripts/eslint-rules/filename-pascal-case.mjs","./tests/eslint-rules/filename-pascal-case.test.ts","./scripts/eslint-rules/no-ad-hoc-z-index.mjs","./tests/eslint-rules/no-ad-hoc-z-index.test.ts","./scripts/eslint-rules/no-complex-jsx-arrow.mjs","./tests/eslint-rules/no-complex-jsx-arrow.test.ts","./scripts/eslint-rules/no-large-inline-object-arg.mjs","./tests/eslint-rules/no-large-inline-object-arg.test.ts","./scripts/eslint-rules/no-long-condition-chain.mjs","./tests/eslint-rules/no-long-condition-chain.test.ts","./scripts/eslint-rules/no-noop-hover-variant.mjs","./tests/eslint-rules/no-noop-hover-variant.test.ts","./tests/mocks/complexityscorerdefaults.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","./node_modules/nuqs/dist/adapters/next/app.d.ts","./src/contexts/authcontext.tsx","./src/contexts/reactqueryprovider.tsx","./src/components/ui/sonner.tsx","./src/app/layout.tsx","./src/components/ui/breadcrumb.tsx","./src/components/shared/toolbarseparator.tsx","./src/components/navbar/blogdropdown/blogdropdown.tsx","./src/components/navbar/docslink/docslink.tsx","./src/components/ui/button-group.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.tsx","./src/components/navbar/notificationsbell/notificationsbell.tsx","./src/contexts/pluginmodecontext.tsx","./src/components/navbar/viewswitcher.tsx","./src/components/themetoggle/themetoggle.tsx","./src/components/navbar/workerdropdown/workerdropdown.tsx","./src/components/dashboardheader.tsx","./src/components/navbar/userdropdown/userdropdown.tsx","./src/components/navbar.tsx","./src/components/common_components/loadingscreen.tsx","./src/app/(dashboard)/components/sidebarprovider.tsx","./src/components/debugwarningbanner.tsx","./src/components/norediswarningbanner.tsx","./src/components/licenseexpirybanner.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","./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/userbanner.tsx","./src/app/(dashboard)/layout.tsx","./src/app/(dashboard)/agentcontrolplaneview.test.tsx","./src/app/(dashboard)/layout.test.tsx","./src/components/shared/pageheader.tsx","./src/app/(dashboard)/hooks/useteams.tsx","./src/components/shared/badgelink.tsx","./src/components/ui/hover-card.tsx","./src/components/common_components/defaultproxyadmintag.tsx","./src/components/common_components/labeledfield.tsx","./src/components/templates/keyinfoheader.tsx","./src/components/shared/advanced_date_picker.tsx","./src/components/shared/summarycard.tsx","./src/components/shared/savingstiles.tsx","./src/components/templates/keysavingstab.tsx","./src/components/shared/paginatedmultiselect.tsx","./src/components/common_components/team_multi_select.tsx","./src/components/common_components/userdropdown.tsx","./src/app/(dashboard)/cost-optimization/_components/shadowevalstartform.tsx","./src/app/(dashboard)/cost-optimization/_components/shadowevalsection.tsx","./src/app/(dashboard)/cost-optimization/_components/tierturnschart.tsx","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarkstab.tsx","./src/components/templates/keyautorouterusagetab.tsx","./src/components/common_components/autorotationview.tsx","./src/components/common_components/deleteresourcemodal.tsx","./src/components/common_components/routersettingssummary.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/shared/errorutils.tsx","./src/components/guardrails/guardrailselector.tsx","./src/components/policies/policyselector.tsx","./src/components/templates/keyeditviewcontrols.tsx","./src/components/team/editloggingsettings.tsx","./src/components/templates/key_edit_view.tsx","./src/components/templates/key_info_view.tsx","./src/components/virtualkeyspage/keytablecolumns.tsx","./src/components/virtualkeyspage/virtualkeystable.tsx","./src/app/(dashboard)/api-keys/apikeysdashboard.tsx","./src/app/(dashboard)/page.tsx","./src/app/(dashboard)/page.test.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/access-group-create/accessgroupcreatedialog.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupstablecolumns.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupstable.tsx","./src/app/(dashboard)/access-groups/_components/accessgroupspage.tsx","./src/app/(dashboard)/access-groups/page.tsx","./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)/access-groups/_components/accessgroupsmodal/accessgroupeditmodal.integration.test.tsx","./src/app/(dashboard)/access-groups/_components/access-group-create/accessgroupcreatedialog.test.tsx","./src/components/constants.tsx","./src/components/scim.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.tsx","./src/components/shared/passwordinput.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/userbannersettings/userbannersettings.tsx","./src/components/settings/adminsettings/cyberark/cyberarkemptyplaceholder.tsx","./src/components/settings/adminsettings/cyberark/editcyberarkmodal.tsx","./src/components/settings/adminsettings/cyberark/cyberark.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/app/(dashboard)/admin-panel/_components/adminpanel.tsx","./src/app/(dashboard)/admin-panel/page.tsx","./src/app/(dashboard)/admin-panel/_components/adminpanel.test.tsx","./src/app/(dashboard)/agents/_components/agentformkit.tsx","./src/app/(dashboard)/agents/_components/cost_config_fields.tsx","./src/app/(dashboard)/agents/_components/agent_form_fields.tsx","./src/app/(dashboard)/agents/_components/agent_card_discovery.tsx","./src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.tsx","./src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx","./src/app/(dashboard)/agents/_components/agent_cost_view.tsx","./src/app/(dashboard)/agents/_components/agent_info.tsx","./src/app/(dashboard)/agents/_components/agentstablecolumns.tsx","./src/app/(dashboard)/agents/_components/agentstable.tsx","./src/app/(dashboard)/agents/_components/agentspanel.tsx","./src/app/(dashboard)/agents/page.tsx","./src/app/(dashboard)/agents/_components/agentspanel.test.tsx","./src/app/(dashboard)/agents/_components/agentstable.test.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.integration.test.tsx","./src/app/(dashboard)/agents/_components/add_agent_form.test.tsx","./src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx","./src/app/(dashboard)/agents/_components/agent_cost_view.test.tsx","./src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx","./src/app/(dashboard)/agents/_components/agent_info.test.tsx","./src/app/(dashboard)/agents/_components/agent_virtual_keys.test.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/_components/apireferenceview.tsx","./src/components/deprecationbanner.tsx","./src/app/(dashboard)/api-reference/page.tsx","./src/app/(dashboard)/api-reference/_components/apireferenceview.test.tsx","./src/app/(dashboard)/budgets/_components/budget_modal.tsx","./src/app/(dashboard)/budgets/_components/budgettablecolumns.tsx","./src/app/(dashboard)/budgets/_components/budgettable.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/budgettable.test.tsx","./src/app/(dashboard)/budgets/_components/budget_modal.integration.test.tsx","./src/app/(dashboard)/budgets/_components/budget_panel.test.tsx","./src/app/(dashboard)/budgets/_components/edit_budget_modal.integration.test.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/coordination_redis_settings/coordinationredisformfield.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredisfieldsection.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredistypeselector.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.tsx","./src/app/(dashboard)/caching/_components/errordrilldown.tsx","./src/app/(dashboard)/caching/_components/cache_dashboard.tsx","./src/app/(dashboard)/caching/page.tsx","./src/app/(dashboard)/caching/_components/errordrilldown.test.tsx","./src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx","./src/app/(dashboard)/caching/_components/cache_health.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/redistypeselector.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/index.integration.test.tsx","./src/app/(dashboard)/caching/_components/cache_settings/index.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/coordinationredistypeselector.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.integration.test.tsx","./src/app/(dashboard)/caching/_components/coordination_redis_settings/index.test.tsx","./src/components/shared/paginationstatusalerts.tsx","./src/app/(dashboard)/cost-optimization/_components/usagetab.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcompressiontab.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/molecules/models/providerlogo.tsx","./src/components/settings/routersettings/fallbacks/editfallbacks.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.tsx","./src/components/routing_groups/routinggroupusagepanel.tsx","./src/components/routing_groups/routinggroupstablecolumns.tsx","./src/components/routing_groups/routinggroupstable.tsx","./src/components/routing_groups/routinggroupmodal.tsx","./src/components/routing_groups/index.tsx","./src/app/(dashboard)/router-settings/_components/general_settings.tsx","./src/app/(dashboard)/cost-optimization/_components/cacheleakagecard.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcachingtab.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.tsx","./src/app/(dashboard)/cost-optimization/page.tsx","./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarkstab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/cacheleakagecard.test.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.activity.test.tsx","./src/app/(dashboard)/cost-optimization/_components/costoptimizationview.test.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcachingtab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/promptcompressiontab.integration.test.tsx","./src/app/(dashboard)/cost-optimization/_components/shadowevalsection.test.tsx","./src/app/(dashboard)/cost-optimization/_components/tierturnschart.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usagetab.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.integration.test.tsx","./src/app/(dashboard)/cost-optimization/_components/usedailyactivityrange.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.integration.test.tsx","./src/app/(dashboard)/cost-tracking/_components/add_provider_form.test.tsx","./src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.integration.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/app/(dashboard)/guardrails/_components/content_filter/patternmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/custompatternmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordmodal.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/patterntable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordtable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentcategoryconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/thresholdinput.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/competitorintentconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterconfiguration.tsx","./src/app/(dashboard)/guardrails/_components/guardrailformfield.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.tsx","./src/app/(dashboard)/guardrails/_components/llm_judge/llmjudgefields.tsx","./src/app/(dashboard)/guardrails/_components/pii_components.tsx","./src/app/(dashboard)/guardrails/_components/pii_configuration.tsx","./src/app/(dashboard)/guardrails/_components/tool_permission/toolpermissionruleseditor.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtablecolumns.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_table.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/categorytable.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterdisplay.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltermanager.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestresults.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestpanel.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestplayground.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_card.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_detail.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.tsx","./src/app/(dashboard)/guardrails/_components/guardrailspanel.tsx","./src/app/(dashboard)/guardrails/page.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestpanel.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestplayground.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailtestresults.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrailspanel.test.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.integration.test.tsx","./src/app/(dashboard)/guardrails/_components/teamguardrailstab.test.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.characterization.test.tsx","./src/app/(dashboard)/guardrails/_components/add_guardrail_form.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_card.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_garden_detail.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.characterization.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.test.tsx","./src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx","./src/app/(dashboard)/guardrails/_components/pii_components.test.tsx","./src/app/(dashboard)/guardrails/_components/pii_configuration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/competitorintentconfiguration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterconfiguration.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfilterdisplay.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltermanager.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/contentfiltertables.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/custompatternmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/keywordmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/patternmodal.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/tagsinput.test.tsx","./src/app/(dashboard)/guardrails/_components/content_filter/thresholdinput.test.tsx","./src/app/(dashboard)/guardrails/_components/custom_code/customcodemodal.test.tsx","./src/app/(dashboard)/guardrails/_components/tool_permission/toolpermissionruleseditor.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/evaluationsettingsmodal.tsx","./src/components/guardrailsmonitor/calcpopover.tsx","./src/components/guardrailsmonitor/metriccard.tsx","./src/components/guardrailsmonitor/unpricednote.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailusagebreakdown.tsx","./src/components/guardrailsmonitor/logviewer.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/components/shared/adminonlynotice.tsx","./src/app/(dashboard)/guardrails-monitor/page.tsx","./src/app/(dashboard)/guardrails-monitor/page.integration.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/evaluationsettingsmodal.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailconfig.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailconfig.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardraildetail.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailusagebreakdown.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailsmonitorview.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/guardrailsoverview.test.tsx","./src/app/(dashboard)/guardrails-monitor/_components/scorechart.test.tsx","./src/app/(dashboard)/hooks/usetabrouting.test.tsx","./src/app/(dashboard)/hooks/common/useresourcelist.test.tsx","./src/components/email_settings.tsx","./src/components/msteamssettings.tsx","./src/components/alerting/dynamic_form.tsx","./src/components/alerting/alerting_settings.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.tsx","./src/components/cloudzerocosttracking/cloudzeroformcontrols.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/loggingcallbackstablecolumns.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.tsx","./src/components/settings.tsx","./src/app/(dashboard)/logging-and-alerts/page.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystablecolumns.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.tsx","./src/components/deletedkeyspage/deletedkeyspage.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstablecolumns.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.tsx","./src/components/deletedteamspage/deletedteamspage.tsx","./src/components/view_logs/auditlogstablecolumns.tsx","./src/components/view_logs/auditlogstable.tsx","./src/components/view_logs/auditlogdrawer/auditlogdrawer.tsx","./src/components/view_logs/auditlogspanel.tsx","./src/components/view_logs/log_filter_logic.tsx","./src/components/view_logs/logs_utils.tsx","./src/components/view_logs/logstabletoolbar.tsx","./src/components/view_logs/requestlogsfilters.tsx","./src/components/view_logs/typebadges.tsx","./src/components/view_logs/requestlogstablecolumns.tsx","./src/components/view_logs/requestlogstable.tsx","./src/components/view_logs/requestlogspanel.tsx","./src/components/view_logs/index.tsx","./src/app/(dashboard)/logs/page.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpstandardssettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpsubmissionstab.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsettablecolumns.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsetstab.tsx","./src/app/(dashboard)/mcp-servers/_components/awssigv4fields.tsx","./src/app/(dashboard)/mcp-servers/_components/openapibyokfields.tsx","./src/app/(dashboard)/mcp-servers/_components/tokenendpointauthmethodfield.tsx","./src/app/(dashboard)/mcp-servers/_components/upstreamtokenheaderfield.tsx","./src/app/(dashboard)/mcp-servers/_components/oauthformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/truepassthroughwarning.tsx","./src/app/(dashboard)/mcp-servers/_components/dcrbridgetoggle.tsx","./src/app/(dashboard)/mcp-servers/_components/passthroughauthorizesection.tsx","./src/app/(dashboard)/mcp-servers/_components/tokenexchangeformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/idjagformfields.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_config.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connection_status.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tool_configuration.tsx","./src/app/(dashboard)/mcp-servers/_components/stdioconfiguration.tsx","./src/app/(dashboard)/mcp-servers/_components/mcppermissionmanagement.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiquickpicker.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiformsection.tsx","./src/app/(dashboard)/mcp-servers/_components/mcplogoselector.tsx","./src/app/(dashboard)/mcp-servers/_components/envvarssection.tsx","./src/hooks/usemcpoauthflow.tsx","./src/hooks/usetestmcpconnection.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.tsx","./src/app/(dashboard)/mcp-servers/_components/importmcpservers.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connect.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpservercard.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_display.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.tsx","./src/components/settings/adminsettings/mcptoolsearchsettings/mcptoolsearchsettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpnetworksettings.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_discovery.tsx","./src/components/mcp_tools/byokcredentialmodal.tsx","./src/app/(dashboard)/mcp-servers/_components/userenvvarsmodal.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx","./src/app/(dashboard)/mcp-servers/_components/toolargumentsform.tsx","./src/app/(dashboard)/mcp-servers/_components/tooltestpanel.tsx","./src/hooks/usetoolsoauthflow.tsx","./src/hooks/useusermcpoauthflow.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx","./src/app/(dashboard)/mcp-servers/_components/index.tsx","./src/app/(dashboard)/mcp-servers/page.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/createmcpserver.permissions.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/envvarssection.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcplogoselector.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpnetworksettings.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpformtestharness.tsx","./src/app/(dashboard)/mcp-servers/_components/mcppermissionmanagement.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpservercard.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpstandardssettings.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsettablecolumns.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcptoolsetstab.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/oauthformfields.test.tsx","./src/app/(dashboard)/mcp-servers/_components/openapiquickpicker.test.tsx","./src/app/(dashboard)/mcp-servers/_components/passthroughauthorizesection.test.tsx","./src/app/(dashboard)/mcp-servers/_components/tooltestpanel.test.tsx","./src/app/(dashboard)/mcp-servers/_components/truepassthroughwarning.test.tsx","./src/app/(dashboard)/mcp-servers/_components/userenvvarsmodal.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcpformstore.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connect.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_connection_status.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_discovery.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_config.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_cost_display.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.integration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_server_view.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_servers.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tool_configuration.test.tsx","./src/app/(dashboard)/mcp-servers/_components/mcp_tools.test.tsx","./src/app/(dashboard)/mcp-servers/_components/utils.test.tsx","./src/app/(dashboard)/memory/_components/memorydetaildrawer.tsx","./src/app/(dashboard)/memory/_components/memoryeditmodal.tsx","./src/app/(dashboard)/memory/_components/memorytablecolumns.tsx","./src/app/(dashboard)/memory/_components/memorytable.tsx","./src/app/(dashboard)/memory/_components/memoryview.tsx","./src/app/(dashboard)/memory/page.tsx","./src/app/(dashboard)/memory/page.integration.test.tsx","./src/app/(dashboard)/memory/_components/memorydetaildrawer.test.tsx","./src/app/(dashboard)/memory/_components/memoryeditmodal.integration.test.tsx","./src/app/(dashboard)/memory/_components/memorytable.test.tsx","./src/app/(dashboard)/memory/_components/memoryview.test.tsx","./src/components/aihub/agenthubtablecolumns.tsx","./src/components/aihub/forms/makeagentpublicform.tsx","./src/components/aihub/mcphubtablecolumns.tsx","./src/components/aihub/forms/makemcppublicform.tsx","./src/components/model_filters.tsx","./src/components/aihub/forms/makemodelpublicform.tsx","./src/components/aihub/modelhubtablecolumns.tsx","./src/components/common_components/iconactionbutton/baseactionbutton.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.tsx","./src/components/aihub/usefullinksmanagement.tsx","./src/components/aihub/skillhubtablecolumns.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/molecules/cost_optimization_feedback_banner.tsx","./src/components/add_model/auto_router_connection_test.tsx","./src/components/model_add/reuse_credentials.tsx","./src/components/update_model_credentials_modal.tsx","./src/components/shared/form/utcdatetimeinput.tsx","./src/components/add_model/cache_control_settings.tsx","./src/components/modelinfoeditform.tsx","./src/components/view_model/model_name_display.tsx","./src/components/model_info_view.tsx","./src/components/common_components/user_search_modal.tsx","./src/components/shared/form/labelwithhint.tsx","./src/components/team/guardrailsselect.tsx","./src/components/common_components/metadatakeyvaluefields.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/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelstablecolumns.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstable.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/allmodelspanel.tsx","./src/components/add_model/handle_add_auto_router_submit.tsx","./src/components/add_model/autorouterroutingtest.tsx","./src/components/add_model/add_auto_router_tab.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterstablecolumns.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterstable.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterspanel.tsx","./src/app/(dashboard)/models-and-endpoints/panels/autorouterstabpanel.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/handle_add_model_submit.tsx","./src/components/add_model/model_connection_test.tsx","./src/components/add_model/provider_specific_fields.tsx","./src/components/add_model/add_model_modes.tsx","./src/components/add_model/addmodelform.tsx","./src/app/(dashboard)/models-and-endpoints/panels/addmodelpanel.tsx","./src/components/model_add/credentialmodal.tsx","./src/components/model_add/credentialstablecolumns.tsx","./src/components/model_add/credentialstable.tsx","./src/components/model_add/credentialspanel.tsx","./src/app/(dashboard)/models-and-endpoints/panels/llmcredentialspanel.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/passthroughsettings/passthroughendpointstablecolumns.tsx","./src/components/passthroughsettings/passthroughendpointstable.tsx","./src/components/passthroughsettings/passthroughsettings.tsx","./src/app/(dashboard)/models-and-endpoints/panels/passthroughpanel.tsx","./src/components/model_dashboard/healthcheckstablecolumns.tsx","./src/components/model_dashboard/healthcheckstable.tsx","./src/components/model_dashboard/healthcheckcomponent.tsx","./src/app/(dashboard)/models-and-endpoints/panels/healthstatuspanel.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/modelretrysettingspanel.tsx","./src/components/model_group_alias_settings.tsx","./src/app/(dashboard)/models-and-endpoints/panels/modelgroupaliaspanel.tsx","./src/app/(dashboard)/models-and-endpoints/components/accessgroupbudgetmodal.tsx","./src/app/(dashboard)/models-and-endpoints/components/accessgroupbudgetcolumns.tsx","./src/app/(dashboard)/models-and-endpoints/panels/accessgroupbudgetspanel.tsx","./src/components/price_data_reload.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.tsx","./src/app/(dashboard)/models-and-endpoints/panels/pricedatapanel.tsx","./src/app/(dashboard)/models-and-endpoints/page.tsx","./src/app/(dashboard)/models-and-endpoints/page.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstable.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/autorouters/autorouterspanel.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/accessgroupbudgetspanel.integration.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/addmodelpanel.integration.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/autorouterstabpanel.test.tsx","./src/app/(dashboard)/models-and-endpoints/panels/healthstatuspanel.test.tsx","./src/components/view_user_spend.tsx","./src/components/usagepage/components/entityusage/topkeyview.tsx","./src/app/(dashboard)/old-usage/_components/usage.tsx","./src/app/(dashboard)/old-usage/page.tsx","./src/app/(dashboard)/old-usage/_components/usage.test.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/org-settings/orgsettingsform.tsx","./src/components/organization/org-create/orgcreatedialog.tsx","./src/components/organization/organization_view.tsx","./src/app/(dashboard)/organizations/_components/organizationstablecolumns.tsx","./src/app/(dashboard)/organizations/_components/organizationstable.tsx","./src/app/(dashboard)/organizations/_components/organizationspanel.tsx","./src/app/(dashboard)/organizations/page.tsx","./src/app/(dashboard)/organizations/_components/organizationspanel.test.tsx","./src/app/(dashboard)/organizations/_components/organizationstable.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/additionalmodelsettings.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatcomposer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.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/endpointutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.tsx","./src/app/(dashboard)/playground/components/chat_ui/a2ametrics.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.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/page.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/agentbuilderview.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatcomposer.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.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/realtimeplayground.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/anthropic_messages.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)/policies/_components/policytablecolumns.tsx","./src/app/(dashboard)/policies/_components/policytable.tsx","./src/app/(dashboard)/policies/_components/pipeline_flow_builder.tsx","./src/app/(dashboard)/policies/_components/policy_info.tsx","./src/app/(dashboard)/policies/_components/add_policy_form.tsx","./src/app/(dashboard)/policies/_components/impact_popover.tsx","./src/app/(dashboard)/policies/_components/attachmenttablecolumns.tsx","./src/app/(dashboard)/policies/_components/attachmenttable.tsx","./src/app/(dashboard)/policies/_components/impact_preview_alert.tsx","./src/app/(dashboard)/policies/_components/tokenselect.tsx","./src/app/(dashboard)/policies/_components/add_attachment_form.tsx","./src/app/(dashboard)/policies/_components/policy_test_panel.tsx","./src/app/(dashboard)/policies/_components/policy_templates.tsx","./src/app/(dashboard)/policies/_components/guardrail_selection_modal.tsx","./src/app/(dashboard)/policies/_components/template_parameter_modal.tsx","./src/app/(dashboard)/policies/_components/ai_suggestion_modal.tsx","./src/app/(dashboard)/policies/_components/index.tsx","./src/app/(dashboard)/policies/page.tsx","./src/app/(dashboard)/policies/_components/attachmenttable.test.tsx","./src/app/(dashboard)/policies/_components/policytable.test.tsx","./src/app/(dashboard)/policies/_components/add_attachment_form.test.tsx","./src/app/(dashboard)/policies/_components/add_policy_form.test.tsx","./src/app/(dashboard)/policies/_components/ai_suggestion_modal.test.tsx","./src/app/(dashboard)/policies/_components/guardrail_selection_modal.test.tsx","./src/app/(dashboard)/policies/_components/impact_popover.test.tsx","./src/app/(dashboard)/policies/_components/impact_preview_alert.test.tsx","./src/app/(dashboard)/policies/_components/index.test.tsx","./src/app/(dashboard)/policies/_components/pipeline_flow_builder.test.tsx","./src/app/(dashboard)/policies/_components/policy_info.test.tsx","./src/app/(dashboard)/policies/_components/policy_templates.test.tsx","./src/app/(dashboard)/policies/_components/policy_test_panel.test.tsx","./src/app/(dashboard)/policies/_components/template_parameter_modal.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.tsx","./src/app/(dashboard)/projects/_components/projectmodals/editprojectmodal.tsx","./src/app/(dashboard)/projects/_components/projectkeystablecolumns.tsx","./src/app/(dashboard)/projects/_components/projectkeystable.tsx","./src/app/(dashboard)/projects/_components/projectkeyssection.tsx","./src/app/(dashboard)/projects/_components/projectdetailspage.tsx","./src/app/(dashboard)/projects/_components/projectstablecolumns.tsx","./src/app/(dashboard)/projects/_components/projectstable.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/projectkeyssection.test.tsx","./src/app/(dashboard)/projects/_components/projectkeystable.test.tsx","./src/app/(dashboard)/projects/_components/projectspage.test.tsx","./src/app/(dashboard)/projects/_components/projectstable.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.integration.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/createprojectmodal.test.tsx","./src/app/(dashboard)/projects/_components/projectmodals/editprojectmodal.integration.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/prompttablecolumns.tsx","./src/app/(dashboard)/prompts/_components/prompttable.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/prompttable.test.tsx","./src/app/(dashboard)/prompts/_components/add_prompt_form.integration.test.tsx","./src/app/(dashboard)/prompts/_components/index.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_info.test.tsx","./src/app/(dashboard)/prompts/_components/tool_modal.test.tsx","./src/app/(dashboard)/prompts/_components/variable_textarea.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/developermessagecard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/modelconfigcard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/promptcodesnippets.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/prompteditorheader.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/promptmessagescard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/publishmodal.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/toolscard.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/versionhistorysidepanel.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/emptystate.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messagebubble.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messageinput.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/messagelist.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/variableinput.test.tsx","./src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.test.tsx","./src/app/(dashboard)/router-settings/page.tsx","./src/app/(dashboard)/router-settings/_components/general_settings.test.tsx","./src/app/(dashboard)/search-tools/_components/searchconnectiontest.tsx","./src/app/(dashboard)/search-tools/_components/createsearchtools.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltablecolumns.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltable.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/createsearchtools.integration.test.tsx","./src/app/(dashboard)/search-tools/_components/createsearchtools.test.tsx","./src/app/(dashboard)/search-tools/_components/searchconnectiontest.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltable.test.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.integration.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtools.test.tsx","./src/app/(dashboard)/skills/_components/add_plugin_form.tsx","./src/app/(dashboard)/skills/_components/plugintablecolumns.tsx","./src/app/(dashboard)/skills/_components/plugintable.tsx","./src/app/(dashboard)/skills/_components/claudecodepluginspanel.tsx","./src/app/(dashboard)/skills/page.tsx","./src/app/(dashboard)/skills/_components/claudecodepluginspanel.test.tsx","./src/app/(dashboard)/skills/_components/plugintable.test.tsx","./src/app/(dashboard)/skills/_components/add_plugin_form.test.tsx","./src/app/(dashboard)/tag-management/_components/tag_info.tsx","./src/app/(dashboard)/tag-management/_components/tagtablecolumns.tsx","./src/app/(dashboard)/tag-management/_components/tagtable.tsx","./src/app/(dashboard)/tag-management/_components/components/createtagmodal.tsx","./src/app/(dashboard)/tag-management/_components/index.tsx","./src/app/(dashboard)/tag-management/page.tsx","./src/app/(dashboard)/tag-management/_components/tagtable.test.tsx","./src/app/(dashboard)/tag-management/_components/index.test.tsx","./src/app/(dashboard)/tag-management/_components/tag_info.integration.test.tsx","./src/app/(dashboard)/tag-management/_components/components/createtagmodal.test.tsx","./src/components/team/availableteamstablecolumns.tsx","./src/components/team/availableteamstable.tsx","./src/components/team/availableteamspanel.tsx","./src/components/teamssosettings.tsx","./src/components/teamspage/teamtablecolumns.tsx","./src/components/teamspage/teamstable.tsx","./src/components/teams.tsx","./src/app/(dashboard)/teams/page.tsx","./src/components/toolpolicies/policyselect.tsx","./src/components/tooldetail.tsx","./src/components/toolpolicies/toolpoliciestablecolumns.tsx","./src/components/toolpolicies/toolpoliciestable.tsx","./src/components/toolpolicies/toolpoliciespanel.tsx","./src/components/toolpoliciesview.tsx","./src/app/(dashboard)/tool-policies/page.tsx","./src/app/(dashboard)/transform-request/transformrequestpanel.tsx","./src/app/(dashboard)/transform-request/transformrequestpanel.test.tsx","./src/app/(dashboard)/transform-request/page.tsx","./src/app/(dashboard)/ui-theme/uithemesettings.tsx","./src/app/(dashboard)/ui-theme/uithemesettings.test.tsx","./src/app/(dashboard)/ui-theme/page.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/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagebarchart.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagelinechart.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagetable.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/endpointusage.tsx","./src/app/(dashboard)/usage/_components/components/modelviewtoggle.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/topmodelview.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/teamuserspendcard.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/entityusage.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/spendbyprovider.tsx","./src/app/(dashboard)/usage/_components/components/usageaichatpanel.tsx","./src/app/(dashboard)/usage/_components/components/usageviewselect/usageviewselect.tsx","./src/app/(dashboard)/usage/_components/components/usagepageview.tsx","./src/app/(dashboard)/usage/page.tsx","./src/app/(dashboard)/usage/_components/components/usageaichatpanel.test.tsx","./src/app/(dashboard)/usage/_components/components/usagepageview.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/endpointusage.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagebarchart.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagelinechart.test.tsx","./src/app/(dashboard)/usage/_components/components/endpointusage/components/endpointusagetable.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/entityusage.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/spendbyprovider.test.tsx","./src/app/(dashboard)/usage/_components/components/entityusage/topmodelview.test.tsx","./src/app/(dashboard)/usage/_components/components/usageviewselect/usageviewselect.test.tsx","./src/app/(dashboard)/users/_components/user_edit_view.tsx","./src/app/(dashboard)/users/_components/bulkeditusers.tsx","./src/components/bulk_create_users_button.tsx","./src/app/(dashboard)/users/_components/default-user-settings/defaultusersettingsform.tsx","./src/app/(dashboard)/users/_components/view_users/userstablecolumns.tsx","./src/app/(dashboard)/users/_components/view_users/userstable.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.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/user_edit_view.test.tsx","./src/app/(dashboard)/users/_components/view_users.test.tsx","./src/app/(dashboard)/users/_components/default-user-settings/defaultusersettingsform.test.tsx","./src/app/(dashboard)/users/_components/view_users/userstable.test.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.integration.test.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.test.tsx","./src/components/vector_store_providers.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretable.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretester.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.tsx","./src/app/(dashboard)/vector-stores/_components/documentstablecolumns.tsx","./src/app/(dashboard)/vector-stores/_components/documentstable.tsx","./src/app/(dashboard)/vector-stores/_components/s3vectorsconfig.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.tsx","./src/app/(dashboard)/vector-stores/_components/testvectorstoretab.tsx","./src/app/(dashboard)/vector-stores/_components/index.tsx","./src/app/(dashboard)/vector-stores/page.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.characterization.test.tsx","./src/app/(dashboard)/vector-stores/_components/createvectorstore.test.tsx","./src/app/(dashboard)/vector-stores/_components/documentstable.test.tsx","./src/app/(dashboard)/vector-stores/_components/indexestable.test.tsx","./src/app/(dashboard)/vector-stores/_components/s3vectorsconfig.test.tsx","./src/app/(dashboard)/vector-stores/_components/testvectorstoretab.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.integration.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoreform.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretable.test.tsx","./src/app/(dashboard)/vector-stores/_components/vectorstoretester.test.tsx","./src/app/(dashboard)/vector-stores/_components/index.test.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.integration.test.tsx","./src/app/(dashboard)/vector-stores/_components/vector_store_info.test.tsx","./src/app/(dashboard)/workflows/workflowruns.tsx","./src/app/(dashboard)/workflows/workflowruns.test.tsx","./src/app/(dashboard)/workflows/page.tsx","./src/app/(dashboard)/workflows/page.integration.test.tsx","./src/app/chat/layout.tsx","./src/app/chat/layout.test.tsx","./src/components/chat/chatmessages.tsx","./src/components/chat/mcpconnectpicker.tsx","./src/app/chat/page.tsx","./src/app/chat/page.integration.test.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/chat/mcpappspanel.tsx","./src/components/chat/connectflowbanner.tsx","./src/components/chat/connectflowsurface.tsx","./src/app/chat/integrations/page.tsx","./src/components/chat/logspanel.tsx","./src/app/chat/logs/page.tsx","./src/components/chat/usagepanel.tsx","./src/app/chat/usage/page.tsx","./src/app/connect/layout.tsx","./src/app/connect/layout.test.tsx","./src/app/connect/page.tsx","./src/app/connect/page.test.tsx","./src/app/login/loginpage.tsx","./src/app/login/loginpage.integration.test.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.tsx","./src/app/onboarding/onboardingerrorview.test.tsx","./src/app/onboarding/onboardingloadingview.tsx","./src/app/onboarding/onboardingformbody.tsx","./src/app/onboarding/onboardingform.tsx","./src/app/onboarding/onboardingform.test.tsx","./src/app/onboarding/onboardingformbody.integration.test.tsx","./src/app/onboarding/onboardingformbody.test.tsx","./src/app/onboarding/onboardingloadingview.test.tsx","./src/app/onboarding/page.tsx","./src/components/betabadge.test.tsx","./src/components/createuserbutton.test.tsx","./src/components/dashboardheader.test.tsx","./src/components/debugwarningbanner.test.tsx","./src/components/deprecationbanner.test.tsx","./src/components/guardrailsettingsview.test.tsx","./src/components/helplink.test.tsx","./src/components/licenseexpirybanner.test.tsx","./src/components/norediswarningbanner.test.tsx","./src/components/scim.test.tsx","./src/components/ssomodals.test.tsx","./src/components/sidebarusagecard.test.tsx","./src/components/teamssosettings.test.tsx","./src/components/teams.test.tsx","./src/components/tooldetail.test.tsx","./src/components/toolpoliciesview.test.tsx","./src/components/uiaccesscontrolform.integration.test.tsx","./src/components/uiaccesscontrolform.unit.test.tsx","./src/components/userbanner.test.tsx","./src/components/activity_metrics.test.tsx","./src/components/add_pass_through.integration.test.tsx","./src/components/bulk_create_users_button.test.tsx","./src/components/cloudzero_export_modal.integration.test.tsx","./src/components/email_settings.test.tsx","./src/components/key_info_utils.test.tsx","./src/components/key_value_input.test.tsx","./src/components/leftnav.test.tsx","./src/components/logging_settings_view.test.tsx","./src/components/model_info_view.test.tsx","./src/components/navbar.test.tsx","./src/components/onboarding_link.test.tsx","./src/components/pass_through_info.integration.test.tsx","./src/components/per_user_usage.test.tsx","./src/components/price_data_reload.test.tsx","./src/components/provider_info_helpers.test.tsx","./src/components/public_model_hub.test.tsx","./src/components/query_param_input.test.tsx","./src/components/route_preview.test.tsx","./src/components/settings.test.tsx","./src/components/update_model_credentials_modal.test.tsx","./src/components/user_agent_activity.test.tsx","./src/components/vector_store_providers.test.tsx","./src/components/aihub/agenthubtablecolumns.test.tsx","./src/components/aihub/mcphubtablecolumns.test.tsx","./src/components/aihub/modelhubtable.test.tsx","./src/components/aihub/modelhubtablecolumns.test.tsx","./src/components/aihub/skillhubtablecolumns.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.integration.test.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.test.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.test.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.test.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.integration.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/modelselect/modelselect.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/docslink/docslink.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/passthroughsettings/passthroughendpointstable.test.tsx","./src/components/passthroughsettings/passthroughsettings.test.tsx","./src/components/settings/adminsettings/cyberark/cyberark.test.tsx","./src/components/settings/adminsettings/cyberark/editcyberarkmodal.test.tsx","./src/components/settings/adminsettings/hashicorpvault/edithashicorpvaultmodal.test.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvault.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/mcptoolsearchsettings/mcptoolsearchsettings.test.tsx","./src/components/settings/adminsettings/pluginsettings/pluginsettings.integration.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.integration.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/adminsettings/userbannersettings/userbannersettings.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/editfallbacks.test.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.test.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.test.tsx","./src/components/sidebaraccountmenu/sidebaraccountmenu.test.tsx","./src/components/teamspage/teamstable.test.tsx","./src/components/themetoggle/themetoggle.test.tsx","./src/components/toolpolicies/policyselect.test.tsx","./src/components/toolpolicies/toolpoliciespanel.test.tsx","./src/components/toolpolicies/toolpoliciestable.test.tsx","./src/components/toolpolicies/toolpoliciestablecolumns.test.tsx","./src/components/usagepage/components/keymodelusageview.test.tsx","./src/components/usagepage/components/entityusage/topkeyview.test.tsx","./src/components/virtualkeyspage/virtualkeystable.test.tsx","./src/components/add_model/addmodelform.test.tsx","./src/components/add_model/autorouterroutingtest.test.tsx","./src/components/add_model/classifierprompteditor.integration.test.tsx","./src/components/add_model/complexityrouterconfig.test.tsx","./src/components/add_model/heuristicscoringconfig.test.tsx","./src/components/add_model/openingprompteditor.test.tsx","./src/components/add_model/routerconfigbuilder.test.tsx","./src/components/add_model/semantickeywordmatching.test.tsx","./src/components/add_model/stallescalationconfig.test.tsx","./src/components/add_model/tiermodeleffortrows.test.tsx","./src/components/add_model/add_auto_router_tab.test.tsx","./tests/mounted-form-host.tsx","./src/components/add_model/advanced_settings.test.tsx","./src/components/add_model/auto_router_connection_test.test.tsx","./src/components/add_model/cache_control_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/model_connection_test.test.tsx","./src/components/add_model/provider_specific_fields.test.tsx","./src/components/agent_management/agentselector.test.tsx","./src/components/alerting/dynamic_form.integration.test.tsx","./src/components/chat/chatshell.test.tsx","./src/components/chat/connectflowbanner.test.tsx","./src/components/chat/connectflowsurface.test.tsx","./src/components/chat/logspanel.test.tsx","./src/components/chat/mcpappspanel.test.tsx","./src/components/chat/mcpconnectpicker.test.tsx","./src/components/chat_ui/codesnippets.test.tsx","./src/components/chat_ui/reasoningcontent.test.tsx","./src/components/chat_ui/responsemetrics.test.tsx","./src/components/common_components/defaultproxyadmintag.test.tsx","./src/components/common_components/deleteresourcemodal.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/metadatakeyvaluefields.test.tsx","./src/components/common_components/modelaliasmanager.test.tsx","./src/components/common_components/modelselector.test.tsx","./src/components/common_components/mountedformfield.test.tsx","./src/components/common_components/newbadge.tsx","./src/components/common_components/newbadge.test.tsx","./src/components/common_components/organizationdropdown.test.tsx","./src/components/common_components/passthroughguardrailssection.test.tsx","./src/components/common_components/premiumloggingsettings.test.tsx","./src/components/common_components/ratelimittypeformitem.test.tsx","./src/components/common_components/routersettingsaccordion.test.tsx","./src/components/common_components/routersettingssummary.test.tsx","./src/components/common_components/userdropdown.test.tsx","./src/components/common_components/routersettingswiring.test.tsx","./src/components/common_components/team_dropdown.test.tsx","./src/components/common_components/team_multi_select.test.tsx","./src/components/common_components/user_search_modal.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/email_events/email_event_settings.test.tsx","./src/components/guardrails/guardrailselector.test.tsx","./src/components/key_team_helpers/budgetfallbackseditor.test.tsx","./src/components/key_team_helpers/modelmaxbudgeteditor.integration.test.tsx","./src/components/key_team_helpers/tagratelimiteditor.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/fetch_models.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/byokcredentialmodal.test.tsx","./src/components/mcp_tools/mcptoolargumentsform.test.tsx","./src/components/mcp_tools/types.test.tsx","./src/components/model_add/credentialmodal.test.tsx","./src/components/model_add/credentialspanel.test.tsx","./src/components/model_add/credentialstable.test.tsx","./src/components/model_add/reuse_credentials.test.tsx","./src/components/model_dashboard/healthcheckcomponent.test.tsx","./src/components/model_dashboard/healthcheckstable.test.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.test.tsx","./src/components/molecules/cost_optimization_feedback_banner.test.tsx","./src/components/molecules/logo/logo.test.tsx","./src/components/molecules/models/providerlogo.test.tsx","./src/components/organisms/regeneratekeymodal.integration.test.tsx","./src/components/organisms/regeneratekeymodal.test.tsx","./src/components/organisms/create_key_button.integration.test.tsx","./src/components/organisms/create_key_button.test.tsx","./src/components/organization/organization_view.test.tsx","./src/components/organization/org-create/orgcreatedialog.test.tsx","./src/components/organization/org-settings/orgsettingsform.test.tsx","./src/components/permissions/agentpermissions.test.tsx","./src/components/permissions/mcpserverpermissions.test.tsx","./src/components/policies/policyselector.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/routing_groups/routinggroupmodal.test.tsx","./src/components/routing_groups/routinggroupstable.test.tsx","./src/components/routing_groups/index.integration.test.tsx","./src/components/search_tools/searchtoolselector.test.tsx","./src/components/shared/alert.test.tsx","./src/components/shared/badgelink.test.tsx","./src/components/shared/copybutton.test.tsx","./src/components/shared/createdkeydisplay.test.tsx","./src/components/shared/entitylink.test.tsx","./src/components/shared/inheritedbudgethint.test.tsx","./src/components/shared/meter.test.tsx","./src/components/shared/multiselect.test.tsx","./src/components/shared/pageheader.test.tsx","./src/components/shared/paginatedmultiselect.test.tsx","./src/components/shared/paginatedsearchselect.test.tsx","./src/components/shared/paginationstatusalerts.test.tsx","./src/components/shared/searchselect.test.tsx","./src/components/shared/sidebar.test.tsx","./src/components/shared/toolbarseparator.test.tsx","./src/components/shared/advanced_date_picker.test.tsx","./src/components/shared/chart_loader.test.tsx","./src/components/shared/datatable/datatable.test-d.tsx","./src/components/shared/datatable/datatable.test.tsx","./src/components/shared/datatable/datatablefilterdrawer.test.tsx","./src/components/shared/datatable/datatablepagination.test.tsx","./src/components/shared/datatable/datatablerowselection.test.tsx","./src/components/shared/datatable/datatablesortheader.test.tsx","./src/components/shared/datatable/datatabletoolbar.test.tsx","./src/components/shared/charts/area_chart.test.tsx","./src/components/shared/charts/bar_chart.test.tsx","./src/components/shared/charts/chart_legend.test.tsx","./src/components/shared/charts/chart_tooltip.test.tsx","./src/components/shared/charts/donut_chart.test.tsx","./src/components/shared/charts/line_chart.test.tsx","./src/components/shared/form/formfield.test.tsx","./src/components/shared/table_cells/autoroutertag.test.tsx","./src/components/shared/table_cells/date_cell.test.tsx","./src/components/shared/table_cells/id_cell.test.tsx","./src/components/shared/table_cells/identity_cell.test.tsx","./src/components/shared/table_cells/models_cell.test.tsx","./src/components/shared/table_cells/money_cell.test.tsx","./src/components/shared/table_cells/spend_budget_cell.test.tsx","./src/components/shared/table_cells/status_badge.test.tsx","./src/components/tag_management/tagselector.test.tsx","./src/components/team/availableteamspanel.test.tsx","./src/components/team/editmembership.integration.test.tsx","./src/components/team/editmembership.test.tsx","./src/components/team/loggingsettings.test.tsx","./src/components/team/myusertab.test.tsx","./src/components/team/teaminfo.test.tsx","./src/components/team/teammembertab.test.tsx","./src/components/team/teamvirtualkeystable.test.tsx","./src/components/team/member_permissions.test.tsx","./src/components/team/permission_definitions.test.tsx","./src/components/templates/keyautorouterusagetab.integration.test.tsx","./src/components/templates/keyinfoheader.test.tsx","./src/components/templates/keyinfoview.handlekeyupdate.test.tsx","./src/components/templates/keysavingstab.integration.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/alert-dialog.test.tsx","./src/components/ui/avatar.test.tsx","./src/components/ui/badge.test.tsx","./src/components/ui/breadcrumb.test.tsx","./src/components/ui/button.test.tsx","./src/components/ui/chart.test.tsx","./src/components/ui/field.test.tsx","./src/components/ui/ref-forwarding.test.tsx","./src/components/ui/select.test.tsx","./src/components/ui/tooltip.test.tsx","./src/components/ui/ui-loading-spinner.test.tsx","./src/components/vector_store_management/vectorstoreselector.test.tsx","./src/components/view_logs/auditlogspanel.test.tsx","./src/components/view_logs/auditlogstable.test.tsx","./src/components/view_logs/configinfomessage.test.tsx","./src/components/view_logs/costbreakdownviewer.test.tsx","./src/components/view_logs/requestlogsfilters.test.tsx","./src/components/view_logs/requestlogspanel.test.tsx","./src/components/view_logs/requestlogstablecolumns.test.tsx","./src/components/view_logs/typebadges.test.tsx","./src/components/view_logs/index.integration.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/auditlogdrawer/auditlogdrawer.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/classifytag.test.tsx","./src/components/view_logs/logdetailsdrawer/collapsiblemessage.test.tsx","./src/components/view_logs/logdetailsdrawer/drawerheader.test.tsx","./src/components/view_logs/logdetailsdrawer/historytree.test.tsx","./src/components/view_logs/logdetailsdrawer/inputcard.test.tsx","./src/components/view_logs/logdetailsdrawer/jsonviewer.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailsdrawer.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/routingdecisioncard.test.tsx","./src/components/view_logs/logdetailsdrawer/sectionheader.test.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.test.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.test.tsx","./src/components/view_logs/logdetailsdrawer/tokenflow.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/usesyntaxtheme.test.tsx","./src/hooks/usetoolsoauthflow.test.tsx","./src/hooks/policies/usedeletepolicyattachment.test.tsx","./src/lib/forms/usezodform.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/@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-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/use-sync-external-store/index.d.ts"],"fileIdsList":[[97,143,484,485,486,487],[97,143],[97,143,226,528,531,2703,2811,2852,2862,2894,2908,2919,2923,2930,2947,3050,3062,3105,3147,3172,3192,3239,3275,3298,3373,3387,3400,3528,3570,3594,3631,3652,3662,3675,3684,3696,3703,3706,3709,3728,3748,3768,3784,3786,3790,3793,3795,3799,3801,3803,3804,3806,3811,3812,3813,3814,3824],[97,143,529,530,531],[97,143,3425,3429,3430,3433,3434,3436,3438,3439,3442,3461,3486,3487,3488,3489],[97,143,3429,3437,3490],[97,143,3435],[97,143,3433,3437,3438,3490],[97,143,3490],[97,143,3431,3490],[97,143,3440,3441],[97,143,3436],[97,143,3436,3438,3439,3442,3459,3490],[97,143,3453],[97,143,3433,3439,3490],[97,143,3425,3429,3430,3432],[97,143,176],[97,143,3425],[97,138,143,3428],[97,143,3425,3433,3490],[97,143,3433,3490],[97,143,3485,3490],[97,143,3433,3455,3463,3485,3490],[97,143,3433,3455,3458,3459,3490],[97,143,3461,3490],[97,143,3479],[97,143,3433,3464,3479,3480,3482,3491],[97,143,3481],[97,143,3489],[97,143,3478],[97,143,3433,3438,3439,3443,3448,3486],[97,143,3448,3449],[97,143,3433,3439,3443,3449,3486],[97,143,3443,3444,3445,3446,3447,3449,3452,3469,3473,3476,3485],[97,143,3433,3438,3439,3443,3486],[97,143,3433,3438,3439,3442,3443,3486],[97,143,3444,3445,3446,3447,3465,3466,3467,3471,3474,3477,3486],[97,143,3450,3451,3452],[97,143,3433,3438,3439,3443,3450,3451,3486],[97,143,3433,3438,3439,3443,3450,3486],[97,143,3433,3438,3439,3443,3454,3461,3485,3486],[97,143,3462,3485],[97,143,3432,3433,3438,3443,3461,3462,3463,3464,3483,3484,3485,3486],[97,143,3432,3433,3438,3439,3443,3486],[97,143,3468,3469,3470],[97,143,3433,3438,3439,3443,3469,3486],[97,143,3433,3438,3439,3443,3449,3468,3470,3486],[97,143,3472,3473],[97,143,3433,3438,3439,3442,3443,3472,3486],[97,143,3475,3476],[97,143,3433,3438,3439,3443,3475,3486],[97,143,3432,3433,3438,3443,3461,3486,3487],[97,143,3435,3461,3486,3487,3488],[97,143,3457],[97,143,3433,3435,3438,3439,3443,3454,3461],[97,143,3456,3461],[97,143,3432,3433,3438,3443,3456,3459,3460,3461],[85,97,143,645,650],[97,143,646,650,651,652,653,654],[97,143,646,650,651,652,653],[85,97,143,642,643,645,646,649],[85,97,143,645,646,647,650],[85,97,143,642,643,645],[97,143,704,705],[97,143,708,709,710,711,712,713,714,716,717,718],[97,143,707,708,709,710,711,712,713,714,716,717],[85,97,143,226,643,706,707],[85,97,143,707,715],[97,143,722,723,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,749,751],[97,143,722,723,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,749,750],[85,97,143,645,727,728],[85,97,143,645],[85,97,143,721],[85,97,143],[85,97,143,645,753],[85,97,143,645,647,753],[97,143,753,754,755,756],[97,143,753,754,755],[97,143,758],[85,97,143,642,643,645,727],[97,143,764],[97,143,760,761,762],[97,143,760,761],[85,97,143,645,647,760],[97,143,648,766,767,768],[97,143,648,766,767],[85,97,143,645,647,648],[85,97,143,642,643,645,649],[85,97,143,647,648],[85,97,143,645,648],[85,97,143,645,728],[85,97,143,645,647],[97,143,730,732,733,734,735,736,737,738,739,740,741,742,744,745,746,749,770,771,772,773,774,775,776,777,778,779,781],[97,143,730,732,733,734,735,736,737,738,739,740,741,742,744,745,746,749,750,770,771,772,773,774,775,776,777,778,779,780],[85,97,143,645,727],[85,97,143,645,647,665,728],[85,97,143,699],[85,97,143,642,643,720],[97,143,748],[97,143,783,784,791,792,793,794,795,796,797,798,799,800,801,802,804,807,810,811,812],[97,143,747,783,784,791,792,793,794,795,796,797,798,799,800,801,802,804,807,810,811],[97,143,226,644,790,809],[85,97,143,810],[85,97,143,226],[97,143,814,815],[97,143,814],[97,143,706,709,710,711,712,713,714,715,717,817],[97,143,705,706,709,710,711,712,713,714,715,717],[85,97,143,645,647,665],[85,97,143,226,642,643,703,705],[97,143,704],[85,97,143,647,664,665,673,699,703,706,1029],[85,97,143,645,705],[85,97,143,819],[97,143,820,821],[97,143,819,820],[97,143,823,824,825,826,827,828,830,832,833,834,835,836,837,838,839,840],[97,143,705,823,824,825,826,827,828,830,832,833,834,835,836,837,838,839],[85,97,143,645,647,665,831],[85,97,143,226,642,643,703,705,831],[85,97,143,829,830],[85,97,143,645,829,831],[85,97,143,645,647,727],[97,143,727,842,843,844,845,846,847,848],[97,143,727,842,843,844,845,846,847],[85,97,143,645,726],[85,97,143,647,727],[97,143,850,851,852],[97,143,850,851],[85,97,143,694],[85,97,143,665,672,694],[85,97,143,645,676],[85,97,143,643,647,664,694,703],[85,97,143,672,694],[97,143,694],[85,97,143,687],[97,143,642,694],[97,143,672,694],[97,143,643,673,694],[97,143,683,694],[85,97,143,645,672,683,694],[97,143,682,694],[85,97,143,672,688,694],[97,143,644,664,673,703],[85,97,143,687,694],[97,143,670,672,674,677,678,679,680,684,685,686,689,690,691,692,693,694,695,696,697,698],[97,143,683],[85,97,143,643,670,672,673,674,677,678,679,680,683,684,685,686,689,690,691,692,693,695,699],[97,143,681,703],[85,97,143,642,643,645,724],[97,143,725],[97,143,644,655,719,726,752,757,759,763,765,769,780,782,809,813,816,818,822,841,849,853,855,857,859,866,881,891,896,912,925,932,936,938,946,966,976,980,987,1002,1004,1006,1014,1026,1028],[97,143,854],[85,97,143,645,849],[97,143,642],[85,97,143,725,727],[97,143,641],[85,97,143,644],[85,97,143,645,675],[85,97,143,645,790],[97,143,783,784,790,791,792,793,794,795,796,797,798,799,800,801,802,804,805,806,807,808],[97,143,747,783,784,789,790,791,792,793,794,795,796,797,798,799,800,801,802,804,805,806,807],[85,97,143,226,642,643,703,785,786,787,788,789],[85,97,143,785,790],[97,143,785],[85,97,143,645,647,664,665,672,673,699,703,790,809],[85,97,143,226,790,803],[85,97,143,785],[85,97,143,645,789],[97,143,856],[85,97,143,790],[97,143,858],[97,143,860,861,862,863,864,865],[97,143,860,861,862,863,864],[85,97,143,645,860],[97,143,867,868,869,870,871,872,873,874,875,876,877,878,879,880],[97,143,867,868,869,870,871,872,873,874,875,876,877,878,879],[85,97,143,645,647,728],[85,97,143,645,883],[97,143,883,884,885,886,887,888,889,890],[97,143,883,884,885,886,887,888,889],[85,97,143,642,643,645,727,882],[97,143,893,894,895],[97,143,747,893,894],[85,97,143,645,893],[85,97,143,642,643,645,727,892],[97,143,900,901,902,903,904,905,906,907,908,909,910,911],[97,143,899,900,901,902,903,904,905,906,907,908,909,910],[85,97,143,226,642,643,703,899],[97,143,898],[85,97,143,647,664,665,673,699,703,897,900,912,1029],[85,97,143,645,899],[97,143,915,917,918,919,920,921,922,923,924],[97,143,914,915,917,918,919,920,921,922,923],[85,97,143,916],[85,97,143,226,642,643,703,914],[97,143,913],[85,97,143,647,664,673,699,703,915,1029],[85,97,143,645,914],[97,143,926,927,928,929,930,931],[97,143,926,927,928,929,930],[85,97,143,645,926],[97,143,937],[97,143,933,934,935],[97,143,933,934],[85,97,143,645,647,933],[85,97,143,645,939],[97,143,939,940,941,942,943,944,945],[97,143,939,940,941,942,943,944],[97,143,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965],[97,143,747,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964],[97,143,747],[85,97,143,645,967],[97,143,967,968,969,970,971,973,974,975],[97,143,967,968,969,970,971,973,974],[85,97,143,645,967,972],[97,143,977,978,979],[97,143,977,978],[85,97,143,642,644,645,727],[85,97,143,645,977],[97,143,981,982,983,984,985,986],[97,143,981,982,983,984,985],[85,97,143,645,981,982],[85,97,143,645,982],[85,97,143,645,647,981,982],[85,97,143,642,643,645,981],[97,143,989],[97,143,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001],[97,143,988,989,990,991,992,993,994,995,996,997,998,999,1000],[85,97,143,645,728,989],[85,97,143,990],[85,97,143,645,647,989],[85,97,143,988],[97,143,1005],[97,143,1003],[85,97,143,645,1008],[97,143,1007,1008,1009,1010,1011,1012,1013],[97,143,645,1007,1008,1009,1010,1011,1012],[85,97,143,645,780],[97,143,1017,1018,1019,1020,1021,1022,1023,1024,1025],[97,143,1016,1017,1018,1019,1020,1021,1022,1023,1024],[85,97,143,226,642,643,703,1016],[97,143,1015],[85,97,143,647,664,673,699,703,1017,1026,1029],[85,97,143,645,1016],[85,97,143,643],[97,143,645,1027],[97,143,671,700,701,702],[85,97,143,670],[85,97,143,642,643,647,664,665,701],[97,143,645,647,673,699,700],[85,97,143,666,699],[97,143,656],[97,143,657],[97,143,657,658,660,661,662,663],[97,143,660],[85,97,143,226,660],[97,143,659,660],[97,143,2668],[97,143,666],[97,143,667,668],[85,97,143,669],[97,143,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],[97,143,2082],[97,143,1082,1301,2081],[97,143,656,2141,2142,2143,2144],[97,143,226],[97,143,1438,1446],[97,143,625],[97,143,1447,1448,1449,1450,1451],[97,143,1446,1448],[97,143,1447,1448],[85,97,143,1445,1446,1447],[85,97,143,226,626],[97,143,627],[97,143,1438,1441],[97,143,1432,1438,1439,1440,1441,1442,1443,1444],[97,143,1438],[85,97,143,1173],[97,143,1434],[97,143,1434,1435,1436,1437],[97,143,1433],[97,143,1154],[97,143,1139,1162],[97,143,1162],[97,143,1162,1173],[97,143,1148,1162,1173],[97,143,1153,1162,1173],[97,143,1143,1162],[97,143,1151,1162,1173],[97,143,1149],[97,143,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],[97,143,1152],[97,143,1139,1140,1141,1142,1143,1144,1145,1146,1147,1149,1150,1152,1154,1155,1156,1157,1158,1159,1160,1161],[97,143,1385],[97,143,1382,1383,1384,1385,1386,1389,1390,1391,1392,1393,1394,1395,1396],[97,143,1381],[97,143,1388],[97,143,1382,1383,1384],[97,143,1382,1383],[97,143,1385,1386,1388],[97,143,1383],[97,143,2680],[97,143,2679],[85,97,143,196,460,1397,1398],[97,143,1652],[97,143,1639,1640,1641],[97,143,1634,1635,1636],[97,143,1612,1613,1614,1615],[97,143,1578,1652],[97,143,1578],[97,143,1578,1579,1580,1581,1626],[97,143,1616],[97,143,1611,1617,1618,1619,1620,1621,1622,1623,1624,1625],[97,143,1626],[97,143,1577],[97,143,1630,1632,1633,1651,1652],[97,143,1630,1632],[97,143,1627,1630,1652],[97,143,1637,1638,1642,1643,1648],[97,143,1631,1633,1643,1651],[97,143,1650,1651],[97,143,1627,1631,1633,1649,1650],[97,143,1631,1652],[97,143,1629],[97,143,1629,1631,1652],[97,143,1627,1628],[97,143,1644,1645,1646,1647],[97,143,1633,1652],[97,143,1588],[97,143,1582,1589],[97,143,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],[97,143,1608,1652],[97,143,600,601],[97,143,4158],[97,143,2131],[97,143,2154],[97,143,4162],[97,143,546,547,4164],[97,143,2723],[97,143,157,184,191,3426,3427],[97,140,143],[97,142,143],[143],[97,143,148,176],[97,143,144,149,154,162,173,184],[97,143,144,145,154,162],[92,93,94,97,143],[97,143,146,185],[97,143,147,148,155,163],[97,143,148,173,181],[97,143,149,151,154,162],[97,142,143,150],[97,143,151,152],[97,143,153,154],[97,142,143,154],[97,143,154,155,156,173,184],[97,143,154,155,156,169,173,176],[97,143,151,154,157,162,173,184],[97,143,154,155,157,158,162,173,181,184],[97,143,157,159,173,181,184],[95,96,97,98,99,100,101,139,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],[97,143,154,160],[97,143,161,184,189],[97,143,151,154,162,173],[97,143,163],[97,143,164],[97,142,143,165],[97,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],[97,143,167],[97,143,168],[97,143,154,169,170],[97,143,169,171,185,187],[97,143,154,173,174,176],[97,143,175,176],[97,143,173,174],[97,143,177],[97,140,143,173,178],[97,143,154,179,180],[97,143,179,180],[97,143,148,162,173,181],[97,143,182],[97,143,162,183],[97,143,157,168,184],[97,143,148,185],[97,143,173,186],[97,143,161,187],[97,143,188],[97,138,143],[97,138,143,154,156,165,173,176,184,187,189],[97,143,173,190],[97,143,173,191],[85,89,97,143,192,193,194,195,196,479,524],[85,89,97,143,192,193,194,195,460,479,524],[85,89,97,143,192,193,195,196,479,524],[85,97,143,196,460,461],[85,97,143,196,460],[85,97,143,1369],[85,89,97,143,193,194,195,196,479,524],[85,89,97,143,192,194,195,196,479,524],[83,84,97,143],[97,143,533,538,539,541],[97,143,587,588],[97,143,539,541,581,582,583],[97,143,539],[97,143,539,541,581],[97,143,539,581],[97,143,594],[97,143,534,594,595],[97,143,534,594],[97,143,534,540],[97,143,535],[97,143,534,535,536,538],[97,143,534],[97,143,1030,1032],[97,143,1030],[97,143,2383],[97,143,2381,2383],[97,143,2381],[97,143,2383,2447,2448],[97,143,2383,2450],[97,143,2383,2451],[97,143,2468],[97,143,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,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,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],[97,143,2383,2544],[97,143,2383,2448,2568],[97,143,2381,2565,2566],[97,143,2567],[97,143,2383,2565],[97,143,2380,2381,2382],[97,143,2058],[97,143,2057],[97,143,2059],[97,143,546,547,2669,2670,4164],[97,143,2671],[97,143,619,620],[97,143,619,620,621,622],[97,143,619,621],[97,143,619],[97,143,157,173,191],[97,143,574,575],[97,143,2767,2770,2773,2775,2776,2777],[97,143,2734,2762,2767,2770,2773,2775,2777],[97,143,2734,2762,2767,2770,2773,2777],[97,143,2800,2801,2805],[97,143,2777,2800,2802,2805],[97,143,2777,2800,2802,2804],[97,143,2734,2762,2777,2800,2802,2803,2805],[97,143,2802,2805,2806],[97,143,2777,2800,2802,2805,2807],[97,143,2724,2734,2735,2736,2760,2761,2762],[97,143,2724,2735,2762],[97,143,2724,2734,2735,2762],[97,143,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759],[97,143,2724,2728,2734,2736,2762],[97,143,2778,2779,2799],[97,143,2734,2762,2800,2802,2805],[97,143,2734,2762],[97,143,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798],[97,143,2723,2734,2762],[97,143,2767,2768,2769,2773,2777],[97,143,2767,2770,2773,2777],[97,143,2767,2770,2771,2772,2777],[97,143,482],[97,143,430,493,494],[97,143,201,202,204,216,240,355,366,475],[97,143,204,235,236,237,239,475],[97,143,204,372,374,376,377,379,475,477],[97,143,204,238,275,475],[97,143,202,204,215,216,222,228,233,354,355,356,365,475,477],[97,143,475],[97,143,211,217,236,256,351],[97,143,204],[97,143,197,211,217],[97,143,383],[97,143,380,381,383],[97,143,380,382,475],[97,143,157,256,454,472],[97,143,157,327,330,346,351,472],[97,143,157,299,472],[97,143,359],[97,143,358,359,360],[97,143,358],[91,97,143,157,197,204,216,222,228,234,236,240,241,254,255,322,352,353,366,475,479],[97,143,201,204,238,275,372,373,378,475,527],[97,143,238,527],[97,143,201,255,425,475,527],[97,143,527],[97,143,204,238,239,527],[97,143,375,527],[97,143,241,354,357,364],[85,97,143,430],[97,143,168,211,226],[97,143,211,226],[85,97,143,296],[85,97,143,217,226,430],[97,143,211,282,296,297,509,516],[97,143,281,510,511,512,513,515],[97,143,332],[97,143,332,333],[97,143,215,217,284,285],[97,143,217,291,292],[97,143,217,286,294],[97,143,291],[97,143,209,217,284,285,286,287,288,289,290,291,294],[97,143,217,284,291,292,293,295],[97,143,217,285,287,288],[97,143,285,287,290,292],[97,143,514],[97,143,217],[85,97,143,205,503],[85,97,143,184],[85,97,143,238,273],[85,97,143,238,366],[97,143,271,276],[85,97,143,272,481],[97,143,2696],[85,89,97,143,157,192,193,194,195,196,479,523],[97,143,157,217],[97,143,157,216,221,302,319,361,362,366,422,424,475,476],[97,143,254,363],[97,143,479],[97,143,203],[85,97,143,208,211,427,443,445],[97,143,168,211,427,442,443,444,526],[97,143,436,437,438,439,440,441],[97,143,438],[97,143,442],[97,143,226,390,391,393],[85,97,143,217,384,385,386,387,392],[97,143,390,392],[97,143,388],[97,143,389],[85,97,143,226,272,481],[85,97,143,226,480,481],[85,97,143,226,481],[97,143,319,320],[97,143,320],[97,143,157,476,481],[97,143,349],[97,142,143,348],[97,143,211,217,223,225,327,340,344,346,424,427,464,465,472,476],[97,143,217,266,288],[97,143,327,338,341,346],[85,97,143,208,211,327,330,346,349,383,431,432,433,434,435,446,447,448,449,450,451,452,453,527],[97,143,208,211,236,327,334,335,336,339,340],[97,143,173,217,236,338,345,427,428,472],[97,143,342],[97,143,157,168,205,217,221,231,263,264,267,319,322,387,422,423,464,475,476,477,479,527],[97,143,208,209,211],[97,143,327],[97,142,143,236,263,264,321,322,323,324,325,326,476],[97,143,346],[97,142,143,210,211,221,225,261,327,334,335,336,337,338,341,342,343,344,345,465],[97,143,157,261,262,334,476,477],[97,143,236,264,319,322,327,424,476],[97,143,157,475,477],[97,143,157,173,472,476,477],[97,143,157,168,197,211,216,223,225,228,231,238,258,263,264,265,266,267,302,303,305,308,310,313,314,315,316,318,366,422,424,472,475,476,477],[97,143,157,173],[97,143,204,205,206,234,472,473,474,479,481,527],[97,143,201,202,475],[97,143,395],[97,143,157,173,184,213,379,383,384,385,386,387,393,394,527],[97,143,168,184,197,211,213,225,228,264,303,308,318,319,372,399,400,401,408,411,412,422,424,472,475],[97,143,228,234,241,254,264,322,475],[97,143,157,184,205,216,225,264,406,472,475],[97,143,426],[97,143,157,395,409,410,419],[97,143,472,475],[97,143,324,465],[97,143,225,263,366,481],[97,143,157,168,203,308,368,372,401,408,411,414,472],[97,143,157,241,254,372,415],[97,143,204,265,366,417,475,477],[97,143,157,184,387,475],[97,143,157,238,265,366,367,368,377,395,416,418,475],[91,97,143,157,263,421,479,481],[97,143,317,422],[97,143,157,168,211,214,216,217,223,225,231,240,241,254,264,267,303,305,315,318,319,366,399,400,401,402,404,407,422,424,472,481],[97,143,157,173,241,408,413,419,472],[97,143,244,245,246,247,248,249,250,251,252,253],[97,143,258,309],[97,143,311],[97,143,309],[97,143,311,312],[97,143,157,215,216,217,221,222,476],[97,143,157,168,203,205,223,227,263,266,267,301,422,472,477,479,481],[97,143,157,168,184,207,214,215,225,227,264,420,465,471,476],[97,143,334],[97,143,335],[97,143,217,228,464],[97,143,336],[97,143,210],[97,143,212,224],[97,143,157,212,216,223],[97,143,219,224],[97,143,220],[97,143,212,213],[97,143,212,268],[97,143,212],[97,143,214,258,307],[97,143,306],[97,143,211,213,214],[97,143,214,304],[97,143,211,213],[97,143,263,366],[97,143,464],[97,143,157,184,223,225,229,263,366,421,424,427,428,429,455,456,459,463,465,472,476],[97,143,277,280,282,283,296,297],[85,97,143,194,195,196,226,457,458],[85,97,143,194,195,196,226,457,458,462],[97,143,350],[97,143,236,257,262,263,327,328,329,330,331,333,346,347,349,352,421,424,475,477],[97,143,296],[97,143,157,301,472],[97,143,301],[97,143,157,223,269,298,300,302,421,472,479,481],[97,143,277,278,279,280,282,283,296,297,480],[91,97,143,157,168,184,212,213,225,231,263,264,267,366,419,420,422,472,475,476,479],[97,143,208,211,218],[97,143,262,264,396,399],[97,143,262,397,466,467,468,469,470],[97,143,157,258,475],[97,143,157],[97,143,261,346],[97,143,260],[97,143,262,315],[97,143,259,261,475],[97,143,157,207,262,396,397,398,472,475,476],[85,97,143,211,217,295],[85,97,143,209],[97,143,199,200],[85,97,143,205],[85,97,143,211,281],[85,91,97,143,263,267,479,481],[97,143,205,503,504],[85,97,143,276],[85,97,143,168,184,203,270,272,274,275,481],[97,143,211,238,476],[97,143,211,403],[85,97,143,155,157,168,201,203,276,374,479,480],[85,97,143,192,193,194,195,196,479,524],[85,86,87,88,89,97,143],[97,143,148],[97,143,369,370,371],[97,143,369],[85,89,97,143,157,159,168,191,192,193,194,195,196,197,203,231,236,414,442,477,478,481,524],[97,143,489],[97,143,491],[97,143,495],[97,143,2697],[97,143,497],[97,143,499,500,501],[97,143,505],[90,97,143,483,488,490,492,496,498,502,506,508,518,519,521,525,526,527,528],[97,143,507],[97,143,517],[97,143,272],[97,143,520],[97,142,143,262,396,397,399,466,467,469,470,522,524],[97,143,191],[85,97,143,1658],[85,97,143,1657],[97,143,1657,1660],[97,143,2961,2962,2967],[97,143,2963,2964,2966,2968],[97,143,2967],[97,143,2964,2966,2967,2968,2969,2971,2973,2974,2975,2976,2977,2978,2979,2983,2998,3009,3012,3016,3024,3025,3027,3030,3033,3036],[97,143,2967,2974,2987,2991,3000,3002,3003,3004,3031],[97,143,2967,2968,2984,2985,2986,2987,2989,2990],[97,143,2991,2992,2999,3002,3031],[97,143,2967,2968,2973,2992,3004,3031],[97,143,2968,2991,2992,2993,2999,3002,3031],[97,143,2964],[97,143,2970,2991,2998,3004],[97,143,2998],[97,143,2967,2987,2994,2996,2998,3031],[97,143,2991,2998,2999],[97,143,3000,3001,3003],[97,143,3031],[97,143,2980,2981,2982,3032],[97,143,2967,2968,3032],[97,143,2963,2967,2981,2983,3032],[97,143,2967,2981,2983,3032],[97,143,2967,2969,2970,2971,3032],[97,143,2967,2969,2970,2984,2985,2986,2988,2989,3032],[97,143,2989,2990,3005,3008,3032],[97,143,3004,3032],[97,143,2967,2991,2992,2993,2999,3000,3002,3003,3032],[97,143,2970,3006,3007,3008,3032],[97,143,2967,3032],[97,143,2967,2969,2970,2990,3032],[97,143,2963,2967,2969,2970,2984,2985,2986,2988,2989,2990,3032],[97,143,2967,2969,2970,2985,3032],[97,143,2963,2967,2970,2984,2986,2988,2989,2990,3032],[97,143,2970,2973,3032],[97,143,2973],[97,143,2963,2967,2969,2970,2972,2973,2974,3032],[97,143,2972,2973],[97,143,2967,2969,2973,3032],[97,143,3033,3034],[97,143,2963,2967,2973,2974,3032],[97,143,2967,2969,3011,3032],[97,143,2967,2969,3010,3032],[97,143,2967,2969,2970,2998,3013,3015,3032],[97,143,2967,2969,3015,3032],[97,143,2967,2969,2970,2998,3014,3032],[97,143,2967,2968,2969,3032],[97,143,3018,3032],[97,143,2967,3013,3032],[97,143,3020,3032],[97,143,2967,2969,3032],[97,143,3017,3019,3021,3023,3032],[97,143,2967,2969,3017,3022,3032],[97,143,3013,3032],[97,143,2998,3032],[97,143,2970,2971,2974,2975,2976,2977,2978,2979,2983,2998,3009,3012,3016,3024,3025,3027,3030,3035],[97,143,2967,2969,2998,3032],[97,143,2963,2967,2969,2970,2994,2995,2997,2998,3032],[97,143,2967,2976,3026,3032],[97,143,2967,2969,3028,3030,3032],[97,143,2967,2969,3030,3032],[97,143,2967,2969,2970,3028,3029,3032],[97,143,2968],[97,143,2965,2967,2968],[97,143,1337],[97,143,628,1337,1338],[97,143,568],[97,143,566,568],[97,143,557,565,566,567,569,571],[97,143,555],[97,143,558,563,568,571],[97,143,554,571],[97,143,558,559,562,563,564,571],[97,143,558,559,560,562,563,571],[97,143,555,556,557,558,559,563,564,565,567,568,569,571],[97,143,571],[97,143,553,555,556,557,558,559,560,562,563,564,565,566,567,568,569,570],[97,143,553,571],[97,143,558,560,561,563,564,571],[97,143,562,571],[97,143,563,564,568,571],[97,143,556,566],[97,143,1387],[85,97,143,1064],[97,143,1064,1065,1066,1067,1068,1071,1072,1073,1074,1075,1076,1077,1080,1081],[97,143,1064],[97,143,1069,1070],[85,97,143,1061,1064],[97,143,1058,1059,1061],[97,143,1054,1057,1059,1061],[97,143,1058,1061],[85,97,143,1049,1050,1051,1054,1055,1056,1058,1059,1060,1061],[97,143,1051,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063],[97,143,1058],[97,143,1052,1058,1059],[97,143,1052,1053],[97,143,1057,1059,1060],[97,143,1057],[97,143,1049,1054,1057,1059,1060],[85,97,143,1054,1057,1058,1059],[97,143,1078,1079],[85,97,143,2318],[85,97,143,2317],[97,143,2765],[85,97,143,2724,2733,2762,2764],[85,97,143,2169,2170,2217],[97,143,2262,2263],[97,143,2169],[97,143,2217],[85,97,143,2264],[85,97,143,2136,2146,2149,2151,2157,2158,2165,2167,2168,2170,2171,2172,2174,2214,2217],[85,97,143,2157,2217],[85,97,143,2136,2146,2149,2151,2156,2158,2167,2169,2170,2171,2175,2177,2178,2214,2217],[85,97,143,2167,2175,2219],[85,97,143,2150,2217],[85,97,143,2135,2136,2138,2146,2217],[85,97,143,2136,2146,2167,2208,2217],[85,97,143,2136,2176,2197,2201,2217],[85,97,143,2149,2158,2170,2171,2184,2185,2217,2258],[97,143,2135,2217],[97,143,2146,2217],[85,97,143,2136,2146,2149,2151,2157,2158,2170,2171,2196,2214,2217],[85,97,143,2136,2138,2175,2188,2241],[85,97,143,2134,2136,2138,2188],[85,97,143,2136,2138,2166,2188,2189,2217],[85,97,143,2136,2146,2149,2153,2157,2158,2170,2171,2185,2198,2200,2214,2217],[85,97,143,2140,2146,2217],[85,97,143,2140,2146,2214,2217],[85,97,143,2217],[85,97,143,2217,2274],[85,97,143,2175,2185,2217],[85,97,143,2135,2185,2217],[85,97,143,2185,2217],[85,97,143,2147],[85,97,143,2136,2185,2217],[85,97,143,2134,2136,2217],[85,97,143,2135,2136,2137,2217],[85,97,143,2135,2136,2138,2217,2274],[85,97,143,2159,2160,2161],[85,97,143,2146,2148,2149,2160,2185,2217,2220],[97,143,2207,2217],[97,143,2146,2147,2166,2212,2214,2217],[97,143,2134,2135,2136,2138,2139,2140,2146,2147,2149,2157,2158,2159,2162,2166,2168,2169,2170,2171,2172,2173,2175,2176,2185,2188,2190,2196,2197,2198,2200,2201,2202,2209,2212,2213,2214,2217,2218,2219,2221,2222,2223,2224,2225,2226,2227,2228,2230,2232,2234,2235,2236,2237,2238,2239,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,2268,2269,2270,2271,2272,2273],[85,97,143,2136,2149,2151,2158,2170,2171,2180,2182,2184,2199,2217,2233,2274],[85,97,143,2136,2140,2146,2189,2217,2231],[85,97,143,2136,2146],[85,97,143,2136,2140,2146,2189,2217,2229],[85,97,143,2136,2158,2166,2170,2171,2181,2189,2217],[85,97,143,2136,2146,2149,2151,2156,2158,2167,2170,2171,2214,2217,2225,2233,2236],[85,97,143,2156,2217],[85,97,143,2169,2217],[97,143,2141,2145,2217],[97,143,2139,2140,2141,2145,2214,2217],[97,143,2141,2145,2150],[97,143,2141,2145,2184,2202,2217],[97,143,2141,2145,2146,2151,2152,2153,2174,2178,2179,2182,2183,2217],[97,143,2141,2145,2159,2162,2217],[97,143,2141,2145,2185,2217],[97,143,2141,2145,2146],[97,143,2141,2145],[97,143,2141,2142,2145,2146,2188,2190],[97,143,2141,2142,2145,2146,2217],[97,143,2141,2145,2147,2173,2217],[97,143,2165,2184,2207,2217],[97,143,2146,2151,2164,2165,2166,2184,2191,2194,2203,2207,2209,2210,2211,2213,2217],[97,143,2146,2151,2164,2165],[97,143,2207],[97,143,2145,2146,2151,2163,2184,2185,2186,2187,2191,2192,2193,2194,2195,2203,2204,2205,2206],[97,143,2141,2145,2146,2148,2149,2184,2217],[97,143,2151,2164,2173,2184,2217],[97,143,2164,2177,2184],[97,143,2151,2184,2217],[85,97,143,2149,2180,2181,2184,2217],[97,143,2184],[97,143,2164,2184],[97,143,2149,2151,2184,2217],[97,143,2167,2184,2217],[97,143,2185,2217],[85,97,143,2175,2176,2217],[97,143,2149,2156,2163,2165,2166,2185,2214,2217],[85,97,143,2149,2173,2176,2197,2201,2217,2221,2244,2245,2246,2259],[85,97,143,2149,2217,2221,2230,2232,2234,2235,2237],[85,97,143,2217,2237,2274],[97,143,2146,2217,2267],[97,143,2140,2217],[85,97,143,2184,2198,2199,2201,2217],[97,143,2156,2164,2167,2184],[85,97,143,2180,2240],[85,97,143,2133,2134,2135,2138,2139,2140,2146,2147,2148,2151,2169,2173,2180,2214,2215,2216,2274],[97,143,2141],[97,143,2774,2807,2808],[97,143,2809],[97,143,2762,2763],[97,143,2724,2728,2733,2734,2762],[97,143,547,579,580],[97,143,173,191,405],[97,143,537],[97,143,2730],[97,110,114,143,184],[97,110,143,173,184],[97,105,143],[97,107,110,143,181,184],[97,143,162,181],[97,105,143,191],[97,107,110,143,162,184],[97,102,103,106,109,143,154,173,184],[97,110,117,143],[97,102,108,143],[97,110,131,132,143],[97,106,110,143,176,184,191],[97,131,143,191],[97,104,105,143,191],[97,110,143],[97,104,105,106,107,108,109,110,111,112,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,132,133,134,135,136,137,143],[97,110,125,143],[97,110,117,118,143],[97,108,110,118,119,143],[97,109,143],[97,102,105,110,143],[97,110,114,118,119,143],[97,114,143],[97,108,110,113,143,184],[97,102,107,110,117,143],[97,143,173],[97,105,110,131,143,189,191],[97,143,2728,2732],[97,143,2723,2728,2729,2731,2733],[97,143,3405,3406,3407,3408,3409,3410,3411,3413,3414,3415,3416,3417,3418,3419,3420],[97,143,3407],[97,143,3407,3412],[97,143,2725],[97,143,2726,2727],[97,143,2723,2726,2728],[97,143,2132],[97,143,2155],[97,143,591,592],[97,143,591],[97,143,543],[97,143,154,155,157,158,159,162,173,181,184,190,191,543,544,545,547,548,550,551,552,572,573,577,578,579,580],[97,143,543,544,545,549],[97,143,545],[97,143,576],[97,143,547,580],[97,143,542,611,616],[97,143,584,603,604,616],[97,143,534,541,584,596,597,616],[97,143,606],[97,143,585],[97,143,534,542,584,586,596,605,616],[97,143,589],[97,143,146,155,173,534,539,541,580,584,586,589,590,593,596,598,599,602,605,607,608,610,616],[97,143,584,603,604,605,616],[97,143,580,609,610],[97,143,584,586,593,596,598,616],[97,143,189,599],[97,143,146,155,173,534,539,541,580,584,585,586,589,590,593,596,597,598,599,602,603,604,605,606,607,608,609,610,616],[97,143,585,586],[97,143,146,155,173,189,533,534,539,541,542,580,584,585,586,589,590,593,596,597,598,599,602,603,604,605,606,607,608,609,610,615,616,617,618,623],[97,143,2071,2072],[97,143,2069,2070,2071,2073,2074,2079],[97,143,2070,2071],[97,143,2079],[97,143,2080],[97,143,2071],[97,143,2069,2070,2071,2074,2075,2076,2077,2078],[97,143,2069,2070,2081],[97,143,1301],[97,143,1301,1304],[97,143,1294,1301,1302,1303,1304,1305,1306,1307,1308],[97,143,1309],[97,143,1301,1302],[97,143,1301,1303],[97,143,1247,1249,1250,1251,1252],[97,143,1247,1249,1251,1252],[97,143,1247,1249,1251],[97,143,1247,1249,1250,1252],[97,143,1247,1249,1252],[97,143,1247,1248,1249,1250,1251,1252,1253,1254,1294,1295,1296,1297,1298,1299,1300],[97,143,1249,1252],[97,143,1246,1247,1248,1250,1251,1252],[97,143,1249,1295,1299],[97,143,1249,1250,1251,1252],[97,143,1310],[97,143,1251],[97,143,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],[97,143,164,226],[85,97,143,226,624,628,1399,1653,2858],[85,97,143,226,628,631,1035,1036,1037,1039,1044,1045,1110,1312,1313,1340,1348,1421,1426,1501,1954,2084,2854],[97,143,226,624,1313],[97,143,226,639,1312],[97,143,226,1311],[97,143,226,624,1399,1421,1422,1653,2857,2863],[85,97,143,226,639,1035,1039,1048,1092,1115,1207,1235,1348,1361,1422,2816,2818,2856],[97,143,226,1036,1037,1039,1044,1045,1082,1311,1348,1426,1501,1954,2854],[97,143,226,624,1421,1653,2856,2863],[85,97,143,226,631,1035,1110,1421,1425,2084,2855],[97,143,226,624,1399,1421,1653,2861,2863],[85,97,143,226,1035,1038,1039,1106,1109,1245,1421,1424,2814,2834,2857,2858,2860],[85,97,143,226,1039,1174,1176,1188,1245,2859],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1245,1362],[97,143,226,1109,2861],[97,143,226,624,1399,1653,2893],[85,97,143,226,631,1035,1036,1039,1044,1092,1109,1110,1178,1214,1311,1348,1954,1956,2084,2868,2869,2870,2872,2880,2882,2883,2886,2889,2890,2891,2892],[97,143,226,1109,1222,2893],[97,143,226,624,1399,2811],[85,97,143,226,624,1214,1399,1653,2863,2901],[85,97,143,226,624,1214,1399,1653,2901],[85,97,143,226,631,1035,1036,1037,1039,1041,1043,1044,1045,1048,1082,1089,1094,1102,1109,1110,1115,1203,1214,1215,1315,1316,1354,1361,1951,1963,1967,1968,2843,2871,2896,2898,2899,2900],[85,97,143,226,624,1214,1399,1653,2863,2899],[85,97,143,226,1035,1036,1037,1039,1048,1094,1101,1115,1183,1214,1316,1361,1452,1956],[85,97,143,226,624,1318,1399,2863,2903],[85,97,143,226,1318],[97,143,226,624,1316],[97,143,226,1214],[85,97,143,226,1035,1036,1037,1039,1044,1045,1082,1094,1315,2896,2897],[85,97,143,226,624,628,1214,1399,1653,2904],[85,97,143,226,624,1214,1318,1399,2904],[85,97,143,226,631,1034,1035,1036,1039,1043,1044,1048,1082,1092,1214,1215,1315,1316,1318,1319,1348,1361,1487,1501,1963,1967,2848,2896,2898,2899,2900,2902,2903],[97,143,226,624,1214,1318,1319],[97,143,226,1214,1318],[85,97,143,226,624,1215,1399,1653,2863,2902],[85,97,143,226,1035,1039,1048,1215],[85,97,143,226,1036,1039,1040,1044,1048,1082,1101],[85,97,143,226,624,1214,1399,1653,2907],[85,97,143,226,631,1035,1039,1106,1214,1215,1318,1347,1956,2901,2904,2906],[97,143,226,624,1318,1399,1653,2906],[85,97,143,226,1038,1039,1048,1094,1174,1176,1188,1318,2662,2905],[97,143,226,1034,1035,1039,1115,1174,1176,1188,1204,1318,1362],[85,97,143,226,1036,1315,2896],[85,97,143,226,1036,1037,1044,1045,1214,1315,2871,2896,2897],[97,143,226,1109,1217,2907],[97,143,226,624,1399,2851],[85,97,143,226,518,1109,1215,1217,1972,2850],[85,97,143,226,1109,2718,2851],[97,143,226,624,1399,1653,2921],[85,97,143,226,1348,1372,2920],[85,97,143,226,1034,1039],[97,143,226,1109,1222,2921,2922],[85,97,143,226,624,1399,1653,2863,2925],[85,97,143,226,631,1035,1036,1039,1044,1045,1101,1110,1311,1321,1455,1954,2084],[85,97,143,226,624,628,630,1399,1653,2929],[85,97,143,226,631,1035,1039,1106,1109,1323,1348,1369,1371,1455,2814,2834,2925,2927,2928],[97,143,226,624,1321],[97,143,226,624,630,1193,1399,1454,1455,1653,2863,2927],[85,97,143,226,630,1036,1039,1042,1183,1188,1430,1454,1455,2926],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362,1455,1700],[85,97,143,226,624,639,1399,1653,2863,2928],[85,97,143,226,631,1035,1036,1039,1044,1045,1082,1101,1110,1321,1455,1954],[97,143,226,1109,2929],[85,97,143,226,624,1399,2863,2946],[85,97,143,226,631,1035,1039,1040,1092,1214,1348,1456,2006,2282,2821,2936,2940,2944,2945],[85,97,143,226,624,1399,1653,2863,2936],[85,97,143,226,1035,1039,1348,2935],[85,97,143,226,1324,1325,2938],[85,97,143,226,1036,1037,1040,1045,1082,1094,1324,1325,1954,2871],[97,143,226,624,1324,1325],[97,143,226,1324],[97,143,226,624,1399,1653,2940],[85,97,143,226,631,1035,1039,1082,1090,1101,1214,1324,1325,2937,2938,2939],[97,143,226,624,1399,2937],[85,97,143,226,1045],[85,97,143,226,1327,1328,2941],[85,97,143,226,1036,1037,1082,1094,1327,1328,1954,2871],[85,97,143,226,624,1327,1399,1653,2863,2943],[85,97,143,226,1045,1327],[97,143,226,624,1087,1328],[97,143,226,1087,1203,1327],[97,143,226,624,628,631,1214,1399,1653,2944],[97,143,226,624,628,631,1087,1214,1399,1653,2944],[85,97,143,226,631,1035,1082,1203,1327,1328,1361,1476,2942,2943],[85,97,143,226,624,1399,2945],[85,97,143,226,639,1035,1039,1092,2276,2282],[97,143,226,1109,2946],[85,97,143,226,1109,1214,1242],[97,143,226,624,1330],[97,143,226,639],[85,97,143,226,624,628,630,1189,1330,1343,1399,2831],[85,97,143,226,630,1043,1045,1048,1092,1115,1178,1189,1193,1330,1333,1342,1343,1348,2821,2829,2830],[97,143,226,624,1332,1342,1399,3047],[85,97,143,226,1039,1048,1092,1178,1193,1333,1342,1348,2821],[97,143,226,624,1214,1332,1333],[97,143,226,1193,1214,1332],[85,97,143,226,624,628,1399,3049],[85,97,143,226,1039,1342,1348,1414,2814,2831,2957,2958,2959,3048],[97,143,226,624,1335],[97,143,226,624,1399,3048],[85,97,143,226,631,1214,1342,3046,3047],[97,143,226,624,1399,1653,2959],[85,97,143,226,631,1035,1036,1039,1044,1048,1092,1094,1214,1311,1335,1361,1954,2084],[85,97,143,226,624,630,1345,1399,1487,1653,2829,2863],[85,97,143,226,630,1035,1039,1048,1092,1109,1115,1178,1333,1345,2828],[85,97,143,226,1035,1036,1041,1042,1045,1091,1092,1109,1189,1345,1487,1508,1560,2825,2826,2827],[85,97,143,226,624,1189,1330,1399,2830],[85,97,143,226,1092,1132,1134,1136,1189,1330,2282],[97,143,226,624,1214,1332,1399,1653,2958],[85,97,143,226,1092,1214,1333,1342,1348,1414,2282,2821,2823],[97,143,226,624,1340,1343],[97,143,226,1214,1340,1342],[97,143,226,624,1342,1399],[97,143,226,624,1214,1342,1399],[85,97,143,226,1106,1214,1332,1341],[97,143,226,624,1345],[97,143,226,628,631,639,1109,1340],[97,143,226,1109,3049],[85,97,143,226,624,1349,1358,1399,1653,2863],[85,97,143,226,1035,1036,1039,1040,1044,1048,1102,1349,1352,1354],[97,143,226,624,1349,1356,1399,1653,2863],[85,97,143,226,624,1349,1352,1356,1399,1653,2863],[85,97,143,226,1035,1036,1038,1039,1040,1044,1048,1349,1352,1354],[85,97,143,226,624,1378,1399,1653,2863],[85,97,143,226,1035,1039,1090,1094,1101,1110,1347,1348,1349,1355,1356,1357,1358,1367,1368,1373,1375,1376,1377],[85,97,143,226,624,1373,1399,1653,2863],[85,97,143,226,1036,1042,1372],[97,143,226,1349,1355,1356,1357,1358,1373,1374,1375,1376,1378],[85,97,143,226,624,1359,1367,1399,1653,2863],[85,97,143,226,1035,1036,1039,1041,1102,1178,1359,1365,1366],[85,97,143,226,624,1349,1359,1365,1399,1653,2863],[85,97,143,226,1035,1039,1043,1092,1115,1178,1193,1349,1359,1361,1364],[85,97,143,226,624,1359,1363,1364,1653,2863],[85,97,143,226,1035,1039,1359,1362,1363],[97,143,226,624,1349,1359,1363],[97,143,226,1193,1349,1359],[97,143,226,1349],[97,143,226,624,1349,1359,1366,1399],[85,97,143,226,1214,1349,1359],[85,97,143,226,624,1355,1399,1653,2863],[85,97,143,226,1035,1036,1039,1349,1350,1352,1354],[97,143,226,624,1374],[97,143,226,1352],[85,97,143,226,624,1352,1357,1399,1653,2863],[97,143,226,624,631,1214,1377,1399],[85,97,143,226,631,1214],[97,143,226,624,631,1375,1399],[85,97,143,226,631,1214,1349,1352,1374],[97,143,226,624,631,1376,1399],[97,143,226,1109,1379],[97,143,226,624,1399,1653,3136],[85,97,143,226,1035,1037,1039,1041,1090,1110],[97,143,226,624,1399,1653,3150],[85,97,143,226,1035,1036,1037,1039,1042,1045,1094],[97,143,226,624,628,1399,1482,1653,3142],[85,97,143,226,628,1035,1039,1115,1203,1214,1348,1361,1482,2016,3136,3138,3140,3141],[97,143,226,624,1214,1653,1659,2863,3145],[85,97,143,226,1214,1661,2006,2821,3142,3144],[97,143,226,624,1399,1482,1653,3144],[85,97,143,226,1035,1039,1174,1176,1188,1191,1199,1361,1482,2017,2814,3136,3137,3138,3139,3143],[97,143,226,624,1399,1482,1653,3140],[85,97,143,226,1039,1174,1176,1188,1194,1199,1482,2017,3137,3138,3139],[85,97,143,226,624,1399,2863,3143],[85,97,143,226,1092,2282],[97,143,226,624,1399,2863,3147],[97,143,226,1109,1414,3145,3146],[85,97,143,226,624,1214,1399,1653,2863,3090],[85,97,143,226,624,1399,2863,3090],[85,97,143,226,631,1035,1036,1037,1040,1044,1045,1048,1082,1091,1110,1214,1354,1361,1406,3081,3082,3083,3084,3085,3086,3088,3089],[85,97,143,226,1035,1039,1045,1115,1174,1176,1188,1409],[85,97,143,226,624,1214,1399,1653,3081],[85,97,143,226,1044,1045,1092,1094,1214,1697,3080],[85,97,143,226,1035,1039,1040,1045,1092,1101,1115,1174,1176,1188,1214,1409],[97,143,226,624,1653,2863,3082],[85,97,143,226,631,1035,1039,1092,1214,1361,3074,3075,3076,3077,3078,3079,3081],[97,143,226,624,2863,3094],[85,97,143,226,1092,1115,3077,3078,3093],[97,143,226,624,1399,1653,3095],[85,97,143,226,1039,1043,1956,3081,3082,3094],[97,143,226,624,1653,2863,3077,3078,3079,3093],[97,143,226,624,1399,1653,3075],[85,97,143,226,1035,1036,1045,1110,1409],[97,143,226,624,1399,1653,3076],[85,97,143,226,1035,1036,1037,1045,1110,1409],[85,97,143,226,1035,1039,1045,1174,1176,1188,1409],[97,143,226,624,1399,1653,3074],[85,97,143,226,1035,1040,1045,1110,1409],[85,97,143,226,624,1399,1653,1697],[85,97,143,226,1040],[85,97,143,226,624,1399,1653,3080],[85,97,143,226,1036],[97,143,226,624,1214,1399,1410,1653],[85,97,143,226,631,1035,1036,1037,1039,1040,1045,1094,1101,1110,1214,1361],[97,143,226,1410],[97,143,226,624,1399,1407,1653,3102],[85,97,143,226,1038,1039,1407,3100,3101],[97,143,226,624,1399,1407,1653,3100],[85,97,143,226,1039,1354,1407],[97,143,226,624,1407],[97,143,226,1406],[97,143,226,624,1399,1407,3101],[85,97,143,226,1034,1035,1039,1354,1405,1407,3090],[97,143,226,624,1214,1399,1653,2863,3096],[97,143,226,624,1214,1399,1653,3096],[85,97,143,226,631,1035,1036,1037,1039,1043,1044,1045,1048,1082,1092,1115,1193,1214,1348,1354,1406,1410,3083,3084,3085,3088,3089,3095],[97,143,226,624,1406],[97,143,226,530],[85,97,143,226,1035,1036,1045,1091,1937,2871,3083],[85,97,143,226,624,1082,1399,1406,2863,3083,3085],[85,97,143,226,631,1036,1037,1044,1045,1091,1103,1214,1361,1406,1937,2871,3083],[97,143,226,624,1399,1653,2093,3092],[85,97,143,226,1039,1174,1176,1188,2093,3091],[85,97,143,226,1039,1044,1045,1048,1082],[85,97,143,226,624,1214,1406,1659,2863,3104],[85,97,143,226,631,1034,1035,1039,1106,1214,1348,1362,1406,1411,1661,2093,2834,3090,3092,3096,3099,3102,3103],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1354,1362,1406,2093],[97,143,226,624,1399,1653,3098],[85,97,143,226,631,1035,1037,1039,1048,1361,3097],[97,143,226,624,1399,1653,3099],[85,97,143,226,631,1038,1039,1092,1214,1361,1406,2093,3098],[97,143,226,624,1399,1653,3097],[85,97,143,226,631,1035,1039,1092],[85,97,143,226,1035,1036,1038,1039,1040,1044,1045,1082,3083],[97,143,226,624,1399,2093,3087],[85,97,143,226,1035,1039,1040,1045,1048,1115,1183,2093],[97,143,226,624,1399,3088],[85,97,143,226,2093,3087],[97,143,226,624,1109,1214,1399,1653,2863,3103],[97,143,226,624,1109,1214,1399,2863,3103],[85,97,143,226,631,1035,1036,1037,1039,1044,1045,1048,1106,1109,1110,1214,1311,1452,1453,1484,1951,1954,2084,2363],[85,97,143,226,624,1399,1653,3089],[85,97,143,226,1035,1036,1037,1039,1043,1045,1048,1092],[97,143,226,1109,3104],[97,143,226,628,1106,1109,1214,1421],[85,97,143,226,624,628,1109,1214,1399,1421],[97,143,226,628,639,1106,1107,1109,1214],[97,143,226,628,1109,1214,1421],[85,97,143,226,624,628,1214,1318,1399,1426],[97,143,226,628,1106,1107,1109,1214,1318],[97,143,226,628,1107,1137,1214],[97,143,226,628,1107,1214],[97,143,226,628,1214],[97,143,226,624,1430],[97,143,226,1174,1176],[85,97,143,226,628,639,1107,1109,1174,1176,1214,1430,1454],[97,143,226,624,1399,1456],[97,143,226,639,1109,1340],[85,97,143,226,624,628,1399,1458],[85,97,143,226,624,628,1399,1460],[85,97,143,226,624,628,1399,1462],[85,97,143,226,624,628,1399,1464,1465],[97,143,226,628,1107,1214,1464],[97,143,226,624,1107],[85,97,143,226,624,628,1174,1176,1399,1454],[85,97,143,226,628,639,1174,1176,1452,1453],[97,143,226,630,1214],[97,143,226,628,1107,1109,1468],[97,143,226,628,1468,1470],[97,143,226,628,1469,1472],[97,143,226,628,1107,1109,1469],[97,143,226,628,1087,1107,1109,1214],[85,97,143,226,624,628,1214,1399,1477],[97,143,226,628,1107,1109,1214],[97,143,226,624,1399,1479],[97,143,226,639,1106,1109,1340],[85,97,143,226,624,628,1129,1214,1399],[97,143,226,624,1399,1482],[97,143,226,639,1340],[85,97,143,226,624,628,1214,1399,1485],[97,143,226,628,1109,1214,1215,1487],[85,97,143,226,624,628,1215,1399,1487],[97,143,226,628,1107,1109,1214,1215],[97,143,226,628,1109,1214,1487],[85,97,143,226,624,628,1214,1399,1491],[97,143,226,628,1109,1214],[85,97,143,226,624,628,1109,1214,1399,1497],[85,97,143,226,624,628,1109,1214,1399,1499],[85,97,143,226,628,1107,1109,1214],[85,97,143,226,624,628,1109,1214,1399,1501],[97,143,226,628,1086,1107,1109,1214],[97,143,226,628,639,1107,1109,1214],[97,143,226,628,639,1340,1505],[97,143,226,628,639,1106,1107,1109,1340],[85,97,143,226,624,628,1214,1399,1508],[85,97,143,226,624,628,1189,1214,1399],[85,97,143,226,624,628,1214,1399,1511],[97,143,226,628,1107,1108,1214],[85,97,143,226,624,628,1214,1218,1399],[85,97,143,226,624,628,1399,1514,1515],[97,143,226,628,1109,1214,1514],[85,97,143,226,624,628,1399,1514,1517],[85,97,143,226,624,628,1399,1514,1519],[97,143,226,628,1106,1109,1214,1514],[85,97,143,226,624,628,1399,1514],[97,143,226,628,1106,1107,1109,1214],[85,97,143,226,624,628,1399,1514,1522],[85,97,143,226,624,628,1214,1399,1524],[85,97,143,226,624,628,1399,1526],[97,143,226,628,1107,1221],[85,97,143,226,624,628,1399,1528],[97,143,226,628,1107,1109,1214,1531],[97,143,226,624,1399,1533],[97,143,226,624,1399,1535],[97,143,226,1109,1340,1533],[85,97,143,226,624,628,1214,1399,1537],[85,97,143,226,624,628,1214,1399,1539],[85,97,143,226,624,628,1109,1399,1541],[97,143,226,628,1109,1214,1526],[85,97,143,226,624,628,637,1214,1399,1544],[97,143,226,628,637,1107,1109,1214],[97,143,226,624,1546],[97,143,226,628,630,1107,1109,1214],[85,97,143,226,624,628,1214,1215,1216,1217,1399],[97,143,226,628,1106,1107,1109,1214,1215,1216],[85,97,143,226,624,628,1108,1214,1399],[85,97,143,226,624,628,1214,1399,1550,1551],[97,143,226,1550],[85,97,143,226,624,628,1214,1399,1550],[85,97,143,226,624,628,1214,1399,1554],[85,97,143,226,624,628,1109,1399],[85,97,143,226,624,628,634,636,1105,1109,1214,1399],[85,97,143,226,634,636,1105,1106,1108,1214],[97,143,226,1109,1219,1227],[85,97,143,226,1228],[97,143,226,624,1228,1229,1399],[97,143,226,624,1228,1233,1399],[97,143,226,624,1214,1219,1399],[97,143,226,1106,1109,1218],[97,143,226,634,1105,1222],[97,143,226,628,1214,1556],[85,97,143,226,624,628,1214,1399,1558],[85,97,143,226,624,628,1214,1399,1560],[97,143,226,624,1399,1419,1420],[85,97,143,226,518,1419],[85,97,143,226,1109,1215,1216],[97,143,226,624,1214,1399,2700,2811],[85,97,143,226,518,630,1206,1214,1224,2700,2711,2715,2717,2718,2719,2720,2721,2722,2810],[97,143,226,624,1242,1243],[97,143,226,1206],[97,143,226,1109,3171],[97,143,226,1109,3191],[85,97,143,226,1036,1039,1048,1084,1572,2066,2871],[97,143,226,624,633,1214,1399,1653,1654,3218],[97,143,226,624,1214,1399,1653,1654,3218],[85,97,143,226,530,631,633,1035,1036,1039,1045,1048,1082,1084,1086,1101,1106,1110,1214,1361,1562,1564,1565,1572,1574,2066,2871,3197,3198,3201,3202,3204,3205,3206,3207,3208,3209,3210,3211,3213,3214,3215,3216,3217],[97,143,226,624,632,1562],[97,143,226,632,1086],[97,143,226,624,1565],[97,143,226,1086,1564],[85,97,143,226,1039,1048,1084,1086,1094,1572],[97,143,226,1086,1567],[97,143,226,624,1086,1564,1565,1567,1568],[97,143,226,1086,1564,1565],[85,97,143,226,624,1082,1084,1399,1653,3215],[85,97,143,226,1035,1036,1038,1039,1045,1048,1082,1084,1572,1574,2066],[85,97,143,226,1036,1037,1039,1048,1084,1091,1572,2066,2871,3200],[97,143,226,624,1570],[85,97,143,226,631,1035,1037,1110,1214,1570,1956],[97,143,226,3232,3237],[85,97,143,226,624,1399,1653,3220],[85,97,143,226,1035,1039,1042,1092,1094,1193,1214,1348,1956],[85,97,143,226,624,1399,1653,3208],[85,97,143,226,1035,1039,1092,1101,1361,1956],[97,143,226,624,1086,1214,1399,1653,3229],[85,97,143,226,1034,1035,1038,1039,1086,1110,1177,1214,1351,3218],[97,143,226,624,1399,1653,3207],[85,97,143,226,1038,1039,1048,1086,1092,1101,1115],[97,143,226,624,1399,3223],[85,97,143,226,1086],[85,97,143,226,624,1086,1214,1399,3222],[85,97,143,226,624,631,632,1214,1399,1653,1654,3222],[85,97,143,226,631,632,633,1035,1036,1037,1039,1045,1048,1082,1084,1086,1091,1214,1348,1564,1567,1572,1574,1956,2066,2645,2871,3201,3202,3204,3205,3206,3207,3209,3210,3211,3214,3215,3216],[97,143,226,624,1086,1399,1653,3224],[85,97,143,226,632,1035,1039,1086,1092,1115,1193,1348,1564,3222,3223,3238],[85,97,143,226,624,628,1214,1399,1653,3232],[85,97,143,226,628,631,632,1035,1038,1039,1045,1048,1086,1106,1115,1214,1347,1348,1361,1499,1501,2351,3194,3196,3218,3219,3220,3221,3224,3226,3227,3228,3229,3230,3231],[85,97,143,226,624,1399,3209],[85,97,143,226,1034,1035,1036,1037,1038,1039,1092,1115,1183,1361,1564,1965],[97,143,226,624,628,633,1214,1399,3237],[85,97,143,226,628,632,633,1034,1035,1038,1039,1086,1092,1115,1214,1351,1361,2351,2645,3234,3235,3236],[97,143,226,624,1084,1572],[85,97,143,226,1082,1084],[85,97,143,226,624,1082,1084,1399,1574],[97,143,226,1082,1084],[85,97,143,226,1082,1084,1399],[85,97,143,226,624,1399,1653,3214],[85,97,143,226,530,1034,1038,1039,1048,1354],[97,143,226,624,1214,1399,1653,3228],[85,97,143,226,1035,1036,1039,1092,1115,1214,1361,2922],[85,97,143,226,624,1399,1653,3211,3245],[85,97,143,226,1035,1038,1039,1044,1048,1082,1084,1086,1091,1094,1101,1572,1574,1956,2066],[85,97,143,226,624,1086,1205,1399,3221],[85,97,143,226,1034,1035,1039,1048,1086,1115,1354,1362,1564],[97,143,226,624,1086,3193],[97,143,226,1086],[85,97,143,226,631,1039,1086,1214,3193],[85,97,143,226,624,628,1086,1214,1399,1501,1503,1653,3196],[85,97,143,226,628,631,1035,1036,1038,1039,1044,1086,1110,1174,1176,1188,1214,1311,1361,1501,1503,1954,2084,3195],[97,143,226,624,1086,1188,1399,1653,3195],[97,143,226,1034,1035,1039,1086,1174,1176,1188,1193,1204,1214,1362],[97,143,226,624,1575],[97,143,226,1086,1565],[85,97,143,226,624,1399,3201,3245],[85,97,143,226,1035,1036,1037,1039,1045,1048,1084,1086,1091,1572,2066,2871,3199,3200],[85,97,143,226,1036,1039,1048,1082,1084,1091,1094,1572],[85,97,143,226,1036,1039,1048,1084,1086,1572,1574,2066,3212],[97,143,226,624,1214,1399,1653,3212],[85,97,143,226,1034,1214,1361],[85,97,143,226,624,1399,3204,3245],[85,97,143,226,1035,1042,1084,1086,1183,1572,2871,3203],[85,97,143,226,1037,1039,1048,1084,1572,2066],[97,143,226,624,1399,1653],[85,97,143,226,1039,1045,1048,1084,1572],[85,97,143,226,1036,1039,1045,1048,1082,1084,1091,1572,2066,2871,3200],[85,97,143,226,1035,1036,1037,1039,1044,1045,1048,1082,1086,1361,1655,1954],[97,143,226,624,1086,1655],[97,143,226,1082,1086],[85,97,143,226,624,1086,1399,1653,2863,3234],[85,97,143,226,631,1035,1039,1048,1086,1351,1655,3233],[97,143,226,624,1086,1399,3202],[85,97,143,226,1039,1086,1956],[85,97,143,226,1036,1039,1048,1084,1572],[85,97,143,226,624,628,1086,1214,1399,1653,3231],[85,97,143,226,628,631,1035,1039,1044,1086,1110,1115,1203,1214,1311,1361,1954,1956,2084,2871],[97,143,226,624,1564],[97,143,226,1109,3238],[85,97,143,226,624,1214,1399,1653,3270],[85,97,143,226,1181,1214],[85,97,143,226,624,1214,1399,1653,3271],[85,97,143,226,1035,1036,1037,1039,1044,1048,1110,1214,1311,1954,2084],[85,97,143,226,624,1174,1176,1214,1399,1653,3273],[85,97,143,226,1039,1174,1176,1188,1214,3272],[97,143,226,1034,1035,1039,1174,1176,1204,1214,1362],[85,97,143,226,624,628,1174,1176,1214,1399,1653,3274],[85,97,143,226,628,631,1035,1039,1174,1176,1214,1452,1453,2834,3270,3271,3273],[97,143,226,624,1399,2863,3275],[97,143,226,1109,1414,2922,3146,3274],[97,143,226,1106,1109,3296,3297],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362,1505,1700],[85,97,143,226,1035,1039,1044,1048,1110,1311,1505,1507,1665,1700,1937,1954,2084],[97,143,226,624,1665],[97,143,226,1505,1507],[97,143,226,624,628,1109,1399,1653,3323,3325],[85,97,143,226,628,631,1039,1109,1174,1176,1189,1206,1214,1217,1452,1508,1673,2109,2834,3322,3323,3324],[97,143,226,624,1399,1653,2109,3324],[85,97,143,226,1034,1035,1039,1041,1045,1174,1176,1188,2109,2705,3323],[97,143,226,624,1667,1669],[97,143,226,1134,1189,1214,1667,1668],[97,143,226,624,1653,2863,3332],[85,97,143,226,631,1035,1039,1110,1189,1214,1662,1668,1669,2834,3329,3331],[85,97,143,226,1174,1176,1188,1204,1669,3330],[85,97,143,226,1034,1035,1039,1115,1174,1176,1188,1204,1362,1669,1671],[97,143,226,624,1671],[97,143,226,624,1399,1653,3363],[85,97,143,226,1035,1036,1039,1042,1045],[97,143,226,1035,1039,1094,1115,1174,1176,1188,1193,1204,2109,2817,3038,3306],[97,143,226,624,1399,3371],[85,97,143,226,1109,1508,3370],[97,143,226,624,1399,1659,1662],[85,97,143,226,1661],[97,143,226,624,628,1399,1653,3373],[85,97,143,226,628,1035,1039,1106,1109,1217,1230,1348,1550,1662,1664,1668,3299,3307,3321,3326,3333,3342,3347,3358,3362,3364,3366,3369,3372],[85,97,143,226,624,628,1399,1653,3369],[85,97,143,226,631,1039,1106,1109,1174,1176,1188,1505,1506,1507,2834,3367,3368],[97,143,226,624,1214,1653,2863,3342],[85,97,143,226,628,1082,1084,1109,1217,1352,1477,1508,3337,3341],[97,143,226,1662,1664,3324,3325],[97,143,226,624,1399,3333],[97,143,226,1106,1109,1217,1550,1668,3332],[97,143,226,624,1399,1659,3362],[85,97,143,226,1109,1174,1176,1189,1217,1508,1662,1673,3306,3361],[97,143,226,3346],[85,97,143,226,1109,1214,3365],[85,97,143,226,631,1109,1214,1530,1664,3363],[97,143,226,1109,3357],[97,143,226,3371],[85,97,143,226,1189],[97,143,226,624,1673],[85,97,143,226,624,1399,1653,2863,3386],[85,97,143,226,1035,1040,1045,1092,1178,1193,1201,1204,1214,1222,1227,1348,2282,2821,3384,3385],[97,143,226,1109,2922,3386],[85,97,143,226,624,628,1399,1659,3396,3398,3399],[85,97,143,226,628,631,1035,1189,1214,1218,1661,2834,3392,3395,3396,3398],[85,97,143,226,624,1214,1399,1653,3398],[85,97,143,226,1039,1174,1176,1188,1214,3397],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1214,1362],[97,143,226,624,1399,1653,3392],[97,143,226,1039,3389,3390,3391],[97,143,226,1109,3399],[97,143,226,624,1399,2852],[85,97,143,226,518,1105,1214,1243,2700,2718,2851],[85,97,143,226,1035,1039,1048,1101],[97,143,226,624,1399,1653,3500],[85,97,143,226,1034,1036,1039,1048,1183,1237],[85,97,143,226,624,1399,1653,1679,3522],[85,97,143,226,631,1035,1036,1037,1039,1045,1086,1090,1091,1214,1347,1348,1361,1372,1679,2352,3404,3521],[97,143,226,624,1399,1684,3509],[85,97,143,226,1684],[97,143,226,624,1399,3501],[85,97,143,226,1034,1035,1038,1039,1048],[97,143,226,1675],[85,97,143,226,506,1039,1684,3503],[85,97,143,226,631,1035,1039,1048,1677],[97,143,226,624,1684,3503],[97,143,226,1684],[97,143,226,624,1399,1675,1684,3517],[85,97,143,226,1039,1086,1369,1371,1675,1683,1684,1687,2766,3508,3509,3510,3511,3512,3513,3515,3516],[97,143,226,624,1090,1399,1653,2863,3403,3521],[85,97,143,226,631,632,1035,1036,1039,1041,1045,1048,1086,1090,1091,1110,1206,1214,1237,1369,1371,1414,1452,1675,1676,1677,1679,1684,1685,1688,1969,2843,2844,3230,3295,3403,3421,3422,3423,3424,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3512,3514,3517,3518,3519,3520],[97,143,226,624,1399,1653,3511],[85,97,143,226,1035,1039,1101,1214,1369,1371],[85,97,143,226,631,1039,1048,1094],[97,143,226,624,1399,1653,1676,3505],[85,97,143,226,1041,1676],[97,143,226,624,1090,1675,3506],[97,143,226,1090,1675],[97,143,226,624,1399,1653,3507],[97,143,226,1035,1039],[97,143,226,624,1399,1653,3520],[85,97,143,226,1035,1036,1039,1045,1214,1676],[85,97,143,226,1039,1684,3514],[85,97,143,226,1035,1039,1101,1684],[85,97,143,226,631,1035,1039,1048,1094,1675],[97,143,226,624,1677],[97,143,226,624,1399,1653,3403,3527],[85,97,143,226,631,1035,1036,1039,1045,1048,1090,1452,1453,1679,1680,1683,1684,3403,3421,3424,3502,3503,3525,3526],[97,143,226,624,1399,1653,1680,3525,3527],[85,97,143,226,1039,1043,1103,1183,1237,1680,1969,2843,3423,3523,3524,3527],[97,143,226,624,1399,1684,3523],[85,97,143,226,1039,1369,1371,1683,1684,2766,3510,3513,3516],[97,143,226,624,1399,1653,3526],[85,97,143,226,1035,1037,1039],[97,143,226,624,1399,1653,3546],[85,97,143,226,1036,1041],[97,143,226,624,1399,1653,1680,3524],[97,143,226,1040,1361,1680],[97,143,226,624,1679,1680],[97,143,226,1679],[85,97,143,226,1039,1214,1414,1689,1990,2349,2844,3403],[97,143,226,624,1399,1685],[85,97,143,226,1083,1086,1452,1683,1684],[85,97,143,226,1687],[97,143,226,1214,1684,3421],[97,143,226,624,1683,3492],[97,143,226,631,1086,1214,1682,1683,1684,2103,3491],[97,143,226,624,3037,3493],[97,143,226,631,1214,1676,3037],[97,143,226,624,3037,3494],[97,143,226,631,1214,3037],[97,143,226,624,3495],[97,143,226,631,1214],[97,143,226,624,1399,3528],[85,97,143,226,1109,1221,1348,2922,3404,3521,3522,3527],[85,97,143,226,624,1214,1399,1653,1689,2863,3563],[85,97,143,226,631,1035,1039,1043,1044,1048,1102,1109,1110,1214,1311,1361,1689,1690,1692,1954,2084,3561,3562],[97,143,226,624,1399,1653,1689,2863,3557],[85,97,143,226,631,1035,1036,1037,1039,1041,1043,1044,1048,1091,1102,1109,1110,1115,1203,1214,1311,1361,1689,1954,1956,2084,2093],[85,97,143,226,624,1399,1653,2863,3568],[85,97,143,226,1035,1036,1037,1039,1041,1048,1092,1110,1183,1214,1361],[85,97,143,226,624,1399,1653,1689,2863,3560],[85,97,143,226,1039,1174,1176,1188,1689,3559],[97,143,226,1034,1035,1039,1174,1176,1188,1193,1204,1362,1689,3558],[97,143,226,624,1690],[97,143,226,1689],[85,97,143,226,624,1399,1653,2863,3566],[85,97,143,226,1035,1039,1043,1110,1115,1183,1406],[85,97,143,226,624,1214,1653,1689,2863,3558],[85,97,143,226,1035,1039,1048,1115,1214,1237,1689],[97,143,226,624,1399,2863,3561],[85,97,143,226,1039,1115,1956],[85,97,143,226,624,1399,1653,2863,3569],[85,97,143,226,631,1035,1039,1106,1214,1348,1689,1956,2093,2354,2834,3554,3555,3556,3557,3560,3563,3564,3565,3566,3567,3568],[85,97,143,226,624,1399,1653,1689,2093,2863,3555],[85,97,143,226,631,1035,1036,1039,1041,1045,1214,1361,1689,2093,2349],[97,143,226,624,1214,1399,1653,1689,2863,3556],[85,97,143,226,1035,1039,1043,1092,1115,1177,1214,1689,1956,3555],[85,97,143,226,624,1214,1399,1653,2863,3565],[85,97,143,226,631,1035,1039,1092,1115,1177,1183,1214],[85,97,143,226,624,1214,1399,1653,2863,3564],[85,97,143,226,1035,1039,1040,1044,1082,1109,1115,1214,1361,1954,1956,3562],[85,97,143,226,624,1399,1653,1689,2863,3554],[85,97,143,226,1039,1174,1176,1188,1689,3553],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362,1689],[97,143,226,624,1692],[85,97,143,226,624,1399,1653,2863,3567],[85,97,143,226,1035,1036,1039,1041,1102,1110,1115,1214,1361],[97,143,226,1109,3569],[97,143,226,624,1514,1653,2863,3590],[85,97,143,226,1035,1039,1092,1115,1201,1203,1217,1235,1361,1519,2282,2818,3586,3589],[97,143,226,624,2863,3589],[85,97,143,226,1038,1039,1092,1174,1176,1487,3588],[97,143,226,624,1215,1653,2863,3588],[85,97,143,226,1039,1174,1176,1188,1215,3587],[97,143,226,1174,1176,1204,1207,1215,2818],[97,143,226,624,1214,1653,2863,3585],[97,143,226,624,1653,2863,3585],[85,97,143,226,631,1035,1039,1110,1361,1515,1694,1973,1974,2084],[97,143,226,624,1214,1514,1653,2863,3586],[97,143,226,624,1514,1653,2863,3586],[85,97,143,226,631,1035,1039,1110,1361,1514,1522,1694,1973,1974,2084],[85,97,143,226,624,1653,1694,1973,2084,2863],[85,97,143,226,1035,1036,1037,1038,1039,1041,1043,1044,1045,1082,1089,1094,1101,1109,1214,1215,1217,1694,1697,1954,1956,1972],[97,143,226,624,1973,1974],[97,143,226,1973],[97,143,226,624,1514,1653,1659,2863,3593],[85,97,143,226,1035,1038,1039,1217,1514,1661,2814,3585,3590,3592],[97,143,226,624,1514,1653,1659,2863,3592],[85,97,143,226,1039,1174,1176,1188,1514,1661,3591],[97,143,226,1039,1115,1174,1176,1177,1188,1204,1514],[97,143,226,1109,3593],[85,97,143,226,624,631,1214,1399,3610],[85,97,143,226,631,1035,1036,1039,1044,1045,1110,1214,1311,1361,1954,2084],[97,143,226,624,1214,1399,1653,2863,3630],[85,97,143,226,631,1035,1039,1045,1106,1214,1347,3607,3609,3610,3629],[97,143,226,1976,3628],[97,143,226,624,1399,3619],[85,97,143,226,1039],[97,143,226,624,1399,3624],[85,97,143,226,1035,1039,1979,1980,3618,3621,3622,3623],[97,143,226,624,1399,3620],[85,97,143,226,1039,1369,1371,1683,1979,2766],[97,143,226,624,1399,3623],[85,97,143,226,624,1399,3621],[85,97,143,226,1039,1979,3619,3620],[97,143,226,1683],[85,97,143,226,631,1214,1683,1977,1979],[97,143,226,624,1399,3618],[97,143,226,624,1399,3616],[85,97,143,226,1092,3615],[85,97,143,226,1976,1977],[85,97,143,226,631,1214,1976,1977,3611,3612,3613,3614,3616,3617,3624,3625,3626,3627],[97,143,226,624,1399,3613],[85,97,143,226,1035,1036,1039,1110,1933],[97,143,226,624,1399,1653,3608],[85,97,143,226,631,1035,1039,1045,1110,1348,1369,1371],[97,143,226,624,1399,3612],[85,97,143,226,1035,1036,1039,1045,1115,3608],[97,143,226,624,1399,3617],[85,97,143,226,1035,1039,1045,1092,1976,3615],[97,143,226,624,1399,3625],[85,97,143,226,1035,1036,1039,1110],[97,143,226,624,1399,1976,3614],[85,97,143,226,1035,1039,1092,1976],[97,143,226,624,1976,1977],[97,143,226,1976],[97,143,226,624,1110,1214,1653,2863,3627],[85,97,143,226,1035,1039,1115,1177,1214],[85,97,143,226,624,1214,1399,1653,3609],[85,97,143,226,631,1035,1039,1092,1110,1115,1178,1193,1214,1348,3605,3608],[97,143,226,1214,1977],[97,143,226,624,1214,1399,1653,3607],[85,97,143,226,1039,1174,1176,1188,1214,3605,3606],[97,143,226,1034,1035,1039,1174,1176,1188,1193,1204,1214,1352,1362,3605],[97,143,226,624,1399,3611],[85,97,143,226,1035,1110],[97,143,226,624,1399,3615],[85,97,143,226,1035,1036,1037,1039,1115,1237],[97,143,226,1109,2922,3630],[97,143,226,624,1214,1653,2863,3046],[85,97,143,226,1035,1036,1038,1039,1045,1092,1094,1178,1204,1214,1348,2960,3040,3045],[97,143,226,1109,3046],[97,143,226,624,628,1214,1399,1653,3655],[97,143,226,624,1399,3655],[85,97,143,226,530,628,631,1035,1036,1037,1039,1040,1044,1048,1082,1106,1110,1214,1311,1354,1361,1954,1981,1982,2084,2871,3654],[97,143,226,3660],[97,143,226,624,631,1214,1399,1653,3654],[85,97,143,226,631,1035,1039,1043,1214,1361],[97,143,226,624,1982],[97,143,226,1981],[97,143,226,624,628,1106,1214,1399,1653,1981,3660],[85,97,143,226,628,631,1035,1036,1037,1044,1045,1106,1110,1214,1311,1361,1954,1981,1982,2084,2834,2871,3655,3657,3659],[97,143,226,624,1399,1653,1981,2863,3657],[85,97,143,226,1039,1174,1176,1188,1981,3656],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362,1981],[97,143,226,624,631,1214,1399,1653,3658],[85,97,143,226,631,1035,1036,1039,1092,1214,1361],[97,143,226,624,1193,1399,1653,1981,3659],[85,97,143,226,1035,1039,1092,1193,1981,3658],[97,143,226,1109,3661],[85,97,143,226,624,631,1214,1399,2863,3671],[85,97,143,226,631,640,1035,1036,1037,1039,1040,1044,1048,1110,1214,1311,1361,1954,2064,2084],[97,143,226,624,640,1214,1399,1653,3674],[85,97,143,226,631,640,1035,1106,1214,1347,3292,3671,3673],[97,143,226,624,640,1399,1653,3673],[85,97,143,226,640,1039,1174,1176,1188,3672],[97,143,226,640,1034,1035,1039,1115,1174,1176,1188,1193,1204,1362,2064],[97,143,226,1109,3674],[97,143,226,624,1399,1653,3682],[85,97,143,226,1035,1036,1037,1039,1044,1048,1091,1101,1110,1311,1700,1937,1954,2084],[97,143,226,624,1214,1399,1653,3683],[85,97,143,226,631,637,1035,1039,1214,2834,3679,3681,3682],[97,143,226,624,637,1214,1399,1653,3679],[85,97,143,226,631,637,1035,1036,1037,1039,1044,1048,1089,1091,1092,1101,1115,1193,1214,1311,1700,1937,1954,1972,2084],[97,143,226,624,637,1204,1399,1653,3681],[85,97,143,226,637,1039,1174,1176,1188,3680],[97,143,226,637,1034,1035,1039,1115,1174,1176,1188,1204,1362],[97,143,226,1109,3683],[97,143,226,1109,3695],[97,143,226,1109,3702],[97,143,226,1109,3704],[97,143,226,624,631,1214,1399,1653,3704],[85,97,143,226,631,1035,1037,1039,1092,1214,1361],[97,143,226,1109,3707],[97,143,226,624,631,1399,1653,3707],[85,97,143,226,631,1035,1036,1042,1092,1214,1224,1361],[97,143,226,624,1332,1399,2863,3716],[85,97,143,226,1092,1332,2282],[97,143,226,624,1332,1399,2863,3717],[97,143,226,624,2863,3718],[85,97,143,226,1174,1176,1188,1201,1204,1332],[97,143,226,624,1399,3719],[85,97,143,226,1332,3716,3717,3718],[85,97,143,226,624,1214,1399,1560,1653,2815,3723],[85,97,143,226,1039,1048,1092,1174,1176,1188,1193,1204,1214,1227,1332,1341,1348,1354,1987,1988,2006,2007,2014,2038,2282,2815,2826,2827,2957,3385,3711,3719,3720,3721,3722],[97,143,226,1332,1986],[97,143,226,624,1988],[97,143,226,1193],[97,143,226,624,1399,3724],[85,97,143,226,1039,1048,1092,1094,1174,1176,1188,1193,1204,2282,3038,3713],[97,143,226,624,1214,1991],[97,143,226,1214,1990],[85,97,143,226,628,1035,1039,1092,1174,1176,1188,1204,1214,1991],[97,143,226,624,1399,1653,3721],[85,97,143,226,1188,1193,1204,1348,2282],[97,143,226,624,1984],[97,143,226,624,1399,2863,3725],[85,97,143,226,1035,1037,1040,1214,1361,2766],[85,97,143,226,624,1109,1214,1219,1399,1426,1479,1558,1560,1653,2863,3727],[85,97,143,226,637,1035,1039,1048,1092,1106,1109,1193,1214,1215,1219,1227,1332,1341,1348,1426,1479,1558,1956,1984,1986,2006,2014,2038,2282,2821,2827,2957,3384,3385,3711,3712,3713,3715,3719,3720,3721,3723,3724,3725,3726],[97,143,226,624,1399,1653,2863,3726],[85,97,143,226,1039,1045,1106,1115,1227],[97,143,226,624,1332,1341,1399],[85,97,143,226,1332],[97,143,226,1109,1217,1218,3727],[97,143,226,624,631,1214,1653,2863,3740],[85,97,143,226,631,1043,1091,1092,1109,1110,1178,1183,1204,1214,1937,3739],[85,97,143,226,624,628,631,1399,1653,1995,2854,3742],[85,97,143,226,628,631,1035,1036,1041,1044,1045,1082,1092,1177,1217,1340,1696,1954,1994,1995,2084,2854],[97,143,226,624,1994,1995],[97,143,226,639,1311,1994],[97,143,226,624,1994],[97,143,226,3746],[97,143,226,624,1214,1399,1653,2863,3739],[85,97,143,226,1035,1036,1037,1039,1044,1045,1046,1047,1048,1089,1091,1106,1183,1311,1700,1954,1963,1967,2084,2097,2101],[85,97,143,226,624,628,1399,1653,2863,3746],[85,97,143,226,628,631,1035,1106,1174,1176,1177,1214,1348,1452,1453,1661,1958,1959,2834,3740,3741,3742,3744,3745],[97,143,226,624,1399,1653,3745],[97,143,226,624,1399,1653,2106,3745],[85,97,143,226,631,1035,1039,1040,1044,1045,1048,1092,1106,1109,1110,1178,1193,1207,1214,1348,1501,1503,1700,1958,2106,2816,2834,2838,3739],[85,97,143,226,624,1174,1176,1214,1399,1653,3744],[85,97,143,226,1036,1039,1041,1174,1176,1188,1214,3743],[97,143,226,1034,1035,1039,1115,1174,1176,1188,1193,1204,1214,1362],[97,143,226,1109,1217,3747],[97,143,226,624,1090,1214,1399,1653,3765],[97,143,226,624,1214,1399,1653,3765],[85,97,143,226,631,1035,1036,1037,1039,1044,1045,1048,1092,1138,1214,1354,1361,1956,3421,3756,3763,3764],[97,143,226,624,1138,1399,1653,3763],[85,97,143,226,1039,1138,1188,3762],[97,143,226,1034,1035,1039,1138,1174,1176,1193,1204,1362],[97,143,226,624,1214,1399,1653,3767],[85,97,143,226,631,1035,1039,1106,1138,1210,1214,1348,2352,2834,3758,3759,3761,3765,3766],[85,97,143,226,631,1138,1209,1214],[97,143,226,624,1209,1210,1399,1653],[85,97,143,226,1039,1174,1176,1188,1208,1210],[97,143,226,1174,1176,1188,1204,1207,1210],[97,143,226,624,1090,1399,3764],[85,97,143,226,1036,1039,1040,1044,1048,1090,1956],[97,143,226,624,1138,1399,1653,3766],[85,97,143,226,1040,1092,1138,3760],[97,143,226,624,631,1214,1399,1653,3761],[85,97,143,226,624,1214,1399,1653,3761],[85,97,143,226,631,1035,1036,1037,1039,1040,1044,1045,1048,1092,1115,1138,1214,1311,1348,1352,1354,1954,2084,3756,3760],[97,143,226,624,631,1214,1399,1653,3759],[97,143,226,624,1214,1352,1399,1653,3756,3759],[85,97,143,226,631,1035,1036,1037,1038,1039,1040,1044,1045,1048,1082,1090,1110,1214,1311,1354,1954,1956,2084,3756],[97,143,226,624,1138,1399,1653,3758],[85,97,143,226,1039,1138,1174,1176,1188,3757],[97,143,226,1034,1035,1039,1138,1174,1176,1188,1193,1204,1362,3756],[97,143,226,624,631,1214,1399,1653,3760],[85,97,143,226,631,1035,1037,1039,1043,1092,1214,1361],[97,143,226,1109,3767],[97,143,226,624,1399,2863,3784],[97,143,226,1109,1414,2922,3146,3782],[97,143,226,624,1399,1653,3782],[85,97,143,226,1034,1035,1036,1039,1045,1048,1101,1174,1176,1181,1188,1214,1361],[97,143,226,2056,3792],[97,143,226,2056,3794],[85,97,143,226,2056,3798],[97,143,226,624,1399,3786],[85,97,143,226,518,1109,1206,1224,1550,2056,2061,2717],[97,143,226,2056,3800],[85,97,143,226,624,1399,2055,3790],[85,97,143,226,518,631,1035,1036,1039,1090,1177,1225,1237,1352,1683,2054,2056,2061,3498,3788,3789],[97,143,226,2056,3802],[97,143,226,624,1399,3804],[97,143,226,1109,1224,2717],[97,143,226,624,1399,3806],[85,97,143,226,1109,3798],[97,143,226,526,529,1370,2698,2699,2700,2701,2702],[97,143,226,624,628,1108,1214,1399,1653,3808],[97,143,226,624,628,634,636,1108,1214,1399,3808],[85,97,143,226,518,634,636,1035,1036,1039,1044,1045,1048,1092,1105,1108,1214,1311,1361,1494,1954,1956,2084,2353,2718,2871],[97,143,226,3808],[85,97,143,226,518,632],[85,97,143,226,518,3296],[85,97,143,226,518,3297],[85,97,143,226,624,1399,3815],[85,97,143,226,1035,1039,1105,1956],[85,97,143,226,624,1399,3819],[85,97,143,226,518,634,635,1214,1511,3815,3817,3818],[97,143,226,624,1399,1653,3818],[85,97,143,226,624,1399,1653,3818],[85,97,143,226,1034,1035,1036,1039,1044,1092,1311,1361,1954,1956,2084,2871],[85,97,143,226,624,1399,3817],[85,97,143,226,1361],[85,97,143,226,518,3819],[85,97,143,226,624,1193,1215,1332,1399,3711],[85,97,143,226,1039,1092,1101,1193,1215,1332,1986,2005,2038,2282,3710],[85,97,143,226,1036,1042,1092,1094,1102,1103,1132],[97,143,226,624,631,1090,1136,1137,1189,1214,1653,2356,2695,2863,3327,3329],[85,97,143,226,628,631,1035,1036,1039,1044,1045,1048,1082,1090,1092,1106,1110,1127,1128,1130,1132,1133,1134,1135,1136,1137,1189,1214,1311,1361,1428,1668,1951,1954,2040,2045,2084,2085,3300,3309,3327,3328],[97,143,226,624,1082,1084,1109,1214,1215,1352,1653,2863,3341],[85,97,143,226,1035,1039,1041,1044,1045,1048,1082,1084,1092,1094,1106,1109,1110,1129,1214,1215,1352,1524,1544,1668,1951,1956,2066,2085,3038,3309,3334,3335,3336,3338,3339,3340],[97,143,226,624,1399,3334,3953],[85,97,143,226,637,1036,1037,1039,1045,1048,1083,1084,1091,1094,1101,1215,1551,1969,2059,2066,2656,3303,3304,3309],[85,97,143,226,1036,1094,1132],[97,143,226,624,1214,2045,2863,3300],[85,97,143,226,1039,1214,2045],[97,143,226,624,1137,1189,2040],[97,143,226,1090,1132,1137,1189],[97,143,226,624,1136,1214,1653,2695,2863,3328],[85,97,143,226,1035,1037,1039,1115,1136,1211,1214,2043],[97,143,226,1136,2043],[97,143,226,1136,1214],[97,143,226,2045],[97,143,226,1133,1136],[97,143,226,1090,1093,1132,1133,1134,1135],[97,143,226,1130],[97,143,226,624,1399,1653,3304],[85,97,143,226,1035,1039,1042,1045,1048,1937],[85,97,143,226,1036,1039,1041,1042,1045,1048,1091,1092,1094,1097,1102,1112,1113,1114,1117,1118,1119,1120,1132,1134],[85,97,143,226,1036,1042,1094,1132],[97,143,226,624,1112,1132,1653,2695,2863],[85,97,143,226,631,1035,1037,1039,1109,1110,1111,1132,1214],[97,143,226,1111],[97,143,226,1039,1045,1048,1134],[85,97,143,226,1036,1042,1094,1136],[97,143,226,624,1093],[97,143,226,1135],[97,143,226,624,1132,1133,1134],[97,143,226,1090,1133,1135],[85,97,143,226,624,1132,1653,2695,2863],[85,97,143,226,1035,1036,1037,1038,1039,1041,1043,1048,1090,1091,1092,1094,1095,1096,1097,1098,1099,1100,1101,1104,1116,1121,1122,1123,1124,1125,1126,1127,1128,1130,1131,1133,1134,1135],[85,97,143,226,1039,1041,1042,1048,1102,1129,1130],[85,97,143,226,624,1082,1084,1399,1653,3335,3953],[85,97,143,226,1036,1048,1082,1084,1174,1176,1188,1352,2066],[85,97,143,226,1039,1048,1091],[97,143,226,631,1130,1136,1214],[97,143,226,624,3337],[97,143,226,631,1214,1352,2654],[97,143,226,624,1116,1132],[97,143,226,624,1114,1116,1117,1121,1132,1653,2695,2863],[85,97,143,226,1035,1036,1039,1042,1101,1103,1114,1115,1116,1132],[85,97,143,226,1035,1039,1045,1048,1091,1092,1093,1134],[97,143,226,624,1352,1399,3336,3953],[85,97,143,226,1036,1082,1084,1091,1352,2066,3309],[85,97,143,226,1094,1132],[97,143,226,624,631,1214,1399,3337,3338],[85,97,143,226,631,1035,1039,1043,1214,3337],[97,143,226,624,1113,1653,2863],[85,97,143,226,1035,1037,1045,1109,1110,1132,1133,1214],[97,143,226,624,628,1082,1084,1352,1399,3339,3953],[85,97,143,226,1035,1036,1037,1039,1045,1082,1084,1214,1352,1524,2066,2871,3309],[97,143,226,624,1399,1653,2087],[85,97,143,226,1035,1036,1037,1039,1041,1042,1043,1048,1090,1092,1101,1115],[97,143,226,624,1128,1653,2863],[85,97,143,226,1036,1039,1041,1048,1090,1094],[97,143,226,624,1124,1132,2863],[97,143,226,624,1133],[97,143,226,1132,1134,1135],[97,143,226,624,1125,1132],[97,143,226,1132,1133,1134,1135],[85,97,143,226,1097,1132],[97,143,226,624,1126],[85,97,143,226,1039,1045,1048,1134],[85,97,143,226,1133],[97,143,226,624,1214,1653,2863,3353],[85,97,143,226,631,1035,1036,1039,1045,1048,1082,1092,1094,1110,1214,1311,1361,1937,1954,1956,2084,3348,3349,3350,3351,3352,3357],[97,143,226,624,1399,1653,1698],[85,97,143,226,1091,1214],[97,143,226,624,1188,1399,1653,3281],[97,143,226,1034,1035,1039,1115,1174,1176,1188,1193,1204,1362],[97,143,226,624,1214,1399,3281,3282],[85,97,143,226,631,1034,1035,1039,1110,1115,1183,1214,3281],[97,143,226,624,1214,1399,3283,3284],[85,97,143,226,631,1034,1035,1039,1110,1115,1183,1214,3283],[97,143,226,624,1214,1399,3286],[85,97,143,226,631,1034,1035,1039,1110,1115,1183,1214,3285],[97,143,226,624,1188,1399,1653,3283],[97,143,226,624,1214,1653,2863,3297],[85,97,143,226,634,636,640,1035,1038,1039,1092,1105,1106,1110,1115,1174,1176,1188,1193,1214,1348,1369,1371,1550,2662,3281,3282,3283,3284,3285,3286,3287,3290,3293,3294,3296],[97,143,226,624,1188,1399,1653,3287],[85,97,143,226,640,1038,1039,1045,1174,1176,1188,3291,3292],[97,143,226,624,640,1188,1399,1653,3291],[97,143,226,640,1034,1035,1039,1115,1174,1176,1188,1193,1204,1362],[97,143,226,624,631,1214,1399,1653,3290],[85,97,143,226,508,631,1092,1106,1178,1214,1932,3289],[85,97,143,226,630,631,1214,3161],[85,97,143,226,624,1399,1653,3161],[85,97,143,226,1035,1036,1039,1082,1094,1115,1178],[97,143,226,624,1229,1230,1399],[97,143,226,1115,1229],[97,143,226,624,1399,1653,3741],[85,97,143,226,631,1035,1039,1110,1178,1214,1932,1957,1990],[85,97,143,226,1035,1039,1048,1101,1369,1371,1683,2054,2766,2809,3512,3513],[97,143,226,624,1205,2061],[97,143,226,624,1399,2061],[85,97,143,226,518,1035,1039,1043,1206,2056,2060],[97,143,226,624,1214,1399,3797],[85,97,143,226,1039,1214,3796],[97,143,226,624,628,1214,1399,3796,3798],[85,97,143,226,518,628,1214,3796,3797],[85,97,143,226,1035,1036,1039,1048,1110,1225,1347,2054,2059],[85,97,143,226,628,631,1035,1036,1039,1042,1110,1115,1177,1178,1214,1215,1957,2638],[97,143,226,624,1214,1399,2863,3800],[85,97,143,226,628,1035,1039,1110,1177,1178,1214,2303],[85,97,143,226,624,628,1086,1205,1214,1399,3796],[85,97,143,226,628,631,632,1035,1036,1039,1086,1177,1214,1348,1354,3236],[85,97,143,226,624,1086,1205,1214,1399,3789],[85,97,143,226,631,1039,1086,1094,1177,1214,1354],[85,97,143,226,628,631,1035,1039,1115,1177,1178,1214,1347],[97,143,226,1086,1683],[85,97,143,226,628,1035,1039,1177,1214],[97,143,226,624,1399,2055],[85,97,143,226,2054],[97,143,226,624,1675,3295],[97,143,226,1086,1675,1684],[85,97,143,226,1034,1039,1086,1101],[97,143,226,624,1399,3513],[85,97,143,226,1035,1039,1101,1369,1371,2766],[97,143,226,624,1399,1683],[85,97,143,226,1039,1048,1682],[97,143,226,624,640,2064],[97,143,226,640],[85,97,143,226,631,640,1034,1035,1039,1110,1115,1183,1214],[85,97,143,226,640,1034,1039,2064],[85,97,143,226,624,1399,1653,3712],[85,97,143,226,631,1035,1036,1039,1044,1045,1110,1214,1311,1361,1954,1956,2084,2871],[97,143,226,624,628,1399,3168],[85,97,143,226,628,1092,1107,1109,1465,3163,3165,3167],[97,143,226,624,628,1399,1653,3165],[97,143,226,624,628,1399,3165],[85,97,143,226,631,1035,1036,1044,1048,1109,1110,1311,1458,1954,2003,2084,3164],[97,143,226,624,1399,3163],[85,97,143,226,1038,1039,1048],[97,143,226,624,628,1399,1464,3167],[85,97,143,226,631,1035,1039,1043,1092,1109,1115,1347,1460,1462,1464,1465,1956,2834,3166],[97,143,226,624,2003],[97,143,226,624,628,1399,1464,1653,3166],[97,143,226,624,628,1399,1464,3166],[85,97,143,226,631,1035,1036,1044,1048,1109,1110,1311,1464,1465,1954,2003,2084,3164],[85,97,143,226,1039,1369,1371],[85,97,143,226,1039,1091,1177,1421],[85,97,143,226,1204,1932],[85,97,143,226,1036,1037,1039,1045,1048,1082,1083,1084,1214],[97,143,226,624,1399,2818],[97,143,226,1115],[97,143,226,624,1653,2834,2863],[85,97,143,226,1035,1038,1039,1092,1110,1956],[97,143,226,624,1399,3389],[85,97,143,226,1034,1038,1039,1452,1453],[97,143,226,624,1399,1653,3390],[85,97,143,226,1034,1035,1039],[97,143,226,624,1399,1653,3391],[85,97,143,226,1035,1039],[97,143,226,624,1399,1932,3288],[85,97,143,226,1034],[97,143,226,624,1399,1653,3289],[97,143,226,1048,1932,3288],[85,97,143,226,624,1082,1653,1701,2863],[85,97,143,226,1036,1039,1043,1045,1048,1094,1183],[97,143,226,624,1399,2819],[85,97,143,226,1034,1195,1235,2818],[97,143,226,624,1399,2718],[97,143,226,1034,1361],[85,97,143,226,1035,1039,1048,1178,1204,1214,3289],[85,97,143,226,624,1311,1399,1546,1653,2084,3311],[85,97,143,226,1035,1036,1039,1082,1177,1311,1546,1954],[97,143,155,164,226,624,1399,1653,1934],[85,97,143,226,631,1035,1036,1092,1178,1932,1933],[97,143,226,624,1399,1653,1933,2863],[85,97,143,226,1036,1039,1041,1090,1452],[85,97,143,226,624,1082,1084,1399,1653],[85,97,143,226,1044,1082],[97,143,226,624,1229,1399,3982],[97,143,226,624,1399,1653,1952],[85,97,143,226,1041,1214],[97,143,226,624,1214,1399,1653,3352],[85,97,143,226,1035,1039,1044,1048,1092,1697,1956,2843],[85,97,143,226,1092,1094],[85,97,143,155,164,226,624,1939,2863],[85,97,143,226,1115,1938],[85,97,143,226,1041,1514],[97,143,226,624,1653,1940,2863],[85,97,143,226,1039,1045,1048],[85,97,143,226,624,628,1090,1399,1945,1950],[85,97,143,226,628,1090,1214,1348,1452,1945,1947,1948,1949],[97,143,226,624,2067],[97,143,226,1950],[97,143,226,624,1399,2835],[97,143,226,1115,2067],[85,97,143,226,624,628,1399,1945,1947,1950,2067],[85,97,143,226,1178],[97,143,226,624,1215,1399,1653,1951,2863],[85,97,143,226,1215,1217,1696],[85,97,143,226,624,1217,1399,1653,2826],[85,97,143,226,1041,1217,2825],[97,143,226,624,1214,1399,1453,1653,3308],[85,97,143,226,1035,1039,1044,1045,1048,1082,1110,1214,1361,1696,1954,1956],[97,143,226,624,1214,1399,1560,1653,2827],[85,97,143,226,1041,1214,1560,1696],[97,143,226,624,628,631,1214,1218,1399,1653,1959,2673],[85,97,143,226,628,631,1035,1036,1037,1039,1044,1045,1048,1082,1089,1091,1101,1110,1183,1214,1218,1951,1954,1956,1958],[97,143,226,624,1399,2022,2715],[97,143,226,518,634,1105,1233,1242,2353,2704,2705,2706,2707,2709,2710,2712,2713,2714],[97,143,226,624,1220,2720,2863],[85,97,143,226,1039,1220,1956],[97,143,226,624,1399,1487,1653,2863,3175],[85,97,143,226,1039,1109,1174,1176,1487,1956,3174],[97,143,226,624,1399,1487,1653,2863,3174],[85,97,143,226,1039,1174,1176,1188,1487,3173],[97,143,226,1174,1176,1188,1204,1487],[97,143,226,624,1217,1399,1653,2863,3178],[85,97,143,226,1039,1109,1174,1176,1188,1217,1956,3177],[97,143,226,624,1217,1399,2863,3177],[85,97,143,226,1039,1174,1176,1188,1217,3176],[97,143,226,1174,1176,1188,1204,1217],[97,143,226,624,1399,1653,2922],[85,97,143,226,508,1039],[97,143,226,624,2088],[97,143,226,2088],[85,97,143,226,631,1035,1036,1039,1044,1048,1090,1093,1110,1116,1128,1130,1132,1133,1134,1135,1136,1214,1311,1361,1667,1954,2084,2085,2086,2087],[85,97,143,226,624,1399,1653,2091,2863],[85,97,143,226,614,631,638,1035,1043,1092,1177,1183,1214],[97,143,226,638,2091],[97,143,226,614],[85,97,143,226,624,1399,1653,2863,3159],[85,97,143,226,631,1035,1038,1039,1092,1214,2092],[97,143,226,624,1399,1653,2011,2012,2863],[85,97,143,226,631,1035,1039,1110,1177,1217,2005,2007,2008,2009,2010,2011],[97,143,226,624,2008,2863],[85,97,143,226,1045,2007],[97,143,226,2009,2863],[85,97,143,226,2006],[97,143,226,624,1653,2010,2863],[85,97,143,226,1102,2007],[97,143,226,2007,2012,2013],[97,143,226,1215,2006],[97,143,226,624,1653,2007,2013,2863],[85,97,143,226,1035,1039,1040,1215,2006,2007,2012],[97,143,226,624,1990,2006,2007,2011],[97,143,226,1193,1986,1990,2006,2007],[97,143,226,624,1214,1399,2843],[85,97,143,226,1091,1214,2093],[97,143,226,624,2863,3312],[85,97,143,226,1034,1039,1092,1115],[85,97,143,226,1039,1237,2017],[85,97,143,226,628,1035,1039,1214,1361,2016,2301,2303,2342],[85,97,143,226,2863,3138],[85,97,143,226,2017],[97,143,226,624,2017],[85,97,143,226,624,1368,1399,1653,2863],[97,143,226,624,2292],[97,143,226,624,1197],[97,143,226,624,1399,1653,1960],[85,97,143,226,1035,1039,1041,1091],[85,97,143,226,1035,1038,1045],[97,143,226,624,1089],[97,143,226,624,2095],[97,143,226,1214,1215],[85,97,143,226,614,1046,1047,1214],[97,143,226,624,1046,2863],[97,143,226,624,1046],[85,97,143,226,1035,1038,1039,1041,1044,1045],[97,143,226,624,2097],[97,143,226,1046],[85,97,143,226,624,1399,1653,1962],[85,97,143,226,1035,1036],[97,143,226,624,2099],[97,143,226,1215],[97,143,226,1046,2097,2101],[85,97,143,226,624,1399,1653,3348],[85,97,143,226,1035,1036,1039],[97,143,226,624,1106,1242,1399,2863],[85,97,143,226,508,518,1034,1035,1039,1106,1109,1115,1206,1214,1217,1219,1220,1223,1224,1225,1226,1227,1230,1238,1241],[85,97,143,226,624,1214,1399,2722],[85,97,143,226,1035,1039,1214,1239,1240,1956],[97,143,226,624,1683,3403],[97,143,226,1086,1214,1682,1683,1684,2991,3037],[97,143,226,624,1090,1214],[97,143,226,1089,1214],[97,143,226,624,1086,2103],[97,143,226,624,1683,1684,3498],[97,143,226,631,1086,1214,1682,1683,1684,1687,3037],[97,143,226,624,1399,2836],[85,97,143,226,1115,1354,1932,1936],[97,143,226,624,1086,1966],[97,143,226,1086,1311],[97,143,226,1086,1088],[97,143,226,624,1088,1399,1497,1501,1503,1653,1963,2863],[85,97,143,226,1088,1091,1497,1501,1503],[85,97,143,226,624,1086,1088,1214,1399,1653,1967,2863],[85,97,143,226,1086,1088,1102,1214,1361,1497,1501,1503,1964,1965,1966],[97,143,226,624,631,1086,1213,1399,1653,3230],[85,97,143,226,630,631,1039,1086,1094,1110,1340,2871],[85,97,143,226,1039,1183,1964],[85,97,143,226,624,1086,1399,1653,3422],[85,97,143,226,1036,1037,1039,1044,1045,1048,1082,1086,1954],[97,143,226,624,1086],[97,143,226,624,1352,2107],[97,143,226,624,628,1214,1352,1399,3343],[85,97,143,226,1035,1036,1041,1048,1082,1084,1110,1214,1352,1354,2066,2107,3309,3339],[97,143,226,624,628,631,1214,1399,1653,3346],[85,97,143,226,631,1035,1039,1106,1109,1214,1477,2644,2834,3343,3345],[97,143,226,624,1214,1399,1653,3345],[85,97,143,226,1039,1174,1176,1188,1214,3344],[97,143,226,1034,1035,1039,1174,1176,1188,1193,1204,1214,1352,1362],[97,143,226,624,1214,1653,2863,3301],[85,97,143,226,1035,1036,1044,1048,1110,1214,1311,1954,2084],[85,97,143,226,624,1174,1176,1399,1653,3361],[85,97,143,226,1035,1110,1174,1176,1214,1215,2376,3359,3360],[85,97,143,226,624,1174,1176,1399,1653,3359,3360],[85,97,143,226,1039,1174,1176,1188,1215,3359],[97,143,226,1034,1039,1174,1176,1188,1204,1215],[97,143,226,624,631,1399,1526,1541,1653,2842,2863,3322],[85,97,143,226,631,1035,1039,1044,1048,1082,1094,1110,1177,1526,1541,1954,2842],[85,97,143,226,1092,2662],[85,97,143,226,631,1092,1178,1214,1932],[85,97,143,226,624,628,631,1214,1399,1653,2695,3307],[85,97,143,226,628,631,637,1035,1039,1048,1083,1092,1110,1134,1189,1193,1214,1217,1348,1354,1508,1551,1667,1668,1673,1932,2045,2088,2644,2657,2834,3300,3301,3302,3305,3306],[85,97,143,226,637,1035,1036,1037,1039,1045,1048,1082,1094,1115,1214,1311,1361,1697,1937,1954,1969,2059,2083,2644,2654,2656,3303,3304],[97,143,226,624,1189,1214,1217,1218,1399,1558,1653,2854,2863],[97,143,226,1040,1048,1177,1189,1214,1217,1218,1558,2019],[97,143,226,624,2019],[97,143,226,624,1399,3299],[85,97,143,226,624,1352,1354,1399],[85,97,143,226,1034,1351,1352,1353],[85,97,143,226,624,1352,1399,3038],[85,97,143,226,1354],[85,97,143,226,631,1035,1038,1039,1092,1214],[85,97,143,226,624,634,1228,1653,2717,2863],[85,97,143,226,508,634,1034,1039,1105,1115,1206,1214,1220,1222,1224,1232,1233,2353,2706,2707,2709,2710,2712,2713,2714,2716],[97,143,226,624,1653,2706,2863],[85,97,143,226,1035,1039,1231,1362,1429,2022],[97,143,226,624,2709,2863],[85,97,143,226,1034,1035,1039,1048,1233,2708],[97,143,226,624,1399,2022,2707],[85,97,143,226,1039,2022],[97,143,226,624,1234],[85,97,143,226,1653,2710,2863],[85,97,143,226,1034,1035,1039,1115,1228,1237,1417],[97,143,226,624,1228,1653,2716,2863],[85,97,143,226,1034,1039,1043,1048,1094,1109,1115,1228,1231,1232,1233,1234,1235,1236,1237],[97,143,226,624,1399,2712],[85,97,143,226,518,1039,1206,1362,1550,2711],[97,143,226,624,1399,1653,2714],[85,97,143,226,1038,1039,1040,2353],[97,143,226,624,631,634,1206,1214],[97,143,226,630,631,634,636,637,638,639,640,1046,1047,1085,1086,1087,1088,1136,1137,1205,1210,1211,1212,1213,1215],[97,143,226,624,628,1220,2721,2863],[85,97,143,226,1039,1220],[85,97,143,226,1047,2120,2837,2838,2839],[97,143,226,624,1958],[85,97,143,226,631,1035,1110,1957],[97,143,226,624,631,1214,1215,1653,1972,2863],[97,143,226,624,1214,1972],[85,97,143,226,628,631,1035,1036,1037,1039,1041,1044,1045,1046,1048,1082,1084,1085,1089,1091,1094,1101,1102,1106,1109,1110,1115,1193,1214,1215,1218,1414,1487,1514,1544,1550,1696,1697,1698,1699,1700,1701,1934,1935,1937,1939,1940,1950,1951,1952,1953,1959,1960,1961,1962,1963,1967,1968,1969,1970,1971],[97,143,226,624,1970],[97,143,226,1046,1700,1936,1950,1961,1962],[97,143,226,624,1215,1653,2841,2863],[97,143,226,624,631,1215,1653,2841,2863],[85,97,143,226,631,1035,1036,1039,1044,1048,1082,1109,1110,1214,1215,1311,1954,1956,1957,2084,2111,2638],[97,143,226,624,2111],[97,143,226,624,1971],[97,143,226,624,2115],[97,143,226,639,1311,2114],[85,97,143,226,624,628,1399,1653,3395],[85,97,143,226,628,631,1035,1036,1037,1044,1045,1110,1218,1340,1954,1963,1969,2084,2114,2115,2854,3394],[97,143,226,624,1214,2117],[97,143,226,639,1214,1311,2114],[85,97,143,226,624,628,1214,1399,1653,3394],[85,97,143,226,628,631,1035,1036,1037,1044,1045,1214,1218,1340,1954,1963,1969,2084,2114,2117,2361,2854],[85,97,143,226,624,1218,1399,1653,2863,3396],[85,97,143,226,628,631,1035,1039,1092,1193,1204,1207,1214,1217,1218,1235,1348,2005,2352,2816,2840,3308,3314,3318,3394],[97,143,226,624,1106,1242,2000,2001],[97,143,226,1106,1242,2000],[97,143,226,624,1214,1653,2863,3354],[85,97,143,226,631,1035,1036,1037,1038,1039,1045,1082,1092,1094,1115,1214,1311,1348,1954,2084,3350,3351,3352],[97,143,226,624,1399,1653,3356,3357],[85,97,143,226,1039,1188,3355,3357],[85,97,143,226,1034,1035,1039,1115,1174,1176,1204,1362,3357],[97,143,226,624,1214,1399,1653,3356,3357],[85,97,143,226,631,1035,1214,3353,3354,3356],[97,143,226,624,1214,1399,1653,3714],[85,97,143,226,1174,1176,1188,1214,1348,2282],[97,143,226,624,1214,1399,1653,2839],[85,97,143,226,1048,1115,1214,1932,2120],[97,143,226,624,2119,2120],[97,143,226,2119],[97,143,226,624,1088,1214,1399,1653,2838],[85,97,143,226,1048,1086,1088,1115,1214,1932,1966,2120],[85,97,143,226,1115,1214,1932],[97,143,226,624,1214,1399,1689,2844,2863],[85,97,143,226,1091,1214,1414,1689],[97,143,226,624,631,1214,1399,1653,3370],[85,97,143,226,631,1034,1035,1038,1039,1043,1048,1092,1110,1115,1214,1347],[97,143,226,624,1352],[97,143,226,530,1351],[97,143,226,624,628,1174,1176,1214,1399,1653,2124,3296],[85,97,143,226,631,640,1039,1040,1048,1091,1092,1110,1115,1174,1176,1188,1214,1224,1348,1352,1675,1684,1932,2122,2124,2125,2126,2662,2717,3293,3295],[97,143,226,624,1174,1176,2122],[97,143,226,628,639,1214,2125],[85,97,143,226,1174,1176,1214,1454,2122,2124],[97,143,226,1115,1174,1176,1188,1204,1352,2122],[85,97,143,226,624,1399,1653,3348,3349],[85,97,143,226,1035,1036,1039,3348],[97,143,226,624,1399,3350],[85,97,143,226,1039,1092,1214],[97,143,226,624,631,1214,1653,2863,2960],[85,97,143,226,631,1035,1214,1945],[97,143,226,624,1399,1941],[97,143,226,624,1399,1942],[97,143,226,624,1399,1653,1945],[85,97,143,226,1941,1942,1943,1944],[97,143,226,624,1399,1653,1943],[97,143,226,624,1399,1653,1944],[85,97,143,226,1094],[97,143,226,624,631,1399,1531,1532,1653,3045],[85,97,143,226,631,1035,1038,1039,1092,1109,1110,1189,1222,1528,1531,1532,3043,3044],[97,143,226,624,1531,1653,2863,3044],[85,97,143,226,1035,1036,1037,1040,1044,1045,1082,1110,1311,1531,1954,2084,2127],[97,143,226,624,1531,2127],[97,143,226,1531],[97,143,226,624,1399,1531,1653,3043],[85,97,143,226,1039,1174,1176,1188,1531,3041,3042],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362,1531,2129],[85,97,143,226,1039,1348,1372,1531,2129],[97,143,226,624,631,1214,1399,1653,2863,2869],[85,97,143,226,631,1035,1036,1039,1043,1044,1092,1214,1311,1361,1954,1956,1957,2084,2842],[97,143,226,624,1214,1653,2863,3313],[85,97,143,226,1034,1040,1214],[97,143,226,624,1082,1214,1399,1653,3171],[85,97,143,226,631,1035,1036,1040,1044,1082,1092,1094,1110,1178,1214,1348,1354,2033,2834,2842,3159,3160,3162,3168,3170],[97,143,226,624,1399,1653,2863,2886],[85,97,143,226,631,1035,1039,1092,1109,1177,1468,1470,1471,1474,1956,2023,2834,2884,2885],[97,143,226,624,1399,1470,1474,1653,2863,2885],[85,97,143,226,631,1035,1036,1043,1044,1109,1110,1311,1361,1470,1474,1954,2023,2084,2871],[97,143,226,624,1399,1472,1475,1653,2863,2887],[85,97,143,226,631,1035,1036,1043,1044,1109,1110,1311,1361,1472,1475,1954,2024,2084,2871],[97,143,226,624,1399,1653,2863,2889],[85,97,143,226,631,1035,1039,1092,1109,1177,1469,1472,1473,1475,1956,2024,2834,2887,2888],[97,143,226,624,1399,1653,2888],[97,143,226,624,631,1399,1526,1543,1653,2842,2863,2870],[85,97,143,226,631,1035,1036,1038,1039,1044,1048,1082,1092,1094,1177,1361,1526,1543,1954,2842],[85,97,143,226,624,1090,1399,1495,1496,1653,3226],[85,97,143,226,631,1035,1036,1039,1041,1044,1048,1082,1090,1092,1094,1103,1177,1361,1495,1496,1954,1956,2025,3225],[85,97,143,226,624,1399,1653,2025,3225],[97,143,226,1035,1037,1039,1092,1348,1933,1956,2025],[97,143,226,624,631,1214,2025],[85,97,143,226,624,1399,1504,3227],[85,97,143,226,631,1035,1036,1037,1039,1041,1044,1048,1082,1090,1092,1103,1177,1361,1504,1954,1956,2027],[97,143,226,624,2027],[97,143,226,1504],[97,143,226,624,1399,1653,2890],[85,97,143,226,1035,1036,1038,1039,1044,1092,1109,1110,1178,1214,1361,1954,2029,2084],[97,143,226,624,1399,2863,2873],[85,97,143,226,631,1035,1110,1361,1537,2031,2842,2872],[97,143,226,624,1399,1653,2673,2863,2872],[85,97,143,226,1036,1037,1044,1045,1082,1183,1311,1354,1954,2030,2084,2871],[97,143,226,624,628,1399,2874],[85,97,143,226,631,1537,1539,2031,2834,2842],[97,143,226,624,1399,1537,1539,1653,2863,2875],[97,143,226,624,631,1399,1537,1539,2031,2842,2872,2875],[85,97,143,226,631,1035,1110,1361,1537,1539,2031,2842,2872],[97,143,226,624,1399,1653,2863,2876],[97,143,226,624,1399,1539,2863,2877],[97,143,226,1039,1043,1092,1174,1176,1188,1203,1539,2030],[97,143,226,624,628,1399,2880],[85,97,143,226,1035,1039,1092,1115,1193,1354,1539,2030,2031,2873,2874,2875,2876,2877,2878,2879],[97,143,226,624,1399,2878],[97,143,226,624,1399,2863,2879],[97,143,226,1039,1092,1177],[97,143,226,624,1539,2031],[97,143,226,1539],[97,143,226,624,1653,2863,2881],[85,97,143,226,1035,1039,1101,1115,1183,2001],[97,143,226,624,631,1399,2882],[97,143,226,631,1043,1092,1094,1109,1177,1550,1554,1956,2881],[97,143,226,624,1214,1399,1556,1557,2863,2883],[85,97,143,226,631,1035,1037,1042,1045,1092,1094,1109,1177,1214,1556,1557,1956,2810],[97,143,226,624,1399,1653,3170],[85,97,143,226,1035,1039,1188,2033,3169],[97,143,226,1034,1035,1039,1174,1176,1204,1362,2033],[97,143,226,624,631,1090,1399,1653,1949],[85,97,143,226,631,1035,1090,1361,1946,1947,1948],[97,143,226,624,1399,1946],[85,97,143,226,1039,1110],[97,143,226,624,628,1090,1399,1653,3039],[85,97,143,226,628,631,1035,1039,1090,1946,1947],[85,97,143,226,1039,1041,1091],[97,143,226,624,628,1090,1214,1399,1653,3040],[85,97,143,226,631,1039,1048,1106,1178,1214,1508,1949,2834,3037,3038,3039],[97,143,226,624,631,1399,1653,1947,1948],[85,97,143,226,631,1035,1039,1348,1947],[97,143,226,624,1399,1653,2821],[85,97,143,226,1034,1035,1039,2006,2303],[97,143,226,624,1399,1956],[85,97,143,226,1034,1955],[97,143,226,624,1399,1653,2816],[85,97,143,226,1034,1115,1195],[97,143,226,624,1399,3713],[85,97,143,226,624,1399,2277],[85,97,143,226,1034,2130,2274,2275,2276],[85,97,143,226,624,1399,2278],[85,97,143,226,624,1399,2279],[85,97,143,226,2130,2276],[85,97,143,226,624,1399,2276],[85,97,143,226,2274],[85,97,143,226,624,1399,2280],[97,143,226,2130,2276,2277,2278,2279,2280,2281],[85,97,143,226,624,1399,2281],[97,143,226,624,1235,1653,2863],[97,143,226,624,631,1399,1653,1968],[85,97,143,226,631,1035,1957],[85,97,143,226,1174,1175,1176],[97,143,226,1174,1176,1180],[85,97,143,226,624,1174,1176,1180,1185,1187,1399,1653,2863],[85,97,143,226,1034,1039,1174,1175,1176,1177,1178,1179],[85,97,143,226,624,1174,1176,1180,1182,1186,1399,1653],[85,97,143,226,1035,1042,1174,1176,1181],[97,143,226,624,1179,1399,1653],[97,143,226,1034,1035,1039,1045],[85,97,143,226,624,1174,1176,1188,1399,1653],[97,143,226,1174,1176,1183],[85,97,143,226,624,1174,1176,1187,1399,1653,2863],[85,97,143,226,809,1034,1039,1174,1176],[85,97,143,226,624,1174,1176,1180,1186,1399,1653],[85,97,143,226,1034,1035,1036,1039,1115,1174,1176,1185],[97,143,226,809,1035,1039,1174,1176],[97,143,226,1175,1176,1179,1180,1182,1184,1185,1186,1187],[85,97,143,226,1174,1176],[97,143,226,624,1195,1399,1653],[85,97,143,226,518,1034,1039],[85,97,143,226,624,1036,1082,1311,1399,1653,1954,2083],[85,97,143,226,1039,1048],[85,97,143,226,1034,1036,2059,2653],[97,143,226,624,1200,1399,1653],[97,143,226,1048,1193,1214,1215],[97,143,226,624,1201,1399],[85,97,143,226,866,1033,1034],[97,143,226,624,1091,1399,1653],[97,143,226,624,2814,2863],[85,97,143,226,2705],[85,97,143,226,624,1041,1399,1653,2825],[85,97,143,226,1039,1040,1041,1695],[85,97,143,226,624,1041,1399,1653,1696,2863],[97,143,226,624,1399,2957],[97,143,226,1035,1039,1956],[85,97,143,226,1038,1039],[85,97,143,226,1193,1332,1333,2822],[97,143,226,624,1041,1399,1653,2863],[97,143,226,624,1226,1399],[85,97,143,226,759,1033,1034],[85,97,143,226,1039,1092,1237],[97,143,226,624,1189,1190,1399],[85,97,143,226,1034,1039,1115,1189],[85,97,143,226,1048],[97,143,226,624,1192,1399],[97,143,226,1191],[97,143,226,624,1193,1194,1399,1653],[85,97,143,226,1034,1039,1191,1193],[97,143,226,624,1196,1399,1653],[85,97,143,226,1034,1039,1195],[97,143,226,1190,1191,1192,1194,1196,1198,1199,1202,1203],[97,143,226,624,1198,1399,1653],[97,143,226,1089,1115,1191,1197],[97,143,226,624,1199,1399],[97,143,226,624,1202,1399],[97,143,226,1193,1200,1201],[97,143,226,624,1203,1399,1653],[85,97,143,226,1034,1115,1191,1195],[97,143,226,624,1399,2705],[97,143,226,1034,1043],[85,97,143,226,1452,1453],[97,143,226,624,1228,1238,1653,2863],[85,97,143,226,1034,1035,1039,1043,1094,1109,1115,1220,1228,1229,1231,1232,1233,1234,1235,1236,1237],[85,97,143,226,624,628,1214,1239,1241,1399,1653],[97,143,226,628,1035,1039,1101,1201,1214,1239,1240],[97,143,226,624,631,1214,1399,1653,2872,2891],[85,97,143,226,631,1035,1044,1082,1110,1214,2842,2872],[97,143,226,624,1399,3423],[85,97,143,226,637,1091,1214],[97,143,226,624,1214,1399,1653,2863,3689,3691],[85,97,143,226,631,1214,3689,3690],[85,97,143,226,1039,1174,1176,1188,3689],[97,143,226,1034,1035,1039,1174,1176,1188,1204,1362],[85,97,143,226,1938],[97,143,226,624,1399,2863,3314],[85,97,143,226,1035,1036,1044,1045,1091,1110,1311,1361,1700,1937,1954,2084,2283],[85,97,143,226,1039,1040],[85,97,143,155,164,226,624,1653,1938,2863],[85,97,143,226,1035,1036,1038,1039,1043,1045,1048,1092,1115,1354,1932,1936,1937],[97,143,226,624,1214,1399,2863,3316],[85,97,143,226,631,1035,1039,1092,1178,1183,1214,3315],[97,143,226,624,2283],[97,143,226,624,2288,2863,3317],[85,97,143,226,1039,1048,1092,1115,1193,2288,2371],[97,143,226,624,3315],[97,143,226,624,2285],[97,143,226,624,631,1086,1189,1214,1217,1218,1399,1421,1487,1501,1503,1546,1558,1653,1966,2863,3321],[85,97,143,226,628,631,1035,1036,1037,1039,1041,1044,1047,1048,1082,1086,1089,1091,1092,1094,1101,1106,1109,1115,1129,1193,1203,1207,1214,1218,1311,1348,1361,1414,1421,1501,1503,1546,1697,1698,1699,1700,1932,1934,1935,1937,1950,1954,1963,1966,1967,1969,2084,2119,2120,2285,2289,2352,2640,2816,2834,2836,2840,2846,2854,3308,3309,3310,3311,3312,3313,3314,3316,3317,3319,3320],[97,143,226,624,1106,1109,1399,1550,1653,2863,3319,3321],[85,97,143,226,1039,1048,1106,1109,1193,1204,1214,1550,3318,3321],[97,143,226,624,2119],[97,143,226,624,1214,1215,1487,1653,2863,3320],[85,97,143,226,1036,1039,1048,1089,1115,1174,1176,1188,1197,1204,1214,1215,1235,1452,1453,1487,2817,2818,2848],[85,97,143,226,624,628,631,1089,1214,1399,1546,1653,1659,2863,3695],[85,97,143,226,628,630,631,1035,1036,1037,1039,1041,1044,1048,1089,1094,1101,1106,1110,1214,1215,1217,1218,1311,1348,1414,1546,1661,1697,1698,1699,1700,1934,1935,1937,1939,1950,1954,1963,1967,1969,2084,2119,2814,2834,2854,3309,3311,3313,3321,3691,3692,3694],[97,143,226,624,1215,1217,2034],[97,143,226,1214,1215,1217,1990],[85,97,143,226,624,1215,1217,1399,1653,2863,3694],[85,97,143,226,1035,1036,1039,1041,1109,1174,1176,1188,1215,1217,1218,1452,1453,2034,3693],[97,143,226,1034,1035,1039,1174,1176,1177,1188,1193,1204,1214,1215,1362],[85,97,143,226,624,631,1214,1653,2863,3692],[85,97,143,226,631,1035,1036,1038,1039,1040,1089,1092,1115,1214,1218,1361,1700,1952,2854],[97,143,226,624,2289],[85,97,143,226,624,1046,1214,1215,1399,1653,2847,2863],[85,97,143,226,631,637,1035,1036,1037,1044,1045,1046,1048,1089,1091,1094,1106,1214,1215,1218,1227,1361,1514,1550,1697,1698,1699,1700,1701,1935,1936,1937,1940,1950,1952,1954,1960,1961,1962,1963,1967,1969,1972,2067,2084,2102,2289,2291,2293,2843,2844,2845,2846],[97,143,226,624,1109,1214,1215,1218,1399,1653,2815,2848,2863],[97,143,226,624,628,1109,1214,1215,1342,1399,1501,1503,1653,2815,2848,2863],[85,97,143,226,628,631,1035,1039,1092,1106,1109,1110,1115,1193,1195,1200,1207,1214,1215,1218,1342,1348,1487,1490,1491,1501,1503,1514,1550,1936,2067,2106,2292,2640,2815,2816,2820,2824,2832,2833,2834,2835,2836,2840,2841,2842,2847],[97,143,226,624,1399,2832,2863],[85,97,143,226,1342,2831],[97,143,226,624,2293],[97,143,226,1215,1311,1936,2289,2291,2292],[85,97,143,226,1039,1045,1048,1082,1937,1954,2293],[97,143,226,624,1399,1653,2820],[85,97,143,226,1035,1039,1043,1048,1115,1195,1207,1235,1362,2817,2818,2819],[85,97,143,226,624,628,631,1106,1399,2848],[97,143,226,624,1332,1342,1399,2824],[85,97,143,226,1092,1106,1333,1342,1348,2282,2821,2823],[97,143,226,624,1370,1399,1653,2713],[85,97,143,226,1035,1039,1370],[97,143,226,624,628,1214,1399,1653,3698],[85,97,143,226,628,1035,1039,1040,1115,1214,1361,1951,2016,3141,3697],[97,143,226,624,2863,3697],[85,97,143,226,1034,1045],[85,97,143,226,624,628,631,1214,1399,1653,2863,3701],[85,97,143,226,628,631,1214,1414,2036,3138,3700],[85,97,143,226,624,1214,1399,1653,2863,3700],[85,97,143,226,1039,1045,1174,1176,1188,1214,3697,3699],[97,143,226,624,1174,1176,1214,1399,1653,3699],[97,143,226,1048,1174,1176,1188,1204,1214,3697],[85,97,143,226,624,1399,1653,2863,3702],[85,97,143,226,1414,3698,3701],[97,143,226,624,1035,1347,1399,1653],[85,97,143,226,719,1034,1035],[85,97,143,226,1033,1034],[97,143,226,624,1236,1399],[85,97,143,226,757,1034],[97,143,226,624,1115,1399],[97,143,226,859,1028,1033,1034],[97,143,226,624,1399,2704],[97,143,226,859,1028,1033,1034,1043],[85,97,143,226,624,1035,1399],[97,143,226,759,1033,1034],[85,97,143,226,624,1399,2275],[85,97,143,226,1034,2274],[97,143,226,763,1034,1039],[97,143,226,769],[85,97,143,226,1029,1034,1035,1038,1039],[85,97,143,226,818,1034,1035,1039],[85,97,143,226,809,1034,1039],[85,97,143,226,624,1044,1399,2673],[85,97,143,226,1033,1034,1042,1043],[97,143,226,925,1034],[85,97,143,226,1033,1034,1035,1036,1037],[85,97,143,226,912,1034],[97,143,226,936,938,1034],[85,97,143,226,624,1035,1036,1037,1042,1043,1092,1115,1177,1178,1361,1399,2275],[85,97,143,226,946,1034],[97,143,226,624,1045,1399,1653],[85,97,143,226,966,1034,1039],[97,143,226,780,1034],[97,143,226,1034],[97,143,226,976,1034],[97,143,226,629,1039,1370],[97,143,226,980,1034],[97,143,226,987,1033,1034],[97,143,226,624,1048,1399,1653],[85,97,143,226,1026,1034,1039],[97,143,226,624,1361,1399],[85,97,143,226,1034,1360],[97,143,226,624,1214,1399,1653,2892],[85,97,143,226,631,1035,1036,1039,1044,1045,1048,1082,1214,1311,1361,1954,2084],[97,143,226,624,1214],[97,143,226,624,631,1214,1399,1653,3302],[85,97,143,226,631,1035,1039,1044,1110,1214,1311,1361,1954,1956,2084,2871],[97,143,226,624,1109,1214,1215,1399,1653,2099,3385],[85,97,143,226,1042,1048,1102,1109,1188,1193,1204,1214,1332,1932,2099,2282,2848],[97,143,226,624,1332,1399,1653,3710],[85,97,143,226,1092,1174,1176,1188,1193,1204,1332,2282],[97,143,226,1986],[97,143,226,1332],[97,143,226,624,2038],[97,143,226,624,1214,1399,3715],[85,97,143,226,1040,1048,1092,1214,1348,2006,2282,3713,3714],[97,143,226,624,1214,1399,1556,2810,2863],[85,97,143,226,1035,1039,1214,1556,1956,2766,2809],[97,143,226,624,1091,1138,1399,1969],[85,97,143,226,1091,1138,1214],[97,143,226,624,1352,3756],[97,143,226,530,1352],[97,143,226,624,1399,1653,2303,3179,3181],[85,97,143,226,1035,1039,1181,1203,1235,2303,2818,3179],[97,143,226,624,628,1214,1399,1653,2863,3182],[85,97,143,226,628,1174,1176,1214,1351,1452,1453,3179,3180,3181],[97,143,226,624,1174,1176,1399,1653,3179,3180],[85,97,143,226,1036,1039,1045,1174,1176,1188,3179],[97,143,226,1174,1176,1204,2818],[97,143,226,624,1399,2313],[85,97,143,226,624,1653,2312,2863],[85,97,143,226,1039,1101,1193],[85,97,143,226,1039,1048,1092,1115,1178],[97,143,226,2298],[85,97,143,226,624,2298,2299,2863],[85,97,143,226,1048,1214],[85,97,143,226,624,1653,2299,2310,2863],[85,97,143,226,1048,1193,2298,2307,2308,2309],[85,97,143,226,624,1653,2299,2307,2863],[97,143,226,624,1399,1653,2863,3191],[85,97,143,226,1348,1361,1414,3175,3178,3182,3190],[85,97,143,226,624,628,1174,1176,1214,1215,1399,2095,2303,3183],[97,143,226,628,1106,1174,1176,1214,1215,2095,2301,2303,2868],[97,143,226,624,1399,2302],[97,143,226,1034,1115],[85,97,143,226,624,1399,1653,2332],[85,97,143,226,1039,1101],[97,143,226,624,1399,2301,2305,2863],[85,97,143,226,1035,1039,1048,1115,1204,1352,2300,2301,2302,2303,2304],[85,97,143,226,624,1399,1653,2329,2335],[85,97,143,226,1039,1101,2329,2334],[97,143,226,2340,2341],[85,97,143,226,624,1399,1653,2329,2336],[85,97,143,226,631,2329,2331,2332,2334,2335],[97,143,226,624,1370,1399,2318,2319],[97,143,226,526,1370,2300,2318],[97,143,226,624,1399,1653,2301,2340],[85,97,143,226,1035,1039,1048,1092,1101,1115,1193,1211,1348,1361,1682,2300,2301,2303,2310,2311,2312,2313,2314,2315,2316,2319,2320,2328,2339],[97,143,226,624,628,1204,1214,1399,2301,2341],[85,97,143,226,628,1039,1181,1193,1204,1214,1348,1493,2295,2297,2300,2301,2302,2304,2305,2306,2320,2340],[85,97,143,226,624,1399,1653,2329,2337],[85,97,143,226,631,2300,2329,2331,2334],[97,143,226,2329],[85,97,143,226,624,1399,2339],[97,143,226,2330,2336,2337,2338],[85,97,143,226,624,1399,1653,2338],[85,97,143,226,1039,1048,1115,2331],[85,97,143,226,624,1211,1399],[97,143,226,1034,1039,1115],[97,143,226,624,1399,1653,2331],[97,143,226,1034,1035,1039,1048],[97,143,226,1034,1035,1039],[85,97,143,226,624,1399,2334],[97,143,226,1034,2329,2333],[85,97,143,226,624,1399,2333],[97,143,226,1034,2329],[97,143,226,624,1399,2316],[97,143,226,624,1399,2315],[97,143,226,1048,1235,2300],[85,97,143,226,2300,2301],[97,143,226,624,2320],[97,143,226,624,2303,3184],[97,143,226,2303],[85,97,143,226,1035,1036,1039,1094,1237,2295,2303,3184],[85,97,143,226,624,1189,1399,1485,1533,1535,1653,2295,2863,3183,3186],[85,97,143,226,1036,1040,1041,1045,1188,1189,1215,1485,1533,1535,1696,2295,3183],[97,143,226,624,628,1214,1399,1452,1453,1653,1659,2301,2303,2863,3190],[85,97,143,226,628,1174,1176,1188,1204,1214,1215,1452,1453,2296,2301,2303,2342,2848,3183,3185,3189],[85,97,143,226,1039,1174,1176,1188,1215,2301,3183,3186,3188],[97,143,226,624,1188,1399,1653,2301,3188],[97,143,226,1174,1176,1188,1193,1204,1352,2295,2301,3187],[97,143,226,1178,2321],[97,143,226,2321,2322,2327],[97,143,226,2321],[85,97,143,226,1348,2321,2323,2324],[85,97,143,226,1034,1039,1115,2321,2325],[97,143,226,624,1399,1653,2301,2322,2327],[85,97,143,226,1039,1101,2301,2322,2326],[97,143,226,624,2301,2322],[97,143,226,2301,2321],[97,143,226,624,1399,3187],[97,143,226,2295],[85,97,143,226,1039,1101,1352],[85,97,143,226,1109,1193,1214],[97,143,226,1039,1174,1176,1177,1188,1200,1204,1214,1215,2817,2818],[85,97,143,226,624,1214,1215,1399,1487,1488,1653,1659,2815,2849,2850,2863],[85,97,143,226,1036,1039,1041,1174,1176,1188,1215,1217,1218,1452,1453,1487,1488,1661,2814,2848,2849],[85,97,143,226,634,635,636,1106,1214],[85,97,143,226,518,2054,2055],[97,143,226,624,1399,2711],[85,97,143,226,630,1214],[97,143,226,628],[85,97,143,226,1214],[97,143,226,2349],[97,143,226,2345,2346,2347,2348,2350],[85,97,143,226,624,628,631,1214,1399,2354],[97,143,226,628,631,1214],[97,143,226,624,632,1214,1399,3216],[85,97,143,226,631,632,1214,2377,2650],[85,97,143,226,624,1369,1370,1371,1399],[85,97,143,226,1369,1370],[85,97,143,226,1086,1214],[97,143,226,624,1214,1399,3235],[85,97,143,226,631,632,633,1214,2351,2377,2650],[85,97,143,226,631,632,1214,2351,2377,2650],[85,97,143,226,1108,1214],[97,143,226,624,1351],[97,143,226,1205,1212],[97,143,226,624,1127,1128,1137,2356],[97,143,226,1093,1127,1128,1132,1134,1135,1136],[97,143,226,624,1034],[97,143,226,1030,1031,1033],[97,143,226,624,1082,1399,2361],[97,143,226,1082],[97,143,226,624,2363],[85,97,143,226,624,1311,1399,1653,2084],[97,143,226,1082,1301,2083],[97,143,226,624,1213,1340],[97,143,226,630,639,1212,1213,1338,1339],[97,143,226,624,630],[97,143,226,624,1212],[97,143,226,624,1213],[97,143,226,1212],[97,143,226,624,639,1981,1982],[97,143,226,624,1353],[97,143,226,1351],[97,143,226,624,630,631],[85,97,143,226,629,630],[97,143,226,2059],[97,143,226,624,1106,1227],[97,143,226,1106],[97,143,226,624,633,634],[97,143,226,633],[97,143,226,624,631,1193],[97,143,226,631],[97,143,226,624,1207],[97,143,226,624,2377],[97,143,226,624,635,636],[97,143,226,635],[97,143,226,624,2638],[97,143,226,2637],[97,143,226,624,2640],[97,143,226,624,1240],[97,143,226,624,1228],[97,143,226,624,2645],[97,143,226,624,633],[97,143,226,632],[97,143,226,624,1964],[97,143,226,624,1214,1668],[97,143,226,1106,1214],[97,143,226,624,1682],[97,143,226,624,1214,1221],[97,143,226,624,2059,2654],[97,143,226,2059,2653],[97,143,226,624,2059,2653,2657],[97,143,226,2059,2654,2656],[97,143,226,624,2656],[97,143,226,1105],[97,143,226,624,1106,1214],[97,143,226,624,2662],[97,143,226,624,1419],[97,143,226,624,1215,2005],[97,143,226,624,1083],[97,143,226,624,1205,1206],[97,143,226,1205],[85,97,143,226,624,628,1105,1399,2700,2852],[97,143,226,2671,2683],[97,143,226,2671,2685],[97,143,226,2671,2687],[97,143,226,2671,2689],[97,143,226,2671,2691],[97,143,226,2671,2693],[97,143,226,624,2671],[97,143,226,624],[97,143,226,2674],[97,143,226,624,2676],[97,143,155,164,226,624,1137],[97,143,226,624,1399],[85,97,143,226,624,628,1399,1653,1659],[97,143,226,624,1109,1332,2863,3385],[97,143,164,226,612]],"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":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","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":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"66be1299a7a3129ceb488b340c291cf575bebb0e337f92e169dec38231472e34","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","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":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","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":"48dcc919f76c040a999c0d46d2bf25ab089645ca21b837f120b222f56a86cd76","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":"2467b00d963828f540f4acd7910f4c04cfe4b489550e6bb682212f65583bca5b","impliedFormat":1},{"version":"854e50b93090b3f8fd6e355b074e1d24dce1ae0240f1ce46563e35fea210a6d5","impliedFormat":99},{"version":"5a16e93d5d53d987dddda1ec606c9821f6bd31d1bdf0635e05e3841312cefa8b","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":"c30436b130b6218b7714314dc41d3f459590db4bdf099eecd51cb1bda32109a8","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","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":"5757b78830c681b3124af568b94c269259ea5e8171a4316508ef67310c2ed1ed","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":"db2d933d8101f90deeec6698e70f1e14729495c5daab3199f4cdf0ac78a87bdf","affectsGlobalScope":true},"7ad303e40d4fddf44f156129e397511953a71481c5cfd86b1862649aaaf240cc",{"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":"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":"3cc8a3d123b6b232d48d34b51b785f9da8d193f5b5817fa521fcd2f3b9315c55","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","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":"061446b67af18b541c723104f25aa94667dd438c050fc873f3c02a7b5a9a3ef0","signature":"b8ee70929b7bfa2ced6aded5f38945440e9ff6809c61d2972b59aaecf88c254c"},{"version":"8ef0b457802d1883c0c185f90610a8aaf33283250633a31853de01bb45565b7b","signature":"b730dbc27807d6a94494d69e0154827379b8ed4606f3dd3a4584a1e2242b1e53"},{"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":"5a2cdf6adeec348bbc876221be4367e8adff0bb78a5680ebd7d71e5c3bad6cc0","impliedFormat":99},{"version":"e004826eac62081f867c66dabd92d3ef7d126d93a70430a2c88429228c3ecc50","impliedFormat":99},{"version":"38d6857b58d2ac42442e396311c542062d4f0dad40f2adb496dd5fd0756ee400","impliedFormat":99},{"version":"34b7d1e2d15845cf08bcf5e3c01adbb92cea1ec27564ee249ba486cdfb28526c","impliedFormat":99},{"version":"64bc7684d633c835220935b80701168771e6ddc8c3d9145af8bb3a3ac7d0c59a","impliedFormat":99},{"version":"8cdbb0f1c8db096cfd876dcecbb38c54e495a4203af88417b762dd6f62daa1b6","signature":"2dc0c34bb13c5ca57ccd5a048d076fec5305f279d22010fc565a27b9a8d66ef5"},{"version":"598c32af38ceddfaf9699b9013ecf2e0b2df7b5d76795c9de010d5ff92c52ad5","signature":"e064b7ccad9850f3a78ba58a45e43e4b3eaf126cd2bd2979896b5885dea07f57"},{"version":"618bb47ac74b0ab39b90743cfe920a6e670b2d3bfa24f37d106b535c1c18ae37","signature":"4c8a45f845c330caf5dc2bc33da6e8e4f317035d08a20fd5f1da986bea511d60"},{"version":"9f50731b7a6739ad4d5d0e00b5d0be3650535cd74d92bf86ba3b81cf57000269","signature":"64be38d2ab0fa005245ad20baf0fc7899f1db575a219b4428e0fc3e550d02410"},{"version":"1d0628911b56f83b654ca0ba826d503b3605926e73b985e754513832eeab5592","signature":"45b43c38bc20ca8c0edcee887ed49ed8b771dbe78e1cb5b4e3ba794e853fa887"},{"version":"6d575d93896c413b308c3726eed99ddd17e821a00bdd2cc5929510b46fe64de4","impliedFormat":99},{"version":"1beebd50610b0c9701d2de263e0183ec22aad6c051d0e15ce9e6cce295c6a40b","signature":"3c49b34b1c62e5d74c637b63276c9acacb605689334f5450cc3f67f560ac0ecf"},{"version":"310c820b803950d18c0ed9376df2cd73def2f56cfcc993f9012008403cdd4843","signature":"6fb95390f4022e0327e4a170917a06de5caad8c8c563c8b00be3cd40a71c759e"},{"version":"4ed96213860296593b569b425eec8dfac37cb5bdaffbce2206c000dc673007c7","signature":"0fbe920fa2bb3439dfa680647a4ea264b7a8ea9bfa75e4cdd9ff2507d69df783"},"a7e69a45d5f2dc289b662813446d0cc7b2b972b05165e41a5501b2080aba0807",{"version":"4720053f6a578540743ee8f3c02278636ada05dfc7184b43433262c4aebbbff5","signature":"20f656d6480d8146a5128b53fee43e77e2851f98fd61b3da28f2d8a5560578b1"},{"version":"ddc62c8eb6b7fb8e8fd0f0f19809530b1e5ba5a131471a6eef65028d0d3b5a6e","impliedFormat":99},{"version":"802cbde8e06732ca0356927b4c9fbc39f5961df58a18b74fdc4e131269293a5d","impliedFormat":99},{"version":"480713ff75c24f445e3f159da28444406e1334730375c94d0aa24523c5e52e1c","impliedFormat":99},{"version":"3ac6eb2cafcb89a552a4923213c705f0fc3c2b50e466eae9dc1a540e3af18bc0","impliedFormat":99},{"version":"073f96a1cfddfedf8695401f8328a8e84a4d98fa5b08b4d894c3885069083cd6","impliedFormat":99},{"version":"66ba40fa928c2fada9a280a61c2b426dfbbfa69085f99913650ff72ddec75b1a","impliedFormat":99},{"version":"4d857105510df8011cfb5b3769dec55624a1df92e85d399cd03bc82bb89d090c","impliedFormat":99},{"version":"19a22f3446387435f13445a31e3d4eb65f132d8e6b7b060d249f0fb138cec698","impliedFormat":99},{"version":"ecc46b24349caeab20d889baf0a6f9d3beafa739a0f2c36afc107dceb15e7b2b","impliedFormat":99},{"version":"b887624859a2f03e78ae6018e96bd269b5318685f42adec4e93256ed7579c125","impliedFormat":99},{"version":"14191b461a91229ff4b388d15b9e15392a8a3af9bf11fa7d0d4cd31405178e75","impliedFormat":99},{"version":"23a564e852dc91b6e6f050584994b35156f6ee8a2d08c493dad04309046a8397","impliedFormat":99},{"version":"daf66c9de89f11011ef703af894970bb15985fd5a4156b8038e895ad4e4616a7","impliedFormat":99},{"version":"d59c3d0c3283c1878913fc2bc88d84160dbcdc69cf06f822ca7ffb39eefef13b","impliedFormat":99},{"version":"05128b72488ad970c2e30ae6b82c7ee232be49ce6def3b4dd56f62d8b7f7704c","impliedFormat":99},{"version":"9eb8e1320fc0ecbfba15c0f3452dfc1957543dfbd466aaf8b67ddb0f2ad0f217","impliedFormat":1},{"version":"4faca872dbd194a17b3ee267bd8ddc3daf3d16df96f4e43a02c7d9a862022c4f","impliedFormat":99},{"version":"87654de60b5cd8d91d59632ec576fa7e313b41c2540073d52814b6cf5bb739e6","impliedFormat":99},{"version":"144a4e5780b800c0553949169f50be285eccbdb0298afd83ef2ae03fef77e2d2","impliedFormat":99},{"version":"66aeb47bf8638d6767f7b4ff684c2d794391c981590073025e98f98e1afed499","impliedFormat":99},{"version":"cd5b0672c9699fe169d69efd65472a874de9d1e25fa8669a934f5f326bf0f025","impliedFormat":99},{"version":"4577621880c696b0aacec6ebd2dbf97ac178ee2e2bfaa0aa3a5260a798220ab4","impliedFormat":99},{"version":"26731910f98a56ed001d25d5167d85b1320def4ffbb76e1cc4b0c6484482a5e2","impliedFormat":99},{"version":"cbaadb95dcc68691900ffa857b3bd7eaa99eeb6c351afca15103560bc87f0d15","impliedFormat":99},{"version":"97b02501eb45f487174d5a0ff89b6a95690d50e9eae242e2162118edd5f2705c","impliedFormat":99},{"version":"bbea0619511648a92fe83d5c8eed6149106d7fbf3065310a1986d18598b83bbf","impliedFormat":99},{"version":"963ece6abb58542445eda863960cf053a98da8f4e8634b7a8826aa04f6f85a56","impliedFormat":99},{"version":"5ecea63968444d55f7c3cf677cbec9525db9229953b34f06be0386a24b0fffd2","impliedFormat":99},{"version":"1d226c1e6786584e97efede708d49f2dbd6f887905f16c785d5f09b300bc098d","impliedFormat":99},{"version":"07ff7d4360fbc945963d7a4a8105a5520d1681a00745c20a962fb36bf04452de","impliedFormat":99},{"version":"1b1c48c4d7cbe6f40616594c2a3f6f95bb1dcefd200a7e4167e47b67725b631a","impliedFormat":99},{"version":"62076be1e1e8b668a8ddcb803402f1aec725a31d592e8722ff39ad368d9cd472","impliedFormat":99},{"version":"2806e4d2a88e0461c3b0c8cd9e7bc8e927034690e33345aa0853439d67f801b4","impliedFormat":99},{"version":"b1d72bde8f54695b85883613af13295a615034b2829dde3a31bd3d2a40eb6bc4","impliedFormat":99},{"version":"26cfaec143443411bc7d5363f274f885ced430b8f4bee25a81f7827248848d7b","impliedFormat":99},{"version":"6870f32dc76ff6f8f6a419ce55add0a011909e8895252d7cca813835f431783f","impliedFormat":99},{"version":"29fcff21ff0ecbe700c7db7f719af2fb4822a08d6d703b6687822535e8bd3126","impliedFormat":99},{"version":"5597cbcd19e16f5c9148c76c914e158680de55849b625c2f6b69723f01f1007c","impliedFormat":99},{"version":"7864233f21a3bd04eb6dfa79103a6c1d0648cf17eb4c47cc7aef19d274dd639c","impliedFormat":99},{"version":"b27f7733758db8f462dadf0ee250056e370413028c99fc723c4a93baa54a7c1c","impliedFormat":99},{"version":"5bd7f6f573ac89ec20aaf326e79394da8a89fbff8a297aa864de9137ca045678","impliedFormat":99},{"version":"81b2bebeae6ec1e73b491fe22a82c7e2d3a8369271e622ed74b6e94ba108a475","impliedFormat":99},{"version":"d0e4a184f48eba140f30e8f770853b884e01694f6aff59b38d0be55b0410d397","impliedFormat":99},{"version":"1d34b3ef8e5926334d86d305477d3592d648adb41fa0110970a68059e13d45c0","impliedFormat":99},{"version":"791e26804cd328b19fc37f7903813e8e41892e70d5241dbe2c39fdb52fdd0c9a","impliedFormat":99},{"version":"02c2773eb8536a50f6e647483e78e8c2991fea8ac32ab69a37f9a24255401530","impliedFormat":99},{"version":"4ac6d584eada1621a7eaa4bfb3dd54e81c2c8a82c7ffdf421ae58d84c3a3490e","impliedFormat":99},{"version":"a522abad9b9b959a9c4bdb4e6bdad96e65d97da9385be13ffc8affc3669fd786","impliedFormat":99},{"version":"ae733b8a8fc9659e24821aa3797d25cfdc205bd31674227b49411ca4d54e510c","impliedFormat":99},{"version":"0aa9c5135c3a086d7c01d8d18409da6b01fd32f09a4a261048f8cb4653f22be1","impliedFormat":99},{"version":"1e185a3af4b4f3bd6fd52fde968f14dcf9a8cbbc4924237270e290e25d81fe40","impliedFormat":99},{"version":"ae42c6173cc8ad49d6ae21187d0bb7c7c65da10204f9e2614eeb83b29c58f4f7","impliedFormat":99},{"version":"050240464b97ffce2e353ccd5251660f5d3dcf9dc834f88504732ded7cfe926c","impliedFormat":99},{"version":"3f896952650454552b2584ef1e3dd072e97f8498908cd2ab25e6b0217e8bfeb2","impliedFormat":99},{"version":"3b3d0685f081f6a02cda029e4d1e1ba5f10690870c971e6697e0c2539501e835","impliedFormat":99},{"version":"5998b174ccb38a61393170f40448f80152ca5518f9d2048f5b5d3cbe0a9fbac2","impliedFormat":99},{"version":"6707d39d8afa069222d0674016d48c4772067eb671f9b62528a6cc8218fd5b40","impliedFormat":99},{"version":"481aab62f04afa6eab4e439fb4f39af392c5c51519f548897ff71e6bad0b6771","impliedFormat":99},{"version":"51de9d738596fcc085d13bdf86c0014f15d9b4e6986631c7be3df9d2f61590d8","impliedFormat":99},{"version":"8eca47167dadd486582ecd4e41f7fba6ae66cc4a4c5202f1f7acf34129a0dadf","impliedFormat":99},{"version":"29cc3322fd17fd1b55ea2150ad6f7cb37f0b587efca5696819cc5b6e95331bd4","impliedFormat":99},{"version":"d09b7414a64adc7cae660ecd6e8a222ad9fa58585dd2390eb0aaabfee812b354","impliedFormat":99},{"version":"769b6f9f1cd9471261d137513abc391a744a3c3a62f492491bcde520219fab53","impliedFormat":99},{"version":"49fdbd971a9b57df943498b37cf11c40fe09b2675493039a0b7841671f385108","impliedFormat":99},{"version":"faba6f3b673c89279d3b41a47e8ea2c850665eadfa1e2a56be4f50a6bf4356c6","impliedFormat":99},{"version":"5cbc3c3c6475704af132c35b095da392a03815baf2e9f2853178ff9b370b64d2","impliedFormat":99},{"version":"074209bc8fc6979cfc363d392a8babe62685adc61c62a8742ecdb86fb9b62ad0","impliedFormat":99},{"version":"6826e70645f65e77bcceb9230962687109301a4ad9d6dbb71a7785167d4a4b9e","impliedFormat":99},{"version":"3c8b637a833f97a085417e7d0024ac82f7fafc0834a4c61d5e48f8edd8da6c10","impliedFormat":99},{"version":"a3a4132d6c64f431b6d0cc890557c392f57eb43371bb73979ea38d27c86a1c4c","impliedFormat":99},{"version":"ec03be0777b98df75dcd97657ebfac0eb7a9153867aab050a591b6caadf1c2a2","impliedFormat":99},{"version":"c788aaea8be5712b40c3bd9cf589c9510930af7b2aa3d986125df0dedc569290","impliedFormat":99},{"version":"e7983072a038e512514c146b25e7e97a8a070ca3507950658ab1e96f6598957c","impliedFormat":99},{"version":"f2333bb4a221631fe506a0354fffb808507d4e5f6fe2c85b69890618226f7d9b","impliedFormat":99},{"version":"6c9114366ff07ee8f5c3cd4ba94ad189a098ce8368040909d605fb38e636d026","impliedFormat":99},{"version":"13e930c27d68ecfa906c24d599b10927b152030d07da0fa0889fd4fddc5b4115","impliedFormat":99},{"version":"b4e61f4f522304f7fce1038590ca1f6d091d58aff84833861848f8157732d8db","impliedFormat":99},{"version":"8c86f563e8bcefb0b5b1ac62e5a27ee6a2a9b775e72dea5793823edeb24d36e9","impliedFormat":99},{"version":"d9134c8daef2565f20b72171f634800efb204eba63b03142d5dae5f36088e95a","impliedFormat":99},{"version":"faf9a217d8d237b02ab6d95508d8736ae431bbeb38d98885eb5b8fb6dbe48cec","impliedFormat":99},{"version":"e702ed1fd1dcb24ec2634901441fd156449f75458359c771074cbe7675e86614","impliedFormat":99},{"version":"bbb044421875fc84b7d2f2aac4fb14499687cae5a5063da51bfa28c58239bcfb","impliedFormat":99},{"version":"74b564cd3da8f83d5e472a5b0cc53bf7e276b25576097cb89e6f67caf95b12dc","impliedFormat":99},{"version":"3705ba677801103461ff0a06d34b6b2149072952365e55d8266969978dd33154","impliedFormat":99},{"version":"cca68a7703ec3717b6d4c287884fc79ba811f894c472718126010418cd306aa7","impliedFormat":99},{"version":"590708a598f58b156518493c563df1d03040d3b2b7f75fe614e1ada06dbb44dc","impliedFormat":99},{"version":"dc2c32ad9c49a7c3e56a18f3f42933e91474bc26ecc2ea47cf533818a54e6471","impliedFormat":99},{"version":"e061e898ffe9970c067278f5a7462665e2706e7cc6ce2362276eca1c92c128f7","impliedFormat":99},{"version":"e5ee49966285e5afa0dd2db7f66acf1e8a9e1d0bc5724b03b67be92ea7819bfc","impliedFormat":99},{"version":"4db2be160aa80fecd367876f8cf1aa197cd1f296e5f82ed8d8b961d9ececb204","impliedFormat":99},{"version":"5a8e4a5e571755e265bd6a840d8ab48eeb1ca2e35487d96bbd601ed296e2d1b3","impliedFormat":99},{"version":"d93cac0bbb7e1fe241f4b0493cd47466df00d9f1c51a53b69e5442456cb4d102","impliedFormat":99},{"version":"9de94cfccea0da314e8554d6b2f1f01a1b63fa4c79dc24b54277e86b918e9d6f","impliedFormat":99},{"version":"b4f7f4e2e4d0e668ab7cfd94ae5b72b6c690eafeac0e7a6d2218b16afdf7432f","impliedFormat":99},{"version":"5e8d925c0b8f6f91ac0af131a83f72683f88d80a61f5eea37d8883afbe8f74fa","impliedFormat":99},{"version":"056e9235afb474b7b2ffb6df16ff331f5238027b185367ca745103eb228fe57b","impliedFormat":99},{"version":"2eb77a708b1d812a8b0a57a6a12cbdb659bf43acf839b21b8996dbbd511d6e53","impliedFormat":99},{"version":"9a2d7fe034d084982a18ed744a3e0748f4768fdc5b9f2cdbf5f190e5226b54a4","impliedFormat":99},{"version":"1cbf7a0290d370c2843e79344bd494a10d267b3e0323bb77cf1b34a36ecf4200","impliedFormat":99},{"version":"2dd580520217749fd86cd77b8e48075a6c2ff32339e2334aef676bd3800f345f","impliedFormat":99},{"version":"939fdf70427033c0a05d112c2b03e8e31f037b8f2ac4617df680107162ffb423","impliedFormat":99},{"version":"9bc7a3d724ff20d2429d94e087c276b9256946b2cd66c9f9bce79ab54ec9c115","impliedFormat":99},{"version":"aa813b5adf5ecf364ddcab7bc6652db73d5c4e43ee5f6ccfdc7737f6d3184667","impliedFormat":99},{"version":"e5fcc46e6fc608a77c7efea569e56e3cb02491a9fc0d74f49e784d0a4a6aee14","impliedFormat":99},{"version":"fd413d87e8bf7a8e523c70d194b2c3279016d1ec733a9db43640cc1e0cabde6f","impliedFormat":99},{"version":"126cab464ce86f9c155c0b79f9c38fa906c422ac02c856ff9874051ca35ceb14","impliedFormat":99},{"version":"4d84f055621f07107b6e882b0cb79848106d08899bde344eb6ad0c9bc3539eae","impliedFormat":99},{"version":"25ada8b073df8f9b669aa007ee66298095904b83236652ee940d827c2ed5fe9b","impliedFormat":99},{"version":"24bb5860d0b4310843a2ce164c113315db19861f3fae4f2a56727ca9b98dc4b4","impliedFormat":99},{"version":"b62d96002ec0c8710d0e99aa3175434e1df0f22f5a09291b19e5ec05e8a877e6","impliedFormat":99},{"version":"221c86478853bcca59d83ce0eb2832e575779f2244a9a0176971de55c45b9690","impliedFormat":99},{"version":"7b8940dddb145146d5e62f9d817d5cb9f54345cd17bb91a363c293dd5216a377","impliedFormat":99},{"version":"7d331ed732ddb23a5e04eb12716cff50491ba01b712f4810df496c174547403f","impliedFormat":99},{"version":"a444b1d18b18c90477babf60511e8348ab9d591698205ed1bf12f3a0bf5862e0","impliedFormat":99},{"version":"08ca4dca79ba1cc23d4610ddec493102d3fdef6bb57f025d99b1cba9759f71b3","impliedFormat":99},{"version":"39c2d0f3d8d82809c02668743fb19a50e66f05d4336d48765946e4a051d0579f","impliedFormat":99},{"version":"495e122ec7cd8b18150ec1191e48edda4e23b2587022e59805571ddf8a3b516a","impliedFormat":99},{"version":"9277cadea8fcd4c10616d7667f521274c5fc6cef385861f6962ef880db3e612d","impliedFormat":99},{"version":"20b20c535eb79b2a4a62229abf83f0fcdd3dc1f041fc3c588dbe01e3a7666ef9","impliedFormat":99},{"version":"e98970286b6514c67e3b0f916f23f8bb81ad6fbe3b5ef1f2bb013272e9ccb00a","impliedFormat":99},{"version":"09c1c46f10e01ae7399f2fb391178be7ddd42d70dc6a3abc41d80ccf73badad9","impliedFormat":99},{"version":"31c9882e1d08811f5821ea24554c0bd8a0d97fb7efc661ef76393a28d9a8eb26","impliedFormat":99},{"version":"7c9aecf4946da6395949f23bacaa6d7e9ce287f5aa65e50e69332ee5f4d1960f","impliedFormat":99},{"version":"6701adb65ce407ccabefbaf20862daf55d52dbdb2a663c899d163cc6cbb59192","impliedFormat":99},{"version":"f73df64d28c41e3bc777eca2fb49cb5cda69b52c3786b32d4dc473f855fca42b","impliedFormat":99},{"version":"b342ffdc48ee317927f88cb38871b984b7edf94634428fcad875c7a9fe5515ae","impliedFormat":99},{"version":"247fa787c809e9036079d3f4bf429f5c6e4d76a31647d5547e668fa25c46477e","impliedFormat":99},{"version":"f015d64096dbfde32ec9117706e6e1376e9ed0ea8534d17d1c4035262fb82ebe","impliedFormat":99},{"version":"0af38d2d00fc29764aead613ae52e263e235289ec9e2f365e909226e8b2df2a5","impliedFormat":99},{"version":"c610c569ccfdbcb03d9e531ac1be3ed944586e099bf4f756885fce2d5e1a680c","impliedFormat":99},{"version":"2062be175b1e4f6a0b6b21b4ad08c1e241833349fd82aae558000acb2a9c905b","impliedFormat":99},{"version":"32c98d5e98a05f108f4e405c853db481f83c5a1a9cd6c53870501d8248f9afad","impliedFormat":99},{"version":"c717d81d125641e3d95b30cb00d3c0179fdcb30c9e716c360aeb23c699e51321","impliedFormat":99},{"version":"9a96c65bc8d115c4cd1f6d61305013640593f0c0f869a2e6cebb7bbcdcd7313c","impliedFormat":99},{"version":"d5e101bf2eaafcf94b79c0a80a8b86e26ea0b24234f8f5b2c88b58cac0842a74","impliedFormat":99},{"version":"27caf95cace62037352d836d1c547a73363248289ba8b05205cb9eef146768ba","impliedFormat":99},{"version":"8ac1275f4eef836ace2b3779aa240cece0a7094cee65e3a56fc730a270695b0b","impliedFormat":99},{"version":"4dae97da440251bfb634edda1739b3cf39e66b56076e05d7b06bd3181a6fc500","impliedFormat":99},{"version":"66a183f89f492290d10baa4bc6840fac3a0212cd3e32f2230c1506eb6b1f84db","impliedFormat":99},{"version":"6576f83f333348274a02f3a9a048dfd9c0fbcc3515ec4e654def0ec5491a6261","impliedFormat":99},{"version":"f657e9bb81b35be0d298f305f4a6924c4b652692f9d48512038015e7eb79c7fa","impliedFormat":99},{"version":"5ea4c5fd9091e33b07825015ed1cce784854121cf42d337f0762f1d707ffacfa","impliedFormat":99},{"version":"0a6af3e7a2a63fec578ef9940ace9987eaa91450112efa665dd94cf26555463f","impliedFormat":99},{"version":"172423ba720956a2999c4e44a640d6b141c1c8646d96e8a88a333181eddb1eea","impliedFormat":99},{"version":"8d1ee20c4ca7a97ffb6c9b19049a1a9ebb34bfff32379261bc6295e82cb77abb","impliedFormat":99},{"version":"258436bc14be16b94eefec3da57b4eca7a3c1df633c79d4ccc35f18eaa9d8107","impliedFormat":99},{"version":"0ad0d843d93b5bf3fdaf79de4e159e28d6f9367a945970413205f345e9797cbc","impliedFormat":99},{"version":"f9a161a77ec523402d8d7dbaf9a04e9fc3d32d0b304dca4d7a86412bfdd1b1c9","impliedFormat":99},{"version":"ecaa337dd6eaa40a78934bc53a46455c969a9e2ec75e07da806552e5e1f5f575","impliedFormat":99},{"version":"747703dab2b5bfcb0f4372616373cbbe85a8a9e246bf4f2002252c54f79750a6","impliedFormat":99},{"version":"c09f4c7ec02ad3b5be269a3e220d69d3f16d43fe3843e2e75263344d3ce7981c","impliedFormat":99},{"version":"d351678cfdd7d86b5dbc0c75eaf66ada923f7ff1c76102508ac22f703cb9b927","impliedFormat":99},{"version":"b3d820765aa7672d9276e319e9a2b4d7a928b5dbfe34169e287bc2c0a03be70b","impliedFormat":99},{"version":"96ce9dcfef17a1945dbb4ec0ff2256f3847e813671bcba46381fa6673cf8b202","impliedFormat":99},{"version":"0d852b4958e9b9dee49676e33381e33280a0345bec8fde3f902b479bd0f69e37","impliedFormat":99},{"version":"1ee834bd1a5b21ee9f0f8e683ed8f46410f2548f5b81ae090c14fa41ebe3173e","impliedFormat":99},{"version":"fd875069349f1541cdbf2859ca8b0acdb81acaffeeb579f74dc08b332e8a2fc4","impliedFormat":99},{"version":"fb6994ae9a491ff440c5a78667f4d5783fb6c5827050db94f9ca7fb14f8ff260","impliedFormat":99},{"version":"3318f774e0fa8cd7decde2830e561c401e53057ea505c031f687966a16f4b32c","impliedFormat":99},{"version":"c130a5e599c565b49b02dfdaef22c5dc68bf648a9678339b44e8913d3d27ce71","impliedFormat":99},{"version":"06cb5fd4ff2e5cf532dfc6bbeae7b47ad7c2879909e6727ffdacc558115ebf0f","impliedFormat":99},{"version":"5cf2e81f262bee804fa9d50112c5288ed4224243b1837c653c9eec5a621a9b13","impliedFormat":99},{"version":"aeea67ef93786c8625e6c2840c5be41e6f6679f9890bc75628ac0a3cf8ea0c04","impliedFormat":99},{"version":"a37b86cc490287c9723338ed95965a938886313f0f912ba12f789462d8bad89c","impliedFormat":99},{"version":"41a073e65cbf693b4ca1f61f6847e16227d023cacfa75a84fb989efc3545cb19","impliedFormat":99},{"version":"e726badbad2c619272fe4fe528dd07cd5ef87bda456dc3656e4fd1bcc11976d0","impliedFormat":99},{"version":"60b0f3b27eed4652b4cf70ff359eecf92d1dadce962239812474436b4d608da6","impliedFormat":99},{"version":"4d7002dcc54793296ab4c4b1e28c00e99cdda63ef31b83ef616c58f8773c25bd","impliedFormat":99},{"version":"040dbae8a47533338afa394e6974e753b4bfc1895c322a3a715eb1be21eab5bf","impliedFormat":99},{"version":"a9d4d662f3494ab31e98c8193f20b0725a9488225df92bb4df2d9f96b5b7a166","impliedFormat":99},{"version":"6170c6827bcca40ead01d9a8e92e73049b82a0e595f1c11ef39bb98282781f7d","impliedFormat":99},{"version":"5dd074521b20eeb26c76fb3e1d0f85fb4bf26cd247c7dffbed08bd888a6d29d3","impliedFormat":99},{"version":"f3a8d4b406af14afba34488fec9b89859900a8df10510a23d9f1c2e8a116d3fd","impliedFormat":99},{"version":"b03aa91aef645f9856216a2223a47001a84954caf37b7ffb1d63d1327b4231fe","impliedFormat":99},{"version":"01b6435dae2508e231ded5ca79334075da7d6ca12d909765cb335211a90ba86e","impliedFormat":99},{"version":"75fc3992422a1d3b15788ee84656da98a10ce15ce5ba257a0df623a024a0d845","impliedFormat":99},{"version":"b6c5cede83853964b2f753d7e202613e1d461857cc3780a57a2a3d346c5afc0a","impliedFormat":99},{"version":"ffc4846043b7f71f310692e4bd38f349373981b832f907963e4bbdd4288f130e","impliedFormat":99},{"version":"54a730e06094b37f96436ccc8e736bb65b74d256439bf1663344e3fab16d2246","impliedFormat":99},{"version":"1389cb1ca8557f7380f983f00c337969542d6c932b1ba294b48f97f6fd1cb69e","impliedFormat":99},{"version":"85cfc4f1cd043b1df65ba7714d292ba7c6c79c9e288db0d4a9ea6a7b567a675b","impliedFormat":99},{"version":"74cc10ca21f4fc15188d7e7aafd66de5c34c82d011f0c9e02b05b9739e0fa31c","impliedFormat":99},{"version":"abe83f442f76121715241d0fc207d2c325510c6a4dfa6b07662f550c95c6a2a2","impliedFormat":99},{"version":"4b6de64797fd57745c2856f26b4c7de6be543f9335dfde7870a186c3541ff183","impliedFormat":99},{"version":"765122fafa15af14742c91619b7e30b36e5c38f01e6ad079d2c5ecd38a4fc45d","impliedFormat":99},{"version":"1194d3241ea56738d7d8e2b4908572a350cfe7a85b82ef89828ea32e20ab1803","impliedFormat":99},{"version":"6449789627c9555d2914c88498ab494cdc4f18e28a7426a1e74dcef3401f181d","impliedFormat":99},{"version":"17326f1b693cd3a0e89fdc1248097f0135adacbc072b0ed62cab9eecb1c21743","impliedFormat":99},{"version":"1b322be99b786ec951d3d14283aeddd32c3ab25033c4cb984b5224630317b232","impliedFormat":99},{"version":"c7523c0ac422da80b521031667dc06ca66d817ce5ac47f69db6fa98531febb26","impliedFormat":99},{"version":"19203771ab06e45e1524b7f608b332cad7143ba3ff473e302827a835ecd99dbc","impliedFormat":99},{"version":"3b7b9365174c24792ba2c762637b0bd5cbb8d88a72153e3f7f82d34e115d5647","impliedFormat":99},{"version":"7e3a2715195f927935488d7565bc30e7f540797776e1de208c64720d4ef87f77","impliedFormat":99},{"version":"5804ffbc65b78751fd510218b90827a7ca677ca34a45b4709a00783b658cbaba","impliedFormat":99},{"version":"0945a03ba41861ce8f75468e2bd1bfd424185418921fc2f55cac5eeeb5049c3d","impliedFormat":99},{"version":"246dc85745d220f0a1041d67bef89de1e02fabf49e6ce896bc1a345eab1fc507","impliedFormat":99},{"version":"616b74da95e0f9bca845458de4a8b25f12142b4a7b02e89882da05b4cc115802","impliedFormat":99},{"version":"b894722e4b4205a60154ee3d6fa8ecc3ffdfb92a7bd38936f666d3f00be6649c","impliedFormat":99},{"version":"1f7f05258c0992bd696cf00984e640011ae5477d7aac3b80fcf61bf27f42fe88","impliedFormat":99},{"version":"9129342b97e39ef2c9df4848dfe011329cef9b27e719c7913fd3859be5fc0cca","impliedFormat":99},{"version":"351edaf90b54a559e1759f7ceb54b7881079cba5f4d6dcf15bdb26f1877dd2c6","impliedFormat":99},{"version":"3f79205d951373afec1ca713cbda4be9816d97daa795a9e0a37fa3ae5429afbe","impliedFormat":99},{"version":"bcc4c8b5a39356915b8d366e3499a28adc89e2e0bffc02a108eaec1c4797a58e","impliedFormat":99},{"version":"5b2287eec9804a7fc7c6021ae0a7a92b0160750eb21604b77203589e2ad905f8","impliedFormat":99},{"version":"03870a19c7cbbad803b0ee2d69b777e12be7734e087ccfb0c862529a41cb493b","impliedFormat":99},{"version":"46a52d6ee42784826515dd6ab9f5afaab3a05dfb49ddd8298a2026b6c756b944","impliedFormat":99},{"version":"995f334b04df585cb2a77b74533441293ff1e1d4549c86dd5495494c1fc3969f","impliedFormat":99},{"version":"e83b7824f3d983e9b8c2785541579cd8d8c153e96959e71ab4f69bd83c71f953","impliedFormat":99},{"version":"7b0030262f3d2cc74ae1dd79f4990a7131c34935b2c177e6cfa17a88a6ea56ee","impliedFormat":99},{"version":"a923cde26c2e5431e455844ac5f31126d45976c85f347c7dfd2b9eba3e8ef63c","impliedFormat":99},{"version":"3b2738cfacb777ea1f53acdb26b4f4306fa3dbac7fc5d0f1c4750350d3f5741d","impliedFormat":99},{"version":"b95d11a17e57f0cd0ab04aa8148c8f0ca3a68f56c9a44ac9179cea8a6cccb546","impliedFormat":99},{"version":"7688f3196338007600eba7158240aaa15ad524ca42c204fdb3888446fd690086","impliedFormat":99},{"version":"514f33cfc8bf4a00d0603f6df438959657ce42f94e93e29df29fa9b58e7d54f9","impliedFormat":99},{"version":"7568cf2d6e505847c539e63406ddbde2ccc0f96f2e6c5f115a4b9774d0b55aad","impliedFormat":99},{"version":"d05bd9004c654c2583de473d77f047f03719e3e7bdbe62861371755208e36d59","impliedFormat":99},{"version":"74371225d6032ec7f73b46e736d9ff6ea3626be6fc7959e8b71fedff0bb75cf4","impliedFormat":99},{"version":"06dd247275efd44b3f91270763246700353f1add0945380bdbca8c90a517f9f1","impliedFormat":99},{"version":"0833e55be9920ff787cedb7ea623e97ac9bab28961e0e11aa4a56d36d6074dd2","impliedFormat":99},{"version":"92fbb2b6566fdefc6ba3f151299b2618bd1780cf26c2d0078dcd7f1bdc1c551e","impliedFormat":99},{"version":"3b5317db0574b276c1ecf6ebad9faa974f4e416786b682ed1f854cc85837c3df","impliedFormat":99},{"version":"5f27b1f1b03636451e90fc414bd8426a1db25ad438782354bea60f47d7efb9d6","impliedFormat":99},{"version":"53eb12cfe4c56afff32a3b8adec4fefefa12685c84202c8207351004d30c3b3a","impliedFormat":99},{"version":"05bc3698de467024d02654162f1eeb4edcb0ed9d855a96133572969a6f3675c4","impliedFormat":99},{"version":"a5ba4a306d8bc21ac2fef4e40e9076708dded0176aa21484f1f6da23a4d400e2","impliedFormat":99},{"version":"1c825d1f1bd9e70c306f6c16a0a6b76ccfe4be9350857831eba93e59b95fbb5b","impliedFormat":99},{"version":"b27224caf8db7ed9edf9b12368cedb963bbba3a9b5143c68dff53f5fb2351c96","impliedFormat":99},{"version":"eb3bfb8488f260946c5bbf5d9e730a6e23e0c4a568fbbbe782f3c365e0595dde","impliedFormat":99},{"version":"15de6ee96c8e0f6a78fed11e60c3a0f9b4535c1e6a802c55d65028d500e91e75","impliedFormat":99},{"version":"052f62cd94d56a5ca9d8ce7e68a2201fe8f399a12d7803be2619fd03dd36f1d9","impliedFormat":99},{"version":"06e98ec1e0428de740d985f3480b2e699826d5cd2fe2457f1265b32ff4797ae4","impliedFormat":99},{"version":"01b8daaa0be6124a730b7170c1bb1375f7ed6acf1b4b49c1389199b5ffb600e7","impliedFormat":99},{"version":"7b0d3cca9104d4d9f484ca0a64bf731ff1aea842c8a4bf93618814b1a8281992","impliedFormat":99},{"version":"e40aa12df628390fb3819a883c52c51ef94fd3998e74965fce6a38917a0530f4","impliedFormat":99},{"version":"0df397db19a2db183105dfe900d75798622677a5db73038608bd325f86a556ed","impliedFormat":99},{"version":"ab505b9c7ee7649920023b14384c71e3c542bc7535f51028dff27d70d2b1d6fd","impliedFormat":99},{"version":"29a9fb009bcc76c847dcf73d820d276d6353e5c6c4c016c847d51e42796f68f5","impliedFormat":99},{"version":"743751f2d8819fd7ac9d3ef6378614b6675d3101e42ddc18767901693621cb2f","impliedFormat":99},{"version":"e4a995fd487783122df0848df4c871dbb536e1636e8e7b6f6186d2993e9761e8","impliedFormat":99},{"version":"45da721f9a605485a439778c248dfbc6351341d87de448ec266b74185e090631","impliedFormat":99},{"version":"ec47a4e180f0cf61787ada2d4691a1cf4f7fd65482f6fa9e01444adff3cbd6eb","impliedFormat":99},{"version":"3209d42dcb86b35a13c127fc39981a644b61a1fb0e59524038d0f3bd7fe25768","impliedFormat":99},{"version":"c8c16f7fdc34f8bda36cd9827b11c065e94ae25473465b2b35aa71df336ecf63","impliedFormat":99},{"version":"db07a4e9f69cc9b58930c2d3a4ad1fd9f882794b92208d55ab057443081f649a","impliedFormat":99},{"version":"735a572ced293fa984b3675cce56091902a0529cef028fe016d9670e3a94dc8b","impliedFormat":99},{"version":"dcf0056dec8dc80fe76eac1e8c6fa778a2e4c094fe2d4b120e6f5bcabd820be8","impliedFormat":99},{"version":"6268a89f0ce2f857f6f7ada0045bf8dc990b449f648b51522c0a7d84d016fe85","impliedFormat":99},{"version":"efd3f26f59c3291a0998435ad54c67191b39b4cd0d451ac807afd8da86bc1996","impliedFormat":99},{"version":"19b70aecc85035f5faef7f3da8dcbf199af4ceccbce15a670950377b388c1d9c","impliedFormat":99},{"version":"d621382b4ad80cc27b2f670e44e0bb11a7e85cb0f6a0b043aa0c9b6b21b16a15","impliedFormat":99},{"version":"5c1f5f0c20f5171a182440cd0347dbb94e5c84f5976184f2f36dec92afbd9b9c","impliedFormat":99},{"version":"1a7e2345e3b20202800bc92adbf628d22a74902b8a5c87a6ce3c361d3ba314a9","impliedFormat":99},{"version":"eea9dc67b1bd75f72aad8483567241f5fdbe46436f018df7f0719e7ee5aa85da","impliedFormat":99},{"version":"5bf7ec4d84bfa8c29f32b7cde878e8ef4e11b1bbf0f4edbb9e851efbfdccbd2b","impliedFormat":99},{"version":"3a49927f72440d36c50e1b62f5cbc2f296253d151ea4e5484ecebc8bc461ad4f","impliedFormat":99},{"version":"ce293a2b914083388ff1de83875cc6e82792c5dc1e99c3be4b787f6b150516bf","impliedFormat":99},{"version":"f8d6e2784bb518d523898f614b8c0ae55341968c982d4617f08867b5d11cf354","impliedFormat":99},{"version":"1413f593b860e74f717f40bbf5c934fd77ee6cbbc630216954bc1a364d5d58a6","impliedFormat":99},{"version":"f7e358590496240e80dc08cd1b71ca492e4d27664bb403d3efbb9acef5075b40","impliedFormat":99},{"version":"656e30f229e3a05096b21a8d0b4a37cadda6201d74631fdfd6a6f52f0c158831","impliedFormat":99},{"version":"03206d1ab6b7f08b118786a903cf849768c8c927a21022df88fe63910ddc3433","impliedFormat":99},{"version":"702cb19c1b38ca1b2d158d765869b667b2c1e5ca0e62862b7792285055cb86d2","impliedFormat":99},{"version":"396f903b4d3bcc1d5a72580bb0a8d9f90c7dac5e481b81c2b58df80c968b64e5","impliedFormat":99},{"version":"0e8707f15586d91f92a120b4751048061e04fdee756246667158d4df0105dbe8","impliedFormat":99},{"version":"c37e7b3d6c0b5da08a46d028e980becdd8d48d7b32b7644209695d75d43f653c","impliedFormat":99},{"version":"fecc5365f9a1dd29cc8c582bc0427a7bf06a52c2a42cdb4b25012976628faa6e","impliedFormat":99},{"version":"0c073335c77c5ad0240a0303cae56c8be8da93e206591c5a5a8bd6a613d78d18","impliedFormat":99},{"version":"827c1178e5058f0aaa9047725b845d598f0f52871792441412059173f895597b","impliedFormat":99},{"version":"9e8ed20b5058a6f5f773f420c0efce5c8eb802c0af94cdb96b782cf2acf1b00b","impliedFormat":99},{"version":"8b0336c60458945b1fee185149fa4b5a512917aa171d4232b1f0805c3c12e31b","impliedFormat":99},{"version":"b15377bca02bd4d77f5d089fa0c7a13dc251b104de3b43f62dde6955cc8ef7e8","impliedFormat":99},{"version":"020409dbb29a4396e3c1c0732a0f8afa939e47c935182f6fd0603e21d5a6a8f2","impliedFormat":99},{"version":"bb259ffb75be8a11b1be05d135a561391f7123110d75074eeb5207be382ceb70","impliedFormat":99},{"version":"1f52c9be8dfb11cc31d9e2aa4f950ef56aa8eaef1b78949431882f70e10487de","impliedFormat":99},{"version":"1e685ffce849148fe9e9649189957078d9495608e9df42cfeab20367d2d70c75","impliedFormat":99},{"version":"97e30735672fbe25393231a53ab5e3b63d34e74d0697c59ffc034f9119c23d31","impliedFormat":99},{"version":"84ffde0a761e4b6cbf3cf90c97c4c01608962e8b55082f3705d29465a194f449","impliedFormat":99},{"version":"d874bdb89c1172b0eb109873d39175a5f210f5d853439e7eb250102622edb0d1","impliedFormat":99},{"version":"2b7e61a49cb27bbfc53fd5b888705290beb2d1fe78a8b433bac1ce7544113904","impliedFormat":99},{"version":"8b28a7039c2ccb5108bb3a3b771ca430db73c4ec9e47031303b8e87732a859a0","impliedFormat":99},{"version":"156eb4c6ef17eb61507364b320e2812cfc5afd862cb1baa251b2ab412384a2a9","impliedFormat":99},{"version":"9efc47a0e98346bfd4b386050634b4e150e6c41dd6d9b2bc1288e80a0f345390","impliedFormat":99},{"version":"2bd0a3ea02475382ae8e87d78e3be763dba251ddf9629664ce73c706b400dc94","impliedFormat":99},{"version":"20008d2327e19c4fd051a2c0ee88ea696d704bc6d7ad39988fc509d81c27a485","impliedFormat":99},{"version":"6cee28d40bc224e61f12e867140ab6d677a03a1defc9ade08b1bd60ab1c06524","impliedFormat":99},{"version":"3a0bb28315b2084f25a012275ef45e180ea80d9ca4bbc37665b9c67e912e998c","impliedFormat":99},{"version":"17662ae9763596c2ddaa833f9e326b3de9289098a71457ee18d2db9407cc681b","impliedFormat":99},{"version":"9442dcf95088615dd8ea58077ebed1f7d5dd662caca210b245a6b19f38984038","impliedFormat":99},{"version":"baf0ad4aa9df446c5b08370689dc08e23e112fdd1a022293676254fbb7897a47","impliedFormat":99},{"version":"e3a929f769e33c3001244a06d6a3e025083be64599c1e961aee31145d623e824","impliedFormat":99},{"version":"083493311f28114ab250a8f379798214e91f264dce121fa2140ae58376fc48c6","impliedFormat":99},{"version":"fd03b3ac929f2bcec6710176bbcdb34969d7f9810b01f65d19cbdac143a2c7d9","impliedFormat":99},{"version":"f3e2f84bdacbe962c856add41824ccfd66fba7b320753f6e9c6871cd6fd5133c","impliedFormat":99},{"version":"d70ae743099d2615ffab06760a3571a2beb01fcb27366cce4025544603a6081a","impliedFormat":99},{"version":"530fcba9474606ca2eca0b85f91b26d5e24c31431c27d20403928d51f9c1931f","impliedFormat":99},{"version":"c483babd94cb2effd09a918f5cacae5fbc8cdcb8b65b1a28cf07c2a9381f2a0d","impliedFormat":99},{"version":"c808470b50113d547da502f2380c6674fd41908d641663e5944a6070113469cd","impliedFormat":99},{"version":"f0568ac6f1c90cb01c4a2b3d14c0c6e734cfbfa34eebc57d789db55e7d0d34f1","impliedFormat":99},{"version":"0ae4ff7dd81505058a06f617152c94802f16fc7a8d2f768c8794f53f8be57178","impliedFormat":99},{"version":"3f61a28c42e990b337e084e92d7fa7df04f8a6b6699da3754dc59611d189b40e","impliedFormat":99},{"version":"73fbbf32113d791d019c474cf474344bb36d4c375f9622728163ad5640492a39","impliedFormat":99},{"version":"91b6fbc14c8a81bc1751cc033f55e0cb6f3b346653d51e30efb7995ecf969ed2","impliedFormat":99},{"version":"77e2fd9131fc81ffaffdc85a8ab553f869f2a67b236ceb95b85b9a1bd72b8823","impliedFormat":99},{"version":"330213ff23c7adbbb6f1b5ead11fb8dfb731c5c24f8c4a18586acaaa47e74077","impliedFormat":99},{"version":"a9f07992ccd51ff2a089628480d51364e19be7e5b22e04edd7e18a519c50e2fc","impliedFormat":99},{"version":"26f62f6b63fff6ad7abd3fc5d89d36f8c74f6ddb32d64795556d0ad3ac6b2d29","impliedFormat":99},{"version":"30c55932c3859c15cfb16c4cf3cda9c303588f3216f8b1ca205e2c41bf801402","impliedFormat":99},{"version":"b727fb19b28fdd8abf41b989f9ec0a6aae52cf07f3918386ad068b33d20c3468","impliedFormat":99},{"version":"d7fad08d42a437ea163bec1c3d08e5e4714a27636d89809602f04328a54a3fa4","impliedFormat":99},{"version":"22469dbd699381a169d6e02d5c080ba9d94b9d6567b7a5c41cb17f505e6a4ad7","impliedFormat":99},{"version":"44412e7238512522c472296f100c52c0accc20d3ee75db7aa503ad4d92b80754","impliedFormat":99},{"version":"27c4c4f9114b51cd89d2ba83e9fa60bacc6c29a1279f2f3b91d19c2f7b2c68ac","impliedFormat":99},{"version":"6acb809bb284648297faaefcb03e0e4500de5f78194a08b75512e13e5887829b","impliedFormat":99},{"version":"c31062874243eeb47ba70f53686f860d4c238bed5587af12ea4f73389ce2333c","impliedFormat":99},{"version":"b4f0992a1069bd5af311d02a49dae7aceb5e0400856449bd766b994267e2adba","impliedFormat":99},{"version":"adf2b0d2362e1b4c99336c56293ac3da8aa0d3ebbda67f963d4a0f3d3ef2a021","impliedFormat":99},{"version":"5fc3d9350eb34ad3cbcb1b69249161a33ffe19d7c0e72e6087c947046de6f756","impliedFormat":99},{"version":"b0418e08aab8aa9e4e406428964d2adf8187dd29f6cdaea32ede28fc36e86f56","impliedFormat":99},{"version":"be4147ddded6518b57942a23f89b50b772d841a97e22c93b70eddf901c7581d2","impliedFormat":99},{"version":"6b952ce628d71b1e1644cf8aea26a4de997596197158dc7b6e71ec356a8cf992","impliedFormat":99},{"version":"2f4dad0e02e51c0d630d46dd18b3a99a1d8c9f184af3e9d109027d8d11735f9f","impliedFormat":99},{"version":"41c5600e8662d67b2a149c2eebb422c80cc2337945f5b79dde92d41427499496","impliedFormat":99},{"version":"9c1e78acaead99ab9c612e54f5e16c0675cb6863627ec2dffa0c3d5651d53659","impliedFormat":99},{"version":"6df75e65602bbd54c977312ed62988e0c64423b046ed74ca126b529970233a2e","impliedFormat":99},{"version":"3ffc0815b3b1da65f6fc42a2a10aece2bda56d024cbcf7477b6380d4249ff8a1","impliedFormat":99},{"version":"7cb50c74ced03d93407f80f61840b52540cdc0ff7189ca603e6995306c2b25c2","impliedFormat":99},{"version":"a44dd85a5c1ba838eea01fd555504229ee74d97b1d237741598e7d97c0e857ca","impliedFormat":99},{"version":"aa7f2b3a9f4bb8a225b3a5e5c611b1a034ad76c3d870a1062e241485e3968e23","impliedFormat":99},{"version":"eb41d07bb7e2d527ac33c71146a3a4802a24d39defb6b8e4d707e5510074d076","impliedFormat":99},{"version":"405bf967f547561f6810f2903df5c5b3c7528d55917fcea0cf251951bedd879b","impliedFormat":99},{"version":"0060d5fcac50ed959be8765d1f5343eda5641109a62eba69696577e004b891d0","impliedFormat":99},{"version":"def4730fa85f358f1257bf2116242bec72080b1a0c70046d0c05ff7f90164707","impliedFormat":99},{"version":"5336f4657e6ffcc8bae26bd762b09b80ae6e3b67dce0a4b4aa99f5baab00c65a","impliedFormat":99},{"version":"8e728eefe8c7160465492dafb86f25085ede8c6b05e360dd2c7129955a155da8","impliedFormat":99},{"version":"c665cdd809976f388c82e21c47a040e5e19ba6cb953d0e0c1c38e1ce61f40922","impliedFormat":99},{"version":"c3bdb6cc2b1abe32815c4894c4d011d4ea80c79d0934d264b467cc6ec0051bcc","impliedFormat":99},{"version":"d40c02d227da200dd6be4e7d56ec2c560c08b9e24a4688a071f392b37953143a","impliedFormat":99},{"version":"01b9b0a56a739482aadb7da55886fa724bc2b557e9814ef4841d2262efb9846b","impliedFormat":99},{"version":"891117d566ab7e1a7798d83c58a20957c1703e92d5a351802081c643cf58faf0","impliedFormat":99},{"version":"6f925dbb5e83ba81d632287af1706945f435bfaec89258540eaae87817804c84","impliedFormat":99},{"version":"0fadf459265643344979f57c02e7ae5fdb5c70244fc9ccece5a1a977fe0b1fb8","impliedFormat":99},{"version":"0e77a1ed700a09eae143529750cc2eef65b8e28d76cf8a6eaf78b7f1afa24c63","impliedFormat":99},{"version":"5408800bf96b2cdd0d8d77e3d52f6848514efbf1590d96d9f8aa86c8ee95bbdf","impliedFormat":99},{"version":"b6e0ad0ba28715ae23a61b1192cdb24c06a909aa58b2048e64e56574aa4da7a8","impliedFormat":99},{"version":"c20b3e5d792dae26f5bbf8d1b73ddd16d9ddc336e32a301b9dd99c68a779f61e","impliedFormat":99},{"version":"d71713801d5419399f8edaaf0471dea5e578dd8b71eefde7abf387fb372feb1b","impliedFormat":99},{"version":"89b56bb82308d69d9ea109de95fff39ef64bcabd250688da972fcea05f50dad3","impliedFormat":99},{"version":"214f90578d41d0f5bf61b4d3de16b4671dc75fe893b803a483a4e7b96e80a1e7","impliedFormat":99},{"version":"32b6a2a6fc20f85513ffb0f35e455dfbaf058f65f063a10dda07ffe9592cd98f","impliedFormat":99},{"version":"8b2bdf89d903b856b52e4d416930701068a3522e9e8c2705602c6e7e2394e86d","impliedFormat":99},{"version":"c988c702e73a0ae03ff6d7868ebc2dd0497e921c3b7ea4fbde42aa781831b8a5","impliedFormat":99},{"version":"8409e2185704c03d12e1522dc4c7b137b6b7524e2fc1f9baee7581ee28fc3d86","impliedFormat":99},{"version":"95195cfacab74280a41490ca2c731fe499a37d7ffcaeac7dd2d9056cdc694623","impliedFormat":99},{"version":"9c8746b57866938dccd94775ccc3abe27e41d182b6f6d32ce82a1044084d3778","impliedFormat":99},{"version":"be71dce0024b565b17433b79dfb73c200bd087568e24e796d712cbd42eebf8cd","impliedFormat":99},{"version":"4d9081308548bde06c710ad7bc3af5e6d7e24538378a4c10eff2e769dec31bd5","impliedFormat":99},{"version":"09e693240afe609150a21882e64d8f34b664eea485d16ae78ac86cb3a47de3f9","impliedFormat":99},{"version":"89502a94ed72858e0018b65766f8deea38577994b7df9d406afc47224fc259c9","impliedFormat":99},{"version":"2c19973a0dad8e650d42349838ecc7bec9e181c28f7aacfc045eb3c0b8c7db19","impliedFormat":99},{"version":"851bba631a33a4413ce53ca3586b8a2d5799d0450207e8f7f9b594340e8d0af6","impliedFormat":99},{"version":"9772a1a4b6a4a8c16e2564c0d83848bc92c5410378710f9da8fb2d912ac32b57","impliedFormat":99},{"version":"ae66b8a49700f9b0e1e857eb7989a033392b92b5a19690c9ed7f8f403a1e219c","impliedFormat":99},{"version":"a095cd74b349b5c587c52343a00871d3a522d5d00614275a608e5c3ff690468f","impliedFormat":99},{"version":"ce2b17e7bb13676b9cfe8b9d71db509625851486b845475bf336e2c6f58a2cf7","impliedFormat":99},{"version":"68ff0025b0ff8a90165ae54d417191c8dddf93c794fd54fbfef6d4ea75f6ca82","impliedFormat":99},{"version":"6ff5a35137457c0c733501de9300f1801ae9abb33aea7bc9c6bf5e9d6d98cfc5","impliedFormat":99},{"version":"71128c986c2bd2554d203c724e897471277d96efae9d67721835e0174bb19a97","impliedFormat":99},{"version":"5118e5ed493b74299ca53eca1e5a422fb8f3207337285fe9206dd5d1f88785ad","impliedFormat":99},{"version":"aacbc0d9b6f47db9784a2193fcc7f4bfb1fc6cc711587a4bbac43e45432332ea","impliedFormat":99},{"version":"5be892a93003f44bc4420408ec0726322928020fe22f9a68264a176dc4eb8b96","impliedFormat":99},{"version":"9cf47cb5d151b9a09d0d2fed8b5858d726cbde497560ccb136557aa203364208","impliedFormat":99},{"version":"981feaf9d706617834eb318674966a8741ea35c93ce33e0ce155498e665d2593","impliedFormat":99},{"version":"8e661b24aed6caeef42e16eba111174c13ed178a660b41fd8f82401dfe129515","impliedFormat":99},{"version":"74606837f50a3a16d02993364c004db527b47cdb828edbd770595d5e4ee8dbde","impliedFormat":99},{"version":"d7e7588481cd78747b1d6a9439feede87c2e497df8448acc74d9803867cfdcc9","impliedFormat":99},{"version":"a46d60895edd2436d8927e02798c82975267d0b6fe3af28d7596177f23da3639","impliedFormat":99},{"version":"f93b561633fc4bf5005f34f0c2f96f48c3e548d1593136cfaa9331d7294ca417","impliedFormat":99},{"version":"d64b9ad5dc93f6dc86e1c13f5e483583597b35fa0ad3170c928e436253b1a252","impliedFormat":99},{"version":"23bbc076a14d01df086f77870c735b053cb1c9dc07c2c8b160f6a04db80c469e","impliedFormat":99},{"version":"7f1f69fdcac775d124fe626182219327b833a962de2c9751073d2643695ce2e0","impliedFormat":99},{"version":"ad774bd48cdebf2909e354cb24ed9ade7763306edda185b7692890a2aa96be4b","impliedFormat":99},{"version":"f53c345523d49bc3e6a11a5f6540ba145b441af3618efaf58d25b58db03d2922","impliedFormat":99},{"version":"164af37e5cde8d2d830b5a5f2aaa6be547b8004e4e98b33fd6977581f8be4d4a","impliedFormat":99},{"version":"2aa08243d9c596b3e993b558033dd391f39ba4d6525ccba992b11bb5be54c04e","impliedFormat":99},{"version":"208371c97acf811ef41ba4b217816aedb802a570129042463b198c7d72d1cca1","impliedFormat":99},{"version":"6311ecffa1680ff0f9587217df76d9556d4c8c623f12464b8beb44461d1d22af","impliedFormat":99},{"version":"16e2700613d061c8a3c21fd26bdff099948396954d5935ce913424d93c97815c","impliedFormat":99},{"version":"0890d6e6870d35b625590a98abc2bd3fa880fa46d0dc3de22dbf01628cfd34b7","impliedFormat":99},{"version":"193814fef68f60058efb9c02cffd20bcbf70eec1d32ea0fce4b5887aef746157","impliedFormat":99},{"version":"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d","impliedFormat":99},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d","impliedFormat":1},{"version":"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88","impliedFormat":1},{"version":"a9ff5614fec6e47cd306851cd39e2bb0bd1b939a9776cad032bc06753a24b105","signature":"2641cc270e66b5b412cf0f887ef90e12173ac7773390a8e0008f653358f66841"},{"version":"709504c4a347b021a9984ee3e65359992e9f0f172d22e63030207d0c604296d6","signature":"b0a30a6f3075e34a6a108ff4fb8c54e7714f964c0690db0b6e82bed93ef6568e"},{"version":"c91f4f63d6c2c84d2e0f7368a97dfb126ac74804e775e5aa5bcefd853917e69e","signature":"96d032d99c255b941936f513419610586f7e642f2abb57d1b8d2581f7d442eb8"},{"version":"a313760e9f66c6f819c3426e038acb9aa8f47a59be74062f51321caa88a688ea","signature":"439593d167651f2e1c0c439482dc3d5d5eb248ea221ecd8feb5c62cd0d60cd86"},{"version":"2c82ac3566fa4072c5cc6320a0a786afb9d27c061d41316411483f61353560eb","signature":"e0d9f1fd5544f50032be81792d9409f65c8ea46853ed0450b9934372d4255930"},{"version":"829b5cb87df9dfb327efb8a4e55644d809f3e03de209067122b99ffebf284f00","impliedFormat":1},{"version":"415510d38ea33f28cb571ef11ebd6ee777a377e0d1886b6771dcb15fdde7a02f","signature":"5a4e0d921d1c64c046a46838efd87367a659f8debed6c7f7801b8440576657de"},{"version":"6e9445b11a3d075d64853d8b32efd159b4a45f37b481bbbb7d3bd57f5a5d5f35","signature":"589cdbba6bdaf20ddef1fe78e3bdedfd4e7f6b6e08179a9d8197ded860ebaed0"},{"version":"80d23e36921e787529d3ccae753675b91180dc2326b4c1a3d8f270205b85af79","signature":"845a9728a8fd9284d40c63aaea7b11076866271659517e0ab1a1cbd041bf8588"},{"version":"1aadf3c39d08e4aeea1b9950040079b0fa8baa1d5f9644667cbbb6b9c8c0837a","signature":"730f18e9a86d7032845d6a326f8c5ec9469490304565e2a637f4dbdd8db08977"},{"version":"03a87f22d5567ad70a9761d76f0d16ca6ae32b6201d79c4946e751f2c4cb4e8a","signature":"daaa96af8feb9c538eac60042eb231ecb684bd361d5d7d5fccb0a614a41c365b"},{"version":"07e1a6c1c01468d20ce760a06f39c9e59c56103e63556c7240bbc9e8c2b8d24b","signature":"e6ec95dc819ab75e36c9e4492ba3e6bcf21507403a6afb5bbe8cdea76fd77fc7"},{"version":"e7205096e87497cb983cffe2ea271035dc0f7bae9db702859e5a2d0941d99597","signature":"1a85b0cd6837d60863844ad43f065863cd13b3cb956c369d493761bb603f4b63"},{"version":"47f5078d810ecb6e57eea5f0382dbfb9db641a35460fdb723e920c4898852e0b","signature":"df6ab0ed5a36c6500e0cd4e0928f73f80fa1bc047359a22f5023393f4023cdcd"},{"version":"21e365e7414b00e1dda3cb0e8c1ffe7eaf8f4cee8665857e7a4ab0051c694811","signature":"d36c6cc5adf1dd3c897e4bfe96cfc0506c9352c7413cd83da0d3032f820781b8"},{"version":"800de8bb8ea525980e16dd155bb6e6847e7fdeccaf816e5c2674e1a24c5bfc9a","impliedFormat":1},{"version":"88efe27bebddb62da9655a9f093e0c27719647e96747f16650489dc9671075d6","impliedFormat":1},{"version":"e348f128032c4807ad9359a1fff29fcbc5f551c81be807bfa86db5a45649b7ba","impliedFormat":1},{"version":"8ee6b07974528da39b7835556e12dd3198c0a13e4a9de321217cd2044f3de22e","impliedFormat":1},{"version":"deefd8c43b40f9797c3921d78d3f9243959621a17b817be7f5d95c149f23a9dd","impliedFormat":1},{"version":"5f12132800d430adbe59b49c2c0354d85a71ada7d756e34250a655baa8ad4ae5","impliedFormat":1},{"version":"ec27c0cee1436f58e785f621703d19d588ebbd489eca245e5198b4d6b715790d","impliedFormat":1},{"version":"b16e757e4c35434065120a2b3bf13a518fc9e621dc9c2ed668f91635a9dc4e75","impliedFormat":1},{"version":"efe2821496a760b9128309bb69ad43f1a99feb49d3fd004673c5e406de523da6","impliedFormat":1},{"version":"ea0e3c7d1347a549ac7ec32d3c61a30e473dbbbc901d458064db03f673128145","impliedFormat":1},{"version":"4374cefdde5c6e9bad52b0436e887b8325b8f407c12035194ad02c28f1553a3a","impliedFormat":1},{"version":"5f1ba0898eb0a54a644cb9c95c2240beaa961d87fd080cbb90807a6cc03daeb3","impliedFormat":1},{"version":"8e92ee8710ba85b158c5d91b0bbc9d0d033f5e062b6e70178063f01b20f63a14","impliedFormat":1},{"version":"ee933420aacba1f60aa70fb8ba47c5e69001b005073b71973114587089a13c7f","impliedFormat":1},{"version":"0a0714999d0a5bdfacd15c7b34cffbcc6f263f6cb0ccb42076cdc541c6987797","impliedFormat":1},{"version":"56584bfc655f9df64afc0f22f7d1122c29e5b74b342c203b891e19de9fa37de8","impliedFormat":1},{"version":"40ec58f0fadd0b3981b3d383e1c12fa0680115ae9f018387fc2cfc0bbcf23204","impliedFormat":1},{"version":"59709e26e08d4fd4c6a133552ad8f94c5b31463f295c4bf75fae1907738b8441","impliedFormat":1},{"version":"849b9e7283b7309a4556c9b90bb8e2dfc27751f157798065bbc513dcddb09a8c","impliedFormat":1},{"version":"76bba0c97594248c1be19af32d5799f7eff51cec2926d8e4dd59267d7636a0b4","impliedFormat":1},{"version":"10e109212c7be8a9f66e988e5d6c2a8900c9d14bf6beadf5fa70d32ada3425cf","impliedFormat":1},{"version":"f4558bcdc26690cc593cd59217cd17d8e00af0f5fbd0c4f1c0d71ba75029c42e","impliedFormat":1},{"version":"51d621c4e724720dd1b7ba6374d8a5b988beeda22d620ac84634a13691b631d9","impliedFormat":1},{"version":"f57a588d8f6b3ce5c8b494f2dc759a8885eaee18e80a4952df47de45403fedbe","impliedFormat":1},{"version":"34735727b3fe7a0ed0651a0f88d06449163d1989a2b2de7f047473adc7c1c383","impliedFormat":1},{"version":"a5b13abc88ab3186e713c445e59e2f6eee20c6167943517bc2f56985d89b8c55","impliedFormat":1},{"version":"8b29e3ed0c90b2ebc40b2bce5a518a0e86c0c417f7fe99a5e7658a61166bd9cd","impliedFormat":1},{"version":"7ae65fe95b18205e241e6695cb2c61c0828d660aca7d08f68781b439a800e6b8","impliedFormat":1},{"version":"c2c8c166199d3a7bd093152437d1f6399d05e458a9ca9364456feecba920cda4","impliedFormat":1},{"version":"369b7270eeeb37982203b2cb18c7302947b89bf5818c1d3d2e95a0418f02b74e","impliedFormat":1},{"version":"94f95d223e2783b0aef4d15d7f6990a6a550fe17d099c501395f690337f7105e","impliedFormat":1},{"version":"945be5a9505194381cfd4a8551a5f0ae48090847e454fecf834e054207c5a57b","impliedFormat":1},{"version":"d1e8b78a5ce49cee9ef4cd2565d4645d269c6fd0650e3592f85ba481f13da3a3","impliedFormat":1},{"version":"61be8f1d5345cf5750aed87af2869888ca1b675ffa481f1d4d80554e10084b4a","impliedFormat":1},{"version":"eeb6c806376b9c3464f29b6058aecf113328f9ce290af0375e520f1a844529cf","signature":"61f11ef9f7b473f14c872a139f0a329251738f177edfdcaefb3745adf8967036"},{"version":"216c830de5b7e1ff7336a1bf11dfe9c98ae2de2da56f616e7e4b4405aa14050d","signature":"f9653d5c0a8d7199894c3721eae87d898c8ce6668c3c28461dde2236367c94e6"},{"version":"ebfca49b6f505f572648960feb0bc5e131c9a6bea97f3f5883dfa9374ed4028d","signature":"5d2270355cb77cb6e68e65ae1d5c258abf97b4845ab9653f0ed1626154bbc114"},{"version":"57bd6ff2a23d6d3df5b81817735cb34c18e6c75e13425f9915d146273a27e776","signature":"a1f8f9135feda1e04f2f32d3f5fb9ecbdaf2dfd4fb30be0d1aacc901a55929e4"},{"version":"b16d890b0ea02f67586f064f87af862c601884fd40ec000b3ec8dacdf1c4c7cf","signature":"c4bf08d84391225b229f7d67fe8f7b3ff511782f27e0d6f5f4680aab2cf451af"},{"version":"ed0300836934be9970c950c0d485e8276fac87cf1fec92f07e5f13fb7203604d","signature":"e3d48af43b4af0455edee6944467120f4272a8306e90d504935da490b053cafd"},{"version":"a7e4a0f02427c3e07643a6d5bb9bf0cc09f2ebe28b37a42189f923133c43c186","signature":"01279e64b86fc37995c2df2f8acd601c7126eed6c6245b1e913a0eaa353f4362"},{"version":"5a6ea8dd0a1da3b272eb9ce6c258c6513b5473f7ebcb2395ff7ec7b352b61e93","signature":"0b02029cc6faaf310eaddc6a996db9c2bc36df3075ae2318bca2847f71444afb"},{"version":"172445546b246f00923ce61b907837020174c84335bfa24cddc78b6a5d28d0a3","signature":"b34528c74b3ff693ae3d27488992d045d0da79151d70e3240ea701f4a8910b5e"},{"version":"a207d5278346c5ef6ea5ce0b34dcb377bf4cccbd7153ab83953cee72c59ab34a","signature":"1dd308df0c17f9580459e35f573f15a40609c032465913c8d86a10883edcda1a"},{"version":"f299ec29ad652a02319d39bcb58adf0803a2bb2387a025aec1a0a16f50519176","signature":"9093242bf5a271587e65352246412d050ee6cca17b21bc0990a7fa7f0c5ae5d3"},{"version":"b53aedd97ce9ebdcaf94f60bb120b172149f79045edf37b22858ff10bb61e09e","signature":"ec13261703b24c5ffb56fe30e3d7b64fb29d7ea5fbf548dbb3440646b65e1316"},{"version":"e8731d253cc0a26c73597bb44242c9dce9e7b2c2e3ef9fee709784f301b90ee0","signature":"20e6bc4b20ee7a3a764dafd105809550487d4bec3cefe2eba50c030422d59de2"},{"version":"39a31ce781dfe7a75500c7e3b106b91257ba43c89970570bcf2f74b3b0852145","signature":"1b402b21cb6f47459c223905112ea4d3e37b3be572fd633ba9786b760bd4a49e"},{"version":"d32fa498a8aeb58c092a884aff775cf3bbc2c734cf62a6d3976dbbeef663b4df","signature":"49859dcca80078ff2d8839e53d9a1aa9a9f89ec3f5951123bf04b25bf5b241da"},{"version":"350fce1cf0e1ebb8beaa4205ef82d2d17c0a963bf17829198d7f2bab3508701b","signature":"c50b967b871d37bca57adc25e444664be2536774dcf45576625ddc4cb0711f38"},{"version":"e63c358caa122f2976f93a564b2d27772cd9a87cc8ad3334a4796f89aeb17e85","signature":"2d220697a8464f714de95c5dceacb017948d1ad9814160f51a0ce1a9a98b8e28"},{"version":"6e94f91e94cd57bc68b891cac44e2ca3044f0242761a183c07dc3c5eb523aebf","signature":"b8b8943e02f90219b68ec7d495ecd78c6f8532f3794df792cfa1743da376a71e"},{"version":"1251b05bdf0f9d8118139c3ed7bb0ea60466ee664c2d58e710be9b39032c6f6c","signature":"f2c6a00624f44434d49aef27eac8b74b150c4ad7ea531992cd5ec7b61cff698a"},{"version":"5bcca0c2e2f15929cfa8e0d91bad9f61ab85e7f256377ccc56d6b0f9f8552960","signature":"d77db17aa371d965761001b744dba64792f22b53c0a9ddd4d80d8c8b359c482b"},{"version":"641984c05f82a6e0b8dac973196b8ba146f1644b3706d318427096d844ac4f0d","signature":"eb5c97b219f68b8629c278d916c59c82b514b848ff10eb0db5d4196d69654147"},{"version":"064945c8a414c7a78b237a277403afd2b7ba4bb433d8cdc41fde3cddf09880f4","signature":"20bd6d8b518e6345256f0e7d38f412028f1c31d21376c07a4f41e3b65d0efdf1"},{"version":"5323f2f109370900f8d4f85c82ff47df76a7d63dbef322abf601217e4e677086","signature":"f59baba97905164ae2797a2a2869308ff3435aa1c66fd33034c0237abeababe1"},{"version":"1beb3dd4e06334a36673fbdf6df977bb28d28134285a21da8584cef98b0e7c46","signature":"1f03749fcec8cba452cffab3b318b2bde43ae572704f238a8d8f8ae059d3b86a"},{"version":"f13cc653015347688208b6a2817d6a024cae82cea1b2be422061ae1fb40e86a3","signature":"9d34eaf37fb26f7e3b5d52527e1cb097956ecec3deece2590b99f360ab4428a7"},{"version":"cc319ff8f06a331d4b359aa39f43dc18f8d4402f1f3c446b22a197389c4067a6","signature":"a6d8aa22b2e3abe3192321c687b18ff88b15d42a8c3165a2ceef83a58045e9dd"},{"version":"3c27fb3f66fa5c3798c663843ad30a16957d6cf39d4c4ee8c154dc03b777bd80","signature":"ad4ff92dbea4696533340e64c444a2c6d93c4cc8f12fe2c7017af7d0eb8d2dba"},{"version":"0c50f7da7e287df66e69485e4e5b56c4a0fb9f8730571541873377e7ed45a2c8","signature":"987de9b3dd9352f138928040bd0776e179cdf67c235a18bd54580bc4163a2999"},{"version":"ae5f21d33e9cece1850a7223c30edff9bd2b842b05492b4d1f5c74891683186a","signature":"1bc767c3ecaad8c2a205ad502ee7c4f20cffc11447fadbd4ab3f573481073582"},{"version":"bcbdfb8a08bb7ace1743deca0384743dc28179759d8d856455913401b3e3f2b1","signature":"cf836e95cd5f7ff40aece41f706a0b25b70f4d6ed2d4b82a65f01a41c6e19745"},{"version":"0bea811d597d5b10907dfb36a49c188fc9b1d7ba4785325447e1c78e0eed2d7a","signature":"b5ace5dea1bff2d9f60db27e5b08c348733034474a3abbc97d47cae81d167f9e"},{"version":"721652119aa07fa7df69fec15bb05e7818c69f4798e424b2a444889f482d5118","signature":"c4b089b69cbaadfddd82db7f25dbb73ba7125d4d401edf45e1aaa9262747a529"},{"version":"31f74c987ac1c8dd1bd2a84a270623b054c1fc4ce81a30eb788ce3d579d95e40","signature":"58a5ec371db12fd72d7b69a8c237fc87c5a131763b45d262a3d191c5d1356d6d"},{"version":"7efed9d38ce35662483150baaecb0eb98e400391ada29a436626063a3cd09be5","signature":"56e3f4727284e65c0f755411270bbf10da22e3fe5529baed216b93557b41276a"},{"version":"b3247c06acbd296275f69ae7aaa4572cfc9228e70de48b19ceb4584247fe05c8","signature":"a6a5dd455139bdffd774acbcf9371280adccee55dc5dd44c7eec8e5a5ac9325d"},{"version":"3ac434b15ae79961bd05e34522420d556094a115c238e97824bb72474b367fe9","signature":"02fca86b2afcbe184abefb4b157f0d795eb0062889ea788cf1bd7da67b76eacb"},{"version":"ca88391208aad15040edba757c64498bdd0ae0bcdd591e7c8407621501307108","signature":"6439ab6cac63fd2353c93b1795e267135c30379faaf24c595846710a20c9fa73"},{"version":"27f4208f541705c9e22a604a7ad66a4b03eb6132e1014489689122c33ccfebd7","signature":"bb26db505bc0e0cbfa1d5618a5ca643d928fbfc0b012df4c0e111c96748c6acb"},{"version":"2b3c21ba0eaf7f23e3d91a4d8871886eb43cfc5fe4af69508ea9109119417e2e","signature":"40ec6fbee6bcb9edbe5e5726919d09de143e600a85ed019f4f0647dd7f946032"},{"version":"8b571e8f3e760621f044a9dee0ea495b7cf5da82cb75828992ded169b5cefe9e","signature":"d0948979f76da0bcd6723ad5fe1ceac5c6e7ab4dac9025b278f4ca3119f835d4"},{"version":"10f72058876ff0b07c27916ebff1c9a5e350117fa4632635b2c18e4e287ecde8","signature":"751ba76f055fbbce30097cba1ce5f5d3e6453224fef819a7eaf7f0383ec9310b"},{"version":"3caef034a84be3e33a76222b9eb965906198d8aa25cd874ff933780f55869f18","signature":"3564adb90073a7f8046f6fb26f3a00d86a7681f0bd8433d7fdca8ee81f3a2feb"},{"version":"afa6b33bd7d4bb9694536653237820f279876c907e0fa0436e14f3b5b09e4310","signature":"14ad6a418f276ae00176848d46d92337e4bda91e388663afbe5cadc4e74db63d"},{"version":"2cb5bcfcddafa73663cc7a0b9d07913ff00864af96cdf56ce809d55f80a1753b","signature":"f543efc561c3e8efe2d9061153ffc4a5881bbd1727d13e2eb8f3afd8f61a023c"},{"version":"8605ab3907c8332a03b0fb2bb8ecb8259321c15adf6ec70b4032b85d771cf2f3","signature":"06ae795b9ca99a2466c46639c2ab809198e6c67d400165f05424a012b1bb817f"},{"version":"0b596ac641129a560bec8f495f52adda3c82d92b1a115434a46e9c48080c9157","signature":"19485a0daffc617967e78d145ebf48193c8b2e01162a202afb02bc4cde9547b3"},{"version":"4a96c42ed30bf77b8c127453fda697a212495a055f6dcafda400e42eb233d029","signature":"c4e5d6b5f65bfd77c192b36ab608481de02288d42739f978b0c01a812dc94321"},{"version":"d2f9a9603312036b4c99561d7ad5049bc3e471140cdda96dc8d18f053c81a06b","signature":"48e6c8e53a8e681596e770eb3a65c70c14302492c4cbb406c67581059091a194"},{"version":"63cbd997e6553c877bbbfd11bdf92e99ce911bda174e6106d6597dc931f126bf","signature":"f052f97a9028807638935bdfd4013cc7e5702556e87ebb14d0a5883d33520daa"},{"version":"b5688bd390446b7c8e669650489199cc24b497853a44c358b45e23ef6a4ab84f","signature":"4211d664ea3e6cdf079df95c3d542279138d328db6a19c6f0916b201ab920d89"},{"version":"000d2e7631f4814dca372a4cb7e3fda4a0197d3a22723e3820f2ae3f74821e08","signature":"6594f6c4fb747b7405701aa752898fded1a76f395aa9c77ed615c28d848c4b44"},{"version":"d37220c6451fc2a23f0f15014dcd63a9a75816d5c9b39d8aaf4a69e09429a5b5","signature":"fdcbd6c4e023a4fbc39b4f1161838845e2d7491dd46a26fd2706675a13fa4e94"},{"version":"1d01e6bb5f169a300b055a8746fa89b35c851d3d09a593bbe42dcf69ec526e17","signature":"b38b35552f23ce23723086036788c7121e840421b6dbd402bf865cfd5cbc790c"},{"version":"dcb53ab6d5213ce22d7073c947756ed346f559ec36284f6c33069957cee62c41","signature":"2313b8fb3f40f12cd9c0f285433be8a4e832ca14383d5eb72ceafb7ad4eca47c"},{"version":"093bafe4dec3ab6fcf9e9a5753856775bb1e0fc6d032a4913a8d847828884271","signature":"634bd3ee5a6e4ba1c1671ed182dc9969b7a429116ba5a3cc8bc899a8aae2e8f4"},{"version":"bb23c7b441db38d447145cda42a252dd88d0ac4113dc27e43a3a7db35524bda9","signature":"c4e6581c0c2bf8d017173140969f491108dcd5784f12ddf140da8b0daf20ac83"},{"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":"02b6d443cd64d2a7e8dba0f1d59944e55e91a16b21a7d7d4fb5a81724c832dc4","signature":"e66fec1c73ea068e8541b003c79af072b1b18910017d07c47ad151a438c709c1"},{"version":"a1cc7006ac0ac2dd2748f5b4a07092a330d175b16adbcd49c0eed365e9b4575f","signature":"6521410466cd5930d8f9db814cebdf1094def90f68293a3b330296a35ff2c1f6"},{"version":"d3984cd8c4d6cdf73a81ea0891dae87ae6a01c1895fd68df0b6d740006acb9d6","signature":"0ac76f72a94a13f3081c41c43b58679492c219ccada653f40801a89fcd5e9d04"},{"version":"64a1df79fbba93c3a1642f66057608a3d7e2fd24ba015b260f7014ddde542908","signature":"feb053fdd4dce7ad7c1ba7791bb6f65fb66d38bb9c1f0543012dab8f663e88b4"},{"version":"9b47bc8dd5a4d6b7f03a22a9ff4f46883813ae93554718511b93888e39ee58d2","signature":"32937206cdaee2551a23ce603292dd67e9606a27fc71a984eab852fbea3b9ad2"},{"version":"98efe4bddac330a746d7f43a39faa36ca747935ea6a8322a8afaca68c5e70f35","signature":"4dd7e1bfc2c138b564a1ff5bddcae96f4cefd39724115166bdbe071cd00b3cb6"},{"version":"c4528c70ebf1acf226f198422561ad4348ac9e35a8990b1fd15e17ad9268d60b","signature":"71f762a4ed63ccdd8a60c9930b445ab8e81bdf4b9919c5b94761511cd866f447"},{"version":"d6d3f9395cfd6f2ed3c9eaf572f882a03a1fecfc1e13acc4519df67833342bd5","signature":"5c597452991cbc579454bf8e1c5f549816d79f80ddd3514b52fbb26cc1cdeced"},{"version":"7c6ce84284a608e8ca9b7636cb5da89481d8c945d03ba511da6a2fd56bcdf78c","signature":"c169279b909f77b0c7b26ce990b20c6719869fd76be6f95f4eadf4f3befda363"},{"version":"83e1bfa7986a958fd6e069fc5df9dec6aa1e63f3dd81ddae889c19edf3a6c450","signature":"6efc188b6e1596f593cdcb356be53ede31fa87f972e5d2adc9377fa511e2685e"},{"version":"c463facc7d18f4c36823714a285903d1123cc38a9dc91a5d099c64145432f75c","signature":"2150afbdeb24336371088cf931c6081d224326f5c57580ee0b36925d1569ad5c"},{"version":"dc916450a7fe9f02ea4f2b015b836fb7d3e6291e59c93b47f711623ec4c62fe4","signature":"1609615e284b1a86bbaebd997d03c23cbe145012ba3b3d4376aa8a43a701e4e3"},{"version":"02a313eaacd1d0d97e7e1605737ac03e732648ba6d92fbf2c24716c1349c30bb","signature":"e42b8c3731c42dd2bdacdcbd0b7639df957c3f9b5fdc1edabac4a5e63772a4b2"},{"version":"af2d7b90a50168850a399d83b4e9afdc302a1025148194e2e94e1a31060b93c6","signature":"9836be02a489f0fb61392d0e3fe4127c72f079fcde9e9fed4c282bb070832fb6"},{"version":"c029e55dd039a7085f8a0f08edbfd0d563a07c48a8d319c8fa56320dac00a465","signature":"dc37598a12fb0f6f547b4a8a14da785916df9f822edbdb9657ecc589bfa33f7d"},{"version":"086d9066a9edc176d4baeb61d0075de9353ee4695c94ecfae51f293be8fefab9","signature":"90c1986dad477ad10a8330aeb2b86a0695d484a12a6f3d6507147e07791b1476"},{"version":"94fe52c96742b25429d30bc54d7ab2a2324f37025cbea99f819a77ec87bb1772","signature":"d570651c0a2c5e78e74c52a792b94ccc2cc9b2b927bfb3a5419acc0150942695"},{"version":"1897adbce3874a07180bb47daf0e8ebedd6d1793819143c63cbce290ca2ec80e","signature":"aaf435d6dc58d0a18a54421b3a622efedf9a7a996d8f75a06354219d91707650"},{"version":"68fe3c692ad2824bc811643cd5e239d872cba48006000dfe185146ad106066b3","signature":"0342e61cdf2eadde061c53e1c6fc7907ad69390beddb2aa50e656dc2a45a632b"},{"version":"b15c4ee8a756cf303d0efc482e861876a2e90b194c7cc393a8acbe7080fb186f","signature":"4a1ee69e5477f0d725306d7c9281d127f43ec0b40a23689a1a27b9430f030177"},{"version":"f6a08a8d8fa7acf45c3ba85e864da549befc88abeca247da5b6732a82685bf45","signature":"afc9b47eb28f4775396aacf528a98d207e4714ed7600c47bd33d01d4d6d3852b"},{"version":"bb0365d741d36b7f82832dbcf1b2e0025b6638516fda9ba3061d7d41f7f073c2","signature":"1e5e485956159fcc1eee2c73dcba5186c0a66f780ad21ec3760cd35a723930ca"},{"version":"431206d65e5858f0534c8be80eb4081627924a9d1cdd853982a3a4d811999681","signature":"e4b7681fdfe65ce81bcf251c1bcdd71b93740fde81479a2e3531a23fd347d951"},{"version":"7b5bae142db908800ba59fb353d7274e1f1ef0eb46074f1675af3ee77c4d789d","signature":"a4b716d6fb7e2bc0cde7e9e02904a13247381b91faaf9092fd6c60e2b96c2d48"},{"version":"058aa6a9383796a202fdc9eb0c5eac4cce8a19ba60bd8b551091beee197fe25d","signature":"b067ed3257c3d808d867d834eb5c1688ce5984ce6377ce6213f7fbea90bd6b58"},{"version":"c837dc2de1fef03dadb1fbe3ae46565ab80cbeea60c057acd5bf1e1d8df1b509","signature":"b4ffb98ee6415d12844fda388c4ca6ef430f4e4217d9ea7a139c252576a464cc"},{"version":"e6c4a6a15416b28fac47309cf33fe8040115a2849ccd012bd098efa4ec4ce9ef","signature":"f3afc6db2c2172dfa631f271d2bc28e8dfddcb2807285ebd2ff547fb786e49bb"},{"version":"283ec3ae2b171cd28e6778d2bdba3f8b055e818dac238b8a2d27403c551974d2","signature":"b8eb376386840de0303ba01f15f27e04407fd37199eccff44e6f51ff9410bba3"},{"version":"63ff2eb7041e18879c303484288ffe060db245e20fb92be664b39fb9f56c4b35","signature":"f350f485c8889421846f7acbdfe9ade72bdb827ef1905f5cc94576b802ed1a57"},{"version":"e0340f2e710b3caf03d7435335ed6441df684f5f416b3008077280a53bc0d195","signature":"043d0bf84c084c637ced77530bd97faa0aa3a8e01e2915aa8cc2129f79d9cedb"},{"version":"d4e9258acb020b007d2a76f0d9870d5d10f0a2d6bc8d24bcaa0f65931892b736","signature":"345eb0a009f9b07377ff2e8bcbd390da1648e549914b3bd027bd6b4987f92481"},{"version":"ea17df0c800a3917b052e4711913019aa15c567d1112d0f12ae22655f1c8e75b","signature":"c333598ce529f5350d9ef0c9a1bcd8c777b86845fa327aa24eef575669cacc91"},{"version":"f5c622b1d6ee97b57e7ea203c6a7a373f7a17eb412b16246b956bbb548fbf1d4","signature":"f8e7efabfc4eda9c2900703c25975deb5ac7d331e2ede7cfc50dd2da9011d789"},{"version":"c32feab5e5456978529c9eb1c2d8b56a04d9074f2f43e757edf680e132d37d00","signature":"ea673b0a7771824aa72008f0f86c71b712e5355684f05f24f8c387accd03f14b"},{"version":"133f92295e085a33a1827544debdab026bfbddc984a1a6b0865c924c38df13a1","signature":"fdd9cbb46caa8f1ba8359945e433ad1f2b954b1496a933f3eb4d29c8ae3deac9"},{"version":"e111d7709868c64a5ec40c93a0831eff084f5f3747bb50300878504738e28c19","signature":"8ac220f8baeacc2d3ee8abf3398308e10ea42de2e146be47ccb866ceb017a397"},{"version":"3dfa41a4c00d6e79b3618e1dc111188c9288bcbe16547bea8305e6af449329f4","signature":"e367993516c9f05fa87238bc5b53220f06b7f84b72629958930e2a7a37436c24"},{"version":"4d6792c606bdd2a9b2cddc4d24923ccc18f7f438cafa31e0e21285e97c58421f","signature":"2d8f81759b547e64f1b0e290fd4b0ac7316dc9c3e96f5ca93db1a1c790ec6038"},{"version":"b8b666a3d41df3b7cf4066283f67e72bc5e8e04ff4414695eb972ba7561ce133","signature":"171b8eafff7d0d126a6df4cb220dfdf7ae67c7c6687fbdc02bf4b791bca40091"},{"version":"bf66b3a8b8a28e911776d8ead5e2b6553ee37f8fa1bf0ed209aeaac310786fd9","signature":"6302a282d76ef8475759f62e01049001af013cf8f9cbcd07dc9fda62eb87c9bc"},{"version":"18ca2e6d4cb671ed4530429e6b4886aa0792c79c4e5e74078dd290668b540599","signature":"c606b46f5784bed24dcbe2bec2d9ee535c29050ba160ea11fd41b6a173bbf25c"},{"version":"93c88804801702c2ebf4d7e282ff71d90f118253ee206e7f0ba03305cc581546","signature":"0a7f51c3fb4b7c9a30745a92c15a4cb4eb88aa3ea69dec8f6286491fdfb99dab"},{"version":"cc2cc8e0ceffbbb09594103d85e205eb53b848b7ecb27d91d7b5cde80e792236","signature":"7f20f857d4b5aa3fa620d0708434711ff7092dd304c9c38f8854ab9bd334edf5"},{"version":"81aa2ba3522bc10222837dca4556efb07a6628de274c94545a536c1955684ef4","signature":"daf649274b917c1d7d6b8e8488d04d7e47f3bbbb09842c2a9899b4ec507fb243"},{"version":"fa1702a90530bc09b078bbfc9e98010c20333706f9d225c18558a146ac9e2219","signature":"c75b37dd144d43a77944ee5a7b8195d397ae78b74065052bf3a1bc721b1f77b4"},{"version":"d5ebf3405d09e5eb9e3316e8b6a7329bba4fa306433222f109b9af077ec77525","signature":"71108da668d27a617e4f2ef6aad932227d526487149856bcb3705f0a2aa9fe9a"},{"version":"c7aa0820877b8341f78e794fde60d464746eec82b0e2624982000ebb19fb8f8c","signature":"488c8eb8a1054444f74a12eb49f8e21ae583aa2ba59ac9cfe4ffc71754c3b1f7"},{"version":"9c2b60f6bc9aa74d213f7dc5bc0b666e7c18c3572adef9d884521c36412bb814","signature":"ca4a72006161d37a422dde05feffb9667a0e38b99737b94e0b564b5a7409ec21"},{"version":"f38eb0a2421beba20ee66d42353732cf2ce6f343c1b1c322252842e0f22b8308","signature":"cd575032c427cf4eba79247a61418781b801f14952fc1bf8a48ed2747def2bcb"},{"version":"f7409ccc7875d9cebabc5e27d9df8f3aca19ba959f30fa1b486418ae9c3058e9","signature":"de963461fc2f6d1fd065c283aae92de10d72a4cca1f7fc0afef6301e741fd381"},{"version":"f51d0b8cfa092a592caf543d772238f191037278e2004d58e7354447d830863f","signature":"986bf1e9bc3d1b0b157927aafcfbf9e94478b28eda319c209ed8e9e613e14827"},{"version":"3b9d0ef4847a6525e297172e340c0dc383c8ab6c58a27aee0a27b2df991ecef1","signature":"57069ea736148610272f87e767f23439015d900f230c3060afa193d6b9029cf2"},{"version":"b6ce8cc18189aa155b9d4386c03e3547f48121a7e3f37b66ed9ad43190b20dd3","signature":"12dd7bcb0994252cc8b7a0155db5662ea1c3437584c67f010758f962b023797c"},{"version":"2c4beeb10c123c02ff09c390cb92953e811cb0e846e2d040c65a8a0e73746894","signature":"5431108d0a4a15cc5f6d78abd5a358d13bcabb849877e09f3efc265eba21e6e2"},{"version":"40f8a5ff101ec9d2a6a08af84db2d4865c35e2deb3da94075a482d94612ca24e","signature":"57e73f014bbd5a960cd0a3b39a240cddbf1842f7f06a09757be73de97a234a79"},{"version":"812fbe241e51f1fb745bfdb0cf447cff8a9802beeac16df1980f14499990900f","signature":"a4c0f47a1176dc8ca692834c31a2f1c95994955eb191e76cbf3e58dbd16ec08c"},{"version":"5559d4fcad759cc07a71aca5a792755409db3b683788828abad2a84da3dcd7fc","signature":"c367bae6e0535dda7431e73df32e233511c1e9b1181082d551efc822fcbaae83"},{"version":"46ea0bba2f2e36762dae6bd527f342a19fe9f46c653dc4e8061d9c8530ba8dca","signature":"19467dd75b0a6bae42afc2ac103445b6ddc4a3e259599ac20b2ec70e75fff499"},{"version":"5112478f7f7dcb622157981c8ac9a0fb3cad40c5eb87a19ff2e37674c75c0fd5","signature":"8ce6788984fbc5caf642946b8dc8a405629def762f166473ae6389aab4822034"},{"version":"3cdbef5d6bb0c6201d2edacc2f930322c36b9798b90131787398324b67bd2130","signature":"b84e31232011f6ac9ded7e727871f7c1ece606d8179950f791bcc03ac3c03b6e"},{"version":"d6c2a85159f32ddee646895592b5c53e04b18cfb5346de79c2d003b814b601e4","signature":"2bc381b2105d5a05c2724fa4ae393e83f0adefda1e390db743e55a0cb949c099"},{"version":"9fda6fefc6a936e326113a3d3dcb56da7dc7c2a7a064bf451429b51e7b645d8f","signature":"f668ac39f924b2946f0e323d23da14308c0d996f579dce2b5fe5c9f2085c9ad2"},{"version":"182221370b4c51b9fdd08f71c259596d747b565fcebeed2875832d1f2f556c8a","signature":"00ec18666782d50d3be062bceb46231a3e2c4abae3128f6638529e9fdabefab0"},{"version":"e1d7527f3d057bd92e487081450d9037a1dc9dd5e2f8e84e1fb2f6c09903db4c","signature":"0b482267029d52a5a2ed300385e2fa5accbe0f69d22bcc5c5f541536e169ad5e"},{"version":"827a3da73904af54ac6cad259cbde0bf0b811d253a3fc370664cf80cb65ad6ed","signature":"02778fe052be781d64d090064f311da1b30eda7863ab768850a522f3c83dabd7"},{"version":"41d344efc8e2dcfc00c0cd0d7bc8f5dabcc6bb0062766fd17aaf85deb4d60ecf","signature":"83df5dd9f98fa4184cd1227ae312c09558f5a00b35243e263069a3a545e7f6b9"},{"version":"d6452b09863385bd57e48e1fb836f95c3a6f36ebe690e342d834fd2868d6ba74","signature":"b9288778951e14a9a541d06ead6a2b1abf3b7541a1680af62e4769e632ff1263"},{"version":"fc002c1c913427c1e1f45dba404ef61759bb7ee2bae7bb396000e3cc521009fc","signature":"486e05d05d1c3a7899f66c9c98d8ae6b2c3a5e391bd74086d2d795f3c091a159"},{"version":"f697b44f7c3fb5f78ccafcf98d0b863e87e997ab71eda6ea9fb8290313fea562","signature":"95e6dbb8526dbc167bdbe072c5c78a5f281bddc36f430a34f36192566c2ff2fa"},{"version":"d0fe15b48303f79346e34ea1b4d207022219399afef4af6451928eec88606e3c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"447809300abe6967b66fe4226b18bcd8513258b0139a8fd1ac516767bc510372","signature":"6d31cb09b5e87e0588c937c73aff7673a15865e355903fe16ac3bdcb3d2894d6"},{"version":"309ebd217636d68cf8784cbc3272c16fb94fb8e969e18b6fe88c35200340aef1","impliedFormat":1},{"version":"91cf9887208be8641244827c18e620166edf7e1c53114930b54eaeaab588a5be","impliedFormat":1},{"version":"ef9b6279acc69002a779d0172916ef22e8be5de2d2469ff2f4bb019a21e89de2","impliedFormat":1},{"version":"71623b889c23a332292c85f9bf41469c3f2efa47f81f12c73e14edbcffa270d3","affectsGlobalScope":true,"impliedFormat":1},{"version":"88863d76039cc550f8b7688a213dd051ae80d94a883eb99389d6bc4ce21c8688","impliedFormat":1},{"version":"e9ce511dae7201b833936d13618dff01815a9db2e6c2cc28646e21520c452d6c","impliedFormat":1},{"version":"243649afb10d950e7e83ee4d53bd2fbd615bb579a74cf6c1ce10e64402cdf9bb","impliedFormat":1},{"version":"35575179030368798cbcd50da928a275234445c9a0df32d4a2c694b2b3d20439","impliedFormat":1},{"version":"c939cb12cb000b4ec9c3eca3fe7dee1fe373ccb801237631d9252bad10206d61","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"03268b4d02371bdf514f513797ed3c9eb0840b0724ff6778bda0ef74c35273be","impliedFormat":1},{"version":"3511847babb822e10715a18348d1cbb0dae73c4e4c0a1bcf7cbc12771b310d45","impliedFormat":1},{"version":"80e653fbbec818eecfe95d182dc65a1d107b343d970159a71922ac4491caa0af","impliedFormat":1},{"version":"53f00dc83ccceb8fad22eb3aade64e4bcdb082115f230c8ba3d40f79c835c30e","impliedFormat":1},{"version":"35475931e8b55c4d33bfe3abc79f5673924a0bd4224c7c6108a4e08f3521643c","impliedFormat":1},{"version":"9078205849121a5d37a642949d687565498da922508eacb0e5a0c3de427f0ae5","impliedFormat":1},{"version":"e8f8f095f137e96dc64b56e59556c02f3c31db4b354801d6ae3b90dceae60240","impliedFormat":1},{"version":"451abef2a26cebb6f54236e68de3c33691e3b47b548fd4c8fa05fd84ab2238ff","impliedFormat":1},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"41f185713d78f7af0253a339927dc04b485f46210d6bc0691cf908e3e8ded2a1","impliedFormat":1},{"version":"23ee410c645f68bd99717527de1586e3eb826f166d654b74250ad92b27311fde","impliedFormat":1},{"version":"ffc3e1064146c1cafda1b0686ae9679ba1fb706b2f415e057be01614bf918dba","impliedFormat":1},{"version":"995869b1ddf66bbcfdb417f7446f610198dcce3280a0ae5c8b332ed985c01855","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"dca963a986285211cfa75b9bb57914538de29585d34217d03b538e6473ac4c44","impliedFormat":1},{"version":"d8bc0c5487582c6d887c32c92d8b4ffb23310146fcb1d82adf4b15c77f57c4ac","impliedFormat":1},{"version":"8cb31102790372bebfd78dd56d6752913b0f3e2cefbeb08375acd9f5ba737155","impliedFormat":1},{"version":"bb9b5a18147a0f927e0fffe91515a39610e2477b0d8a0d0b391c283013e0bfac","signature":"d373335450e0c74b3455541e03c0ff8fef26b51201c49ef145a0afb217a9f026"},{"version":"4bc5159b0bb1e303f1b662d485b7f9dcfaf785a29f8cd101ea85817fdb3a518e","signature":"70cdd1bdaa655ea305231ef8f3d9f830459ae85cad5a2395b70b4caa2d81abe0"},{"version":"25bb698c825c728521550bae3d4d8777520fea078d96529db79d3901278e084f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b71e71966963661cbc81a93e3017c50f97fe724452ffc49059ed4cf0540e464","signature":"eb4779cf04d947168653273aa00f2a6a72a61fe039f17400e6d76179a7d62fd1"},{"version":"6fbedb59be020e7d349de8a1ffe8aaa52d16c78f9aea437249f14782b290aee9","signature":"eba9ab6bd63d7d7bc2a05d255e9d56cb7321477c3ec92364db4cdfb12873e8b7"},{"version":"1318f5ed0496352fb48c4c03ed01567db651e1794260df1b2da1e146a1aefab7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fcbc73a398e35777c583049d7a6315455a1c340d06a7ba06fd65a08a998576a3","signature":"bb33db3843913e4d9bba12a3c10ed9c8bb77a67266905cfd9e0afeb093e715fd"},{"version":"8f9f32fa2ac067466268c5f25be56dec212c0bdb9b62a123fa588e72bd6b0019","signature":"a01968c471a290a4b9657a07cf814af299366e7c83a56d8ae6ea425e9b83c81e"},{"version":"24defda36322571a1cc5b324b17b3c1e721804a2add0166477d24efaa523ffc1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70ac7fbe8555de02f7cb0fe42f479173ddb89a737908c560014d733348422046","signature":"d9b4f0fd652a60e8727bf295164c2d0a652cb6d79ac90e8b13c48d4230a47039"},{"version":"656ebe6a1e35fb1e45ace5b3d8975099fa82a7a42542c09ee1e1e975b4951722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c4f3d9c6228f744351b3f3d6ac2593edba9e7cf0a965cc6ccb1805595f44f275","signature":"96dacaf48c43f86fe63578c47b008b14f31ea3b26ba6604869be23386548a0af"},{"version":"37d5de471b9dc564c40740998405448999d504e4199bccb259fc7b0734b1ed88","signature":"cead6dd08c0e7a7b5fe6b1665ac0c10e08d5bb8ed6e044a5716acdf61bbc658c"},{"version":"256ac94c8da7010cbaacfb3e0f55cab2ce49beb7f21309659ab1e5c44b66cba3","signature":"4932a57ec8dc885c99967df2c08c4be4dcde303de1727465afc901bb526c9dce"},{"version":"16774047f3b38fcf2d977046b054d22bb00258771f607fa6e4a06e76cc114cfa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"45d8c8b72f837a46ca63ef01ea3f4244112587c0abac142367982f443e31ea7d","signature":"eb8463d6df0ca2c38399823f1e38ff66180aaefb12e5b403155c2abe1eda8b5a"},{"version":"964e363030719b2e66f7eb64663b22f039bc64985dd1e75eae362e378608ad32","signature":"52b37759b4c21b0266e113f72e72db24ca11859fca9beaae88ac286fa508c5eb"},{"version":"2aca0bc14bc6a0e2ce70f410e002eb4aec77e7622afd0e40200a2d6c36542db3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d21bdd834776d159085df8f067883d3437dacf6eab3d356f7c3bb2bce9a9c98d","signature":"114811397c9ad0f10abb90a439425e93671eb3698dc832ceeb9147bfc4848dbb"},{"version":"912bade28ea48bda95091b26d3082abbaa1f48637b4b4ac3e289ded44a153a1a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c29a2bfef4c6aaed66d0e46bf5601f98e4ea8fba4e230773bcf8a57d76d7ddcb","signature":"724894208b6a6516915c69b386dc88e8ae38db466a047c122baa0acb05b2466e"},{"version":"338fbeac4bc8d461eed91e454d837c2cae2c403822f1c3f8a0a68f1d1e2488b8","signature":"ded5f2d0a193751dcb1172fb8ae1678a74ec41f30a52c8992268992fc21b9f6a"},{"version":"dd902a78d8188c4f89377024097f0ba90aed6822e4f13c742e498eb1fe9fbe8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2ade88925e99f1feff0c33e971814e734e05f6f9e32c2fb7c6260247635417ac","signature":"06ca53e7c778e43262f44194db43a44dae84e02e9d9ae674f74a4039f043a39a"},{"version":"a5110f54ba5e9c7c7fdc029cd20e65f35a9ddab6830394949279d03f4baaa112","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e08859a0433654484c23ea7d2447e7da43768e228643cde336290e80359be015","impliedFormat":99},{"version":"427d5d08714a73f909d965ace2642ea9819e49245620483d47acb73b4eb922cf","impliedFormat":99},{"version":"242e53307a5705e9235ca1be47168a4c3155ea674e80f5a13f94d7938c23bb5d","impliedFormat":99},{"version":"44a6c89655f418dd8250d22276530cfa20826b2fd8d51e90fa4d1ada108706f7","signature":"b8ceda97cfbcc009561ba63ca8e39df0dcab8ad77f6bb03d001f12d0f5174f03"},{"version":"8ffa57b994af8cee7411cd7bfec0409118909a2a897b417d5ba378025b9b8eb3","signature":"66d21fb03c05d9e19a9e6328311f0e929ca450163fe3ad5a1f19b3e0563710df"},{"version":"a2a59b41271e830cdf2ce76662b32e2f9d3c2a3c35e9f9fc359e9bf994ca8104","signature":"9b835bb9c177bb27a9d814290adebe192bcd22f633810aa7d4f0a4ec1d6fda4d"},{"version":"c4e23c61fd109ee83f2f5c355698161dd4edccf0882115c1c1450e55d02fba1c","signature":"cfb868d5da01515efc27424a0c579a94c56621768b4e7a4d2275203046da4685"},{"version":"338381ea6d7ef717f6f5c22294b6c1d820d06133f4f5fbc86cce64fc4fb81f5d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"93fae08c020f721198112b7bd06a7a405f3d5ed77c756e20bcaa264d585b906e","signature":"c9d3eacf4c5ea3ad593e74b88cb8b3dda3258df41d236c336936146d3847fdd7"},{"version":"849d186951b6fe08777eb595e7b5423a933404a59b255b15b3ef91eaa9e03e2b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"398322573c9e0ba2826eaea162b97f4987dd00caaa4dc29df93d7dccaff40c2f","signature":"dc1df284b2ecb2adb8124f0411490cbb6adc27d6b3f783cb98e4de022894c67c"},{"version":"0e614492dab5ee5f4418895293386b642203c8f1a3a9d14a8eca94a906c91c04","signature":"1ce004dab6fc4c13fe2a946bf541afc29f77e4b6d197bd7e078c216bf331c288"},{"version":"f22a38020548019be436f7d33fae243aecf68c9e068dd7fb5d88ec31fcfce2c5","signature":"990a86a4c51ffd7c3c146bc5b5e4f2a6eb31f8a08186da00d699ec62be38d14c"},{"version":"07a57ea6e42f784f7664053d917baf68d010a79f0df1fdb8fba87a6af92ddd7b","signature":"e57424bb8ca9fcb02c7b73c295bff56d7889e92610490ef4dbcf83dcf5006809"},{"version":"9462d342a282781c02c97f4c036158faa37e7fc0ab0a14e1df622ccd9c757f5a","signature":"d55fa4fbc6da7d276cc39c6f62402a7a65cbb0924015aeb475013a0d50eb9d5f"},{"version":"50418661dd171c08d9cae6e3b5ff41663e70267a80e0716eef30fdaa381b3607","signature":"438a206e90ab4a99894d015b8659ad993a2e15df506e3e52ef38b5892098917d"},{"version":"2c0d69ddfa8c836d8052a3a7589b0d6c6e8132d50949d6440d5672e0f0948ea7","signature":"8858e8d480c6f9ce98c8f48ea265e292dbe3fc51a9f9c3b0d125ffad18d89e29"},{"version":"ba0789108c9bf8c7ec38cc853e188f9d131c7850a44fba82868eebd7230cbe2e","signature":"e6cceaf655d91958114f0707a4d6c800cfd0d72ea8673f4f0face6b049c90ec3"},{"version":"9f2145479716604449381a636127459790f9e428a5c526cd0795223bb66dd9b3","signature":"d86c8b7a6c6edbeb6a14c73aad61eec9d13cec0040264ef13c78a8c2f7ecbf43"},{"version":"fa32219cc14042734452368d122b82bae2849be88d8941c5b363e3c47c9b651a","signature":"7c56fc0ecedef369430a6cc78f797e1f72ac6aa30cf3861bea222fc9efe93d49"},{"version":"f52646c7394ab792adfca993338d590f7d9030ae3269526d5dede9b131247717","signature":"d1d2efd128275df07279bf887018192c1b38c0cc2aea96243de78a8e92bc30ad"},{"version":"b1ffda5abe3874eaf7ee7e57cefd4c4ed1e85e00932b5da6847cbe0e22c7eedc","signature":"29afa7f4d2f64a222d590227109f01361ddb9c6588355096f6aaa036b9d67d05"},{"version":"0cdab5351929ad0e2d94ecea2b8d60d11fcdb153ff2355ab9c5109e84a697ddb","signature":"91537516c066b5bda3446b1dbd01a6b3ff342925cde014d5acb7b6f8b99ed12c"},{"version":"0172224d148517f7cf90527aa73f03cb436b3fe7f06ac70c039d6886b8f9dfdf","signature":"d01168279f1f9a417a578d3768c58eb5170a5b3445e7b2dedf880dbf0d1fe873"},{"version":"a6d5aa9d1ace7ec2a992729d02341737fc267a9d2d7f1467313bd170e4b26b15","signature":"a65ecfa05330aaeae23d23b899f0bd37c34e42fa5083d180b4a0bff3dc3ae25e"},{"version":"831b967c1911010eb3adbfe96d76340dce858803d80310236352a7b52de799c2","signature":"76d26c617c0a9f48d4e21938e684ae22166d2d3604d00cafab5212b0e15b57fc"},{"version":"d09b5dbb314f199a0d7ae3c2c2a9c7258b611f3f28d1cc83c2685d3c738aec3e","signature":"068d0597a17af822c2ec3af9b1c2a9b9a26c0a4387eb66f655a0f1d26e36ba84"},{"version":"35164c8ef06e7c366f6f45f993da6e0df0f7c2cc93e78198c199bec111da8fa4","signature":"942546eaf5ae2d0c5948c6d25a748fba25b6f4d760911ed595b40f043fbae102"},{"version":"ea484c456f3f9236d0b324d2c6563f6e77571c9414768590248a016b2e248a3a","signature":"65bb767048368601ad35597c54f6b112e3147dc84fc338199931af5d57b8fe95"},{"version":"1ac040d4f47be4c1b44b4a521c7bc1264c97371b3c51bc2170326827fc8a5406","signature":"dd24f7d41609a7eb1c990ec2f7d7cdd63a419e355e6294050a67c029bdad0d78"},{"version":"f3d7aece0ac20c6911c50aa54b50c1ae6768a8793a72412d346aab2b66b4a7f7","signature":"ab04dac1f806941027328926be16232e21366ff829c3be737572abc646b0ff3e"},{"version":"6a282a4b745d9ac9d04d759b34b9e51124a950ba33d83a1408f76742cab5d8a7","signature":"519c584866e4d804355422ce52d8088fda0124969a9668779f10b0d50e114153"},{"version":"3cef134032da5e1bfabba59a03a58d91ed59f302235034279bb25a5a5b65ca62","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c05d0fcee91437571513c404e62396ee798ff37a2d8bef2104accdc79deb9c0","impliedFormat":1},{"version":"373cf226ee7ddf9535231d4ea2c24d47e4262372e1c075aee7b48e0d2d38e759","signature":"3a700951382c62ca71c0a4fb951071e1a2692a3ddfc899ba0145c275ff12a006"},{"version":"29a8061c2db56515c67ce514bcc6f91c853407acf4539f5d08aa723526270a1e","signature":"7cf5ac50b3def9f8df750c1e7ea9a102216484b4bba94f9e0bf68458bc77eacd"},{"version":"2d4b53789aab997f99121021686c05f5f54aae58fbb0525243fdd322c80d612d","signature":"5c975df906b720e560dc80cf99f12cb2763329a0d5c42ffe3039256137dc3a70"},{"version":"e43472b89b27f89f28fcb57260e48230b95baff6fa6c489c5710115cfa6c9506","signature":"2f9e549adb20bf7d44ab18efcdb5e7dab6bdf423d310f3df05e5ac78e3828990"},{"version":"91d7a64938101c27f0f5493074dd0ebc4f82ed6d58c42c8d148235de3f8978ed","signature":"b4698bce6f7a4a17593cff994a72d565662855439386bcfabf5f0335ea8d4be1"},{"version":"d563b38c81c713a23b730e0e385c44442992d3b1dfad2424fd9c635e3eacf593","signature":"97ebafc9d89ce29d62958a732cda28a5cd408a1257cfcac0d253584fcc850e6d"},{"version":"6d4b59d8a599531b5bd5cef904c5f8832f062b79eac5298805b9aade268d66b8","signature":"97ea7a733867ec926cad347ae90178d3c3fb96a4fc076d2d2d41201e9a1bea75"},{"version":"fee8eb73b4397c9d3fc50904fb4d93947f32879251345c687761a5ac20a76314","signature":"4e69e7b65fb23298ec08b6e0cc86692fb6602df5473948f46baf219a07967697"},{"version":"0d922edfe50c6ffb2c16d49dcdd160dc468b0f93f69fcb021d6464db95664a21","signature":"aaa2dfcda87fdc4c24fc251d7d04070f379d25c631d2b130c846becc582e1b77"},{"version":"df3a3dc2616be7db489fe6a853faa1e52a83dfde06d2f3214994ee7ef81f18e4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"3d3208d0f061e4836dd5f144425781c172987c430f7eaee483fadaa3c5780f9f","impliedFormat":1},{"version":"34a8a5b4c21e7a6d07d3b6bce72371da300ec1aed58961067e13f1f4dc849712","impliedFormat":1},{"version":"6a534c594838029f9096b88db91c054e612ff951a57ed9d9efd92f19643a2753","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6223f56cb79eac77e1211e76830da993ddcd9baea0dfe2d10a61a131d39f427a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20d8184cc9bf496dfd9415be762d5233809d005d149417d3c30a16084b0c3842","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f72a954c349bccf89e393e243763fb141257a54d6647e369c79beda371378f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6b47f0ea0108d741abf731f728b357a8370c238ffe73fcee007619484c24356","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0d1598df1729ec0e33ea6dfd7ca8e063941308fc28b90fe59660158167b9627","signature":"f592c7e333a33b4e5dba58516b31a9ba2c3f5639c989fce88351556ba49606fc"},{"version":"99338b1762fec1fed1659b7aad2bcdd744d239177d0c147c540b34ef93ecc986","signature":"408859ac0de230e5b42b2bb888ba6da273ead46b0a8123a5d21282ddf6335afb"},{"version":"20410f27b941544a2d84068655c0f03e95d6bfc15348fd081814eedb7dd1a023","signature":"571d3448b7e5dbb700ec919745d70b84c0859909793935026a8331b7666d91ed"},{"version":"ce2867edd9fc13f9654dfd5cc76cf42d05110585108187658b8cdd4a797bb144","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa4db26266d6f651c711350ddf671278179e6f59b28d3c390ae50a9b20a3aae4","signature":"921c81a312317ce376b3db64ec158a40d264b56c798653f7985b9361289d951a"},{"version":"541204b45188f36c133a8dbdb67627cacea1fcccc8f56e34ab3a6df5a8842e23","signature":"03da542307af2869e7b2c1de8d1237d05b584cf0acf36c9d40f8e994f21adb2e"},"5f1ab4340b3a3f3d2c88167d0b98d1d8ae6c6d4b1ed845f25c3069d7d1b902d1",{"version":"62fdb9ea1d1284dc72bae3338d2a20c737814b30d30c9d0ce40aec4fcbd51746","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cab9185002bd5ebd154b6106ff93ae480bb26be2bc14bbf19180ae690449af28","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b980df9c1d9398fb15cda202074eeb45eca1b733888708d0fb43c021b5411991","signature":"b0ba848f7538ba06336d964c03d2289007500242648df4d1a2e1f693d4823c38"},{"version":"3d64c2914a71ab3af9fd253eda13ea735a784923284005d07af763636578b46e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"178dc732f2d61a4fd094b6672b6c438d1b1d6cfd5489564206a84e3df486beff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6f815019b8b763503cecf9ac86f9de6bd8593180a0db3a624f98acf88dad162f","signature":"b5e89db47e4299930bf6020c3ac33fe228d590042b7dd4c5a3dc245027bd9a83"},{"version":"8f6aa64ab08524e8ee85ed63f8dffa377a7f4017680001a3669a963162f9ddef","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03c74f23371366e58d1160b9f84eb63aedecd51d3ca319bfb994b8f6b3ea344d","signature":"7b27496df462d7c5956667f688b1b318c2ab3081852bcd634ba80e1de4e9ffe0"},{"version":"9382ac249f4efbc0256803deafe838b51123955ca8b68c68a4be2b2c4a94027b","signature":"f4956881b9e58a4a626bbd99a98451461e46649ddbdc1560b635cb904b527c19"},{"version":"e9cf4f29b3c48849f12aa161f42fa890e606873410f535dd09def91b19b4924d","signature":"8f1e73205101242a9924a904f571655aa7b1c1c193ff6c4ba0e7e272aeecaa37"},{"version":"e1ae660c622a39b34c87f6dd244d59846a5d110d2c39bcf4316a499b166b6e7a","signature":"6940bec49524de4fcb325d41bbb724aada9cc73b4df1ae6498284a510bf97a59"},{"version":"9071146edc7aae0d6978d242a8f9d5988db42204717a627555a2342181c3ccd0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"695e9a27d875aae3f404cbd6ff901fedf0d77c193cfd9552edba781658ed0e85","signature":"a299ef368d46feb485cacc1257882c710c962c3835c15268544a95d9385c6641"},{"version":"d0608ad5086ad006c7c2a10e4fce58cbdac946d73cda4c270717bbc11751afcb","signature":"9d735a325ce1bbc7e6f8c5188f6eab0f769559979163f6083635913e83cbe5c7"},{"version":"42b18867a7543fec221e4f0321e077538e596cbacfec0595872df4876635dccb","signature":"baa8a117328606a5a80729fa29d3b99e604d1c58274ce6c705b1dd17550d4173"},{"version":"c07f3037b31e0bd7e1384c41a6fd6524ac141e8f56b93a4a12ec63d75a704edf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"da142229522424470a86d0f568ba8eb149a4792e52f53cb7ce8c6b117ef65a3c","signature":"131aafceaec427354fbdb2f0c518ce34b5d29ed41ba96f5be2e0c6828e34c36d"},{"version":"6e925ea850d5c27ac93c1d0a26203779a347ded9a658643ac71febb557086d09","signature":"faf83f25a2e1c4c2cdd395f86877ce483031a5fde85d0bfa74cd27548f3139ff"},{"version":"25b749d6ada24514fd767c7212f8710ed80c3f54499a13642246913e678553a1","signature":"27bb3ddf3da26f0251f6fa1f7b1d888720e20fcb54f8513e691c7276c730e0c0"},{"version":"f77ebf90d0877e84d5f546d128be5e362554f93395f46ab6fe1fbf060b962765","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8efd52cc22298b4d6f0540b8c96ad97a563fd1a0effd9c245b9da300b5eef04","impliedFormat":99},{"version":"0d8cb9539485655600b329ddfcdb91d1b4b20f5d1b40a9e40c8017938fb68d5a","impliedFormat":99},{"version":"51aae950c97b61105064619e52f8b4e702ed9d88f6a38d8a6d461389be28dd0b","impliedFormat":99},{"version":"08a8feab6868367d5112474f9015e5b00c101012a639309e88fb105f94ced534","impliedFormat":99},{"version":"a15a870f6ab5a26a7eb91ddd8c47ff4e00bc23ece96ab48ff8aaf42450478a50","impliedFormat":99},{"version":"73390a82cbd5ea87d8bcdf183d66853207a111de00a8512c68ca17b47a11e65d","impliedFormat":99},{"version":"53755d0e8037d36720dc68e2e8c77512698befd0c0d48ac5d20c41986df91bc2","impliedFormat":99},{"version":"e0f44fe626dcd0026670f01dc0af34c40332729e8a1ce2dccc67e2ad5c96e3a5","impliedFormat":99},{"version":"2919501096a871a68fa6bda28480d7c237b232928215d6edec67e75dafc820c3","impliedFormat":99},{"version":"7f7f3dabb63cde6344d767f66379aa90b17c87362d999507724758247445d005","impliedFormat":99},{"version":"1d6f7095a9b7bfd7d035a4b07f23378ba7c44993d881b75593a252f186671e51","impliedFormat":99},{"version":"952f574aeb762b9927559c9d128dccf663352aa92736ebb856d2cec80fabceda","impliedFormat":99},{"version":"c06c1379c3f1bcf21007bd9a92e8c7a8e63611387392411bbb2399c0a4c5ae04","impliedFormat":99},{"version":"0fab16fa249312e20d9a96e0464c7ae63c841b17c02401a59ccd0bcdfa67bfcc","impliedFormat":99},{"version":"027464bfd5f5d3110b7b5303ee3a09d3bd74e630393c5caef2cbfe1bb6ca59d7","impliedFormat":99},{"version":"3e9aad7dd39dc61c41d0c249427d39b3548f7ac02f2fa2e4a813c38a8e1a2e01","impliedFormat":99},{"version":"3f28c2bdd8d3da9487f032bf85ea09bf9f24f6f02ae2336cb65e6988aa92de5b","impliedFormat":99},{"version":"6a437b4b58f8b3b220f3ae8af2230bf3bdd0fa4c17db62a9a2a03fd224a68a70","impliedFormat":99},{"version":"e16749a9377888735e5edcc765da4ac2f5a552de2ef46d930039b2d54f199fb6","impliedFormat":99},{"version":"bf973f547b27688728916b64a98fcfa836772e7382211a9692684947220ad550","impliedFormat":99},{"version":"507b0e93358d09b74a0caef2370175290a47c790dbf71fb63d1b4593b7e070ff","impliedFormat":99},{"version":"eb7e05259b0603e91365983fffb6e6dc1e574f1cbcf09c51bdad4f3717869a82","signature":"5679163e510a4314da81e928dfe7e72c6671b0377ebfa606c80e18db43ad402f"},{"version":"8269474f9aca3f56fe5ff007900aed4be90d6271a628d561d20cc29de0d5576e","signature":"298cce3b54e8d74b37facacfcc1297add32f454323d60ed4b4ee24ad651c76d4"},{"version":"88b5d609cf1c008e5d7926489df81bd606581dd083772e8ca735c1c0bc103093","signature":"3ba28f6b4d58c39bee9b307f9a7267970b31adae4c3163ce2fb889c48f25396f"},{"version":"dd0a4bfc93ee858cf6af173c428400652c01288761e7dc00b513652d005cd91f","signature":"a32a75b40daf9f63898f39c282c8975b28c1f9086ee5957707c075c8cadc8bf1"},{"version":"f35ccdbcb49becc34f1c71a68ad0d843bf02fea572cb852884d0a96ac7169830","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"4e50f3ba9e8934a6d1fe00ae73264a3ab2202db1939a6fe875e3375022d62b4a","signature":"29022c2d2e6ec02be440b4da758f983ded6daf9a771dfb932ad7f3b93a04142d"},{"version":"7f13d1469822b3ce57bd440274f4a8d9c2d9925fa644d284fdeeb520f0ab43fa","signature":"248bf8fb49df3283090f3d2a137e3d74dbe93490ca12bcca8686a3e1004e5c40"},{"version":"38b62c6f9b44cd3b4e899e63d64a5dda62a2abfd9a14807995944543680ae4e9","signature":"6c7d3152f35e7feb3abd1e338a59f61ff28eb27f96fbc42a01b988084cb1999b"},{"version":"0ffafc0be8faa4fdb62e0a657016a6ddf07e40096b6f5aea41809208e4465534","signature":"7f9e8c2aef5ef9f51990ac594879a01c44bb8459d6c0d49fe5c311cf2e08af8b"},{"version":"0ed05108ce0f4538b5351bcf9d523912d200abe5b804951cf18b2e12feff6b70","signature":"f453c01ca04957da00f261867fea88fe674b34dde9b1da183dc55f2bed19f364"},{"version":"fe55ccd61eb15dd0480443215f5556cd56d9a68075a79a1294e2d9cae200d70d","signature":"9992a93411c1d80cef73f32ab5ac10acddd25700903cf8b5b47925eae8be2a60"},{"version":"bc37c33a1ff23e40250be0eb50b16aa3ebc6d24f58a55e5c41d968c4a23439ff","signature":"dbeea29fcca6bdfffbac8a6c482bb4199d8fb77cc64503880fb3dddee81212b6"},{"version":"7fd1557a212fb2a671abe96de2e1b6a3e5110a07c49076705cdaf9c3e0f81f7c","signature":"dc57421ea686f59b81eeb7885916d7a5cfcf6aac9113b8908c5edbe4d4a7a296"},{"version":"b9eb4fbe039e06b65ba30bb786e50fa9b48e25d7eb26c4cc1cceba3a6c81615a","signature":"70faab149c7f9a9cfde8ede12a99419d9ebbc61d822a7c16757902918cee94aa"},{"version":"7ad085c66c15e665b43f1055b09ddc1d9c11a3d8f21174c9d3d9205d7dbc03c8","signature":"5b361261b9d93e4a2c0d2e02bf9f0dfc60fd8c761ef6fccdabf563bd3aebb419"},{"version":"68dbb99a0ef2ffb046b1385fca8116795a7b4bf5700193b7bf2fe9cfb62e72e6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ded33ba85bd4f5491a76b8972dfb3104e8b7b4e1b256c44313d7ea9d21647d2c","signature":"43d84b56d871c4b5bcfbaae3b58381ff0a77d0bca1733ded9b89350275269033"},{"version":"1f260100362d7309e0cbae29fc09c4c36be2e4512013a3f6cd4706ada09c6675","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f911a52096fa219660557bc3c9ccb5745889d8a357674e8ae67585678891e106","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bb2d10f9aa6efc083456e706d085f717688c6c0a4030e0c0a1afe45068b41d1c","signature":"88525a755e322d9d9eac12bc44f4e51464db195ab9b4a5e0534d7a453fcf586a"},{"version":"b1a83e0013403df0cac8f2fbc11e735c29efdfc41757d6c49702a330510605d0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a2cadf5506cf6c268f65051dd63ddd5cdc82f5effcf658778af2b12e497726b","signature":"3d3c808c01d46ac6f212b5bf9a3780af30c9ac93fabf3f754e01eece8478207d"},{"version":"2e1f498ba2e37492111a97c6048e07af342ff8df126eeaac6cb99f94372f8ca7","signature":"4a997dea3de3d148c650f5ad6c57d75d5adb6655108e0af42e57f9661d5a9297"},{"version":"759a24229c02dafd2cd73ad6e3325c043d16ad85081d2e76296664bd96226ae2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"315789c8527a430a3cd968051ca6d731a0f13a47ff395dcf180e0d02dbee4dc4","signature":"0b8a37fabf134f4cfdaefe57ab21468b1c97d26b90c4ff558d695fb8e4c11be6"},{"version":"9b7287bd51e848b323551afe464c4a91ef2b74bf1ed703dc7c7c5e35cd9073f4","signature":"bf47aee07d830c691e0bb1caecf0a38aba368d98da54866d98258c4057feaaee"},{"version":"45eddefc55733af83490debe404d6f06892219189f1b3cf4470e304415600773","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89f0ba8c9a02ad55e23f296ff35f0d1d6361a190811401c3f0d767a1aeeefec1","signature":"11a904b87a71e58a2283da4755e0042d78cd7f56395ab9a1b6ecb09290f3672b"},{"version":"5aa6936f80aaf206b952e46cb830f50e49e37862c6cc4fdca99000c797995a54","signature":"2bb79d1f86f6d11a1a240d2a4a538d676a6ff8231126766ef84667cc2e945903"},{"version":"1f607599e3d2f94f8bc20f8f46a594132cd1b1b1004f0a4619dcfe84f792c774","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca3da68186289d0f9bd93dc9a7e5c3eb30dae2526ca55be4b106af239b63d0fd","signature":"d539ac9920f9a947cd986cb61772e65f48bc0442d1d94e2ce8d6e25f394cedac"},{"version":"fe17ac85f4e0b305bf44f3a4d81cb75397d9d7776aa2649ead1caf2291f0d416","signature":"8326d42a2c5e8fd8339f23ecbcb4a89f839e171090d9d2daf0e758e12a154c89"},{"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":"a82585ad71d31401551de3ffa5389e8b078cc4211320f84ac2bf055e522c2765","signature":"60ff22f8744ceb8cfa14cb7ba540a56a25d7f4902114a3ff67a6e54586a3d3b7"},{"version":"d98bc291f61d6be8db9ae5fab941757fb72f211684af075289bd48cccb780aa5","signature":"4b0eec9971cc44cf2d3c76d33c5d4a7e7465908fb225e9b612064b90d5fb394c"},{"version":"92b944d4674ccbb19cf8373ffa9e6f50fd707709093aee9fc445163550002a40","signature":"8f7b9781bbbed468f49e1f74933b7c0e5d2c6ddf8afbd6a5d81183d053d43a28"},{"version":"5be39f3f33dcda0458cca755ee8c910507c3d2683e10541dc7e5a8393fc82601","signature":"94b0e10227654e51b43772a7332db7c9bebe31377ab1cd1cef4797d40a3a0c6b"},{"version":"7236d482e2ca1a6307e2182466f18dc8ee373209e5588f156b019f949cd9ece0","signature":"c92738c8f42ef530edebc3a1912a4ba2ec85ad86494839d23b6084782f9f2e91"},{"version":"da35015d12dac52832201a4900b07e4b0f4fc08f282eee6e7131d895db1930c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7d0aee097d3d04fc9227b58b76f92516a8b29f419ac2b93d10b58ea75f213258","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"24ac88c5cd809281eca45ae09b8b16dec4318221ea50d1b47888254798a046f9","signature":"c97c4207c753de5cccfb48d3488e193f8846f302690bd3ff73f4de951675b01a"},{"version":"63bb37a1d958427795e2e6ca7fb9451aff711e665b45bd4878ba693da9a140cc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8f785b87f0a4f596648fb2e5fb351d34d4bffa5288980a8eb52ee82ed27180b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b581577a6affe98a790ea707971843ad284ae4cbd4bd1dc17642196e3c0de158","signature":"840d3e2bdea7d5a418436aafedac9749b1d9de78bda0f825a48869cfbb3e7f83"},{"version":"3b8a02a9dda0124bf30a030727c96e3d34272b55369bc55cf55102cc90ff4a41","signature":"09d3eb6502b5bfea1281c54cfb4111b4a05d9716f4643a5029f964c230b5b551"},{"version":"0eee1242c13bce68990b788037aaecdb865d63943bc7b5681b8688cbc6d64e60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3be9843036cf2c658821504c4931c7e1055db71db2a607b9bc5d8aa7be679a2","signature":"3a526554ad06e5c46700e8b1ac5e6f817fdca923787a3c9344acba81e8d17ff1"},{"version":"ae3566ec3d167f05176112f609460a77863016fec3216a87a15c36bef01d370d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c914982336f64f67076a86d5569fd1b878a42f415962a07d1d151a4d65d187d2","signature":"33831d2be2fefd1ecc0b722e8270094b857a42109f3eb3bdb5c5e666233c588c"},{"version":"4d5caf53ba49b41ff378943f6b1dad1ea8e042426fa713b57c9524219bbbf573","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd703e7c27cc5dd4780eba552a51a698b459c9f12dbddfeafa9e0d216a4e66b2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c09c42c93bcd3ed631ea6902d069b5798db25862e47fdf4ba5f47ff0d36b2a51","signature":"fa9886eda8d6ff931fdf8e61b9af2aa42491e152278324020c487e489e778f70"},{"version":"bf82447aeb19b4df2e40900f920c15695a8557392588397ce359c51b133c00df","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e4dd6a5a91d2798fa4dd70d7e6f682475942bedb36b9f870cbb15e5c1f1a54b","signature":"a8a191cc7d792c8cc2d87c992ffee823187689960dc717e122e158f24b77a242"},{"version":"0a5fe133b4ef41f0f2443b3cc82c4b99be11738a93d613fd12014e9d632c2fcd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cbf3758a6cc16ff397b8a2a27221d1f6d5f053265e353af0f37b356f0384b85b","signature":"3d07ef5fca347d934f76c6eb3558e0a81da33951129d307c04716a0812321893"},{"version":"e9dc912bf8a7678feb40d7d996c9263bad3b7fdab9ad9ac95d4e4ed023403d29","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b3f5e2463bca0844de6802fe4ee8e1e29a3c11d14fdd3fa7c96ae69fba8d74e","signature":"053cec7f0a8bd24eeddfee887cbc9883f56cab39ebed9e143de9f0a6cf34d202"},{"version":"d878f9fb504fbde395cd7c61e48f2fc7bbc7d0cf14828004f95e5f9fe64f238c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"12564f17d21c31e77240293e7434c0c933945a3a3a142130eaa166d0042f5529","signature":"ed4aed28c29ff0fefa86143fc6824969cb43f6bde467d4f9254c84372fa63cfc"},{"version":"3e8a4a82fa1baa0281a0ccf309c22c954488a949c13a7ab6fd7c171b1bafb361","signature":"13428a7125f291070d1c531f233c9e8719c80160dd3587d51adf86a3e41bb62f"},{"version":"b51fb5ae439f311990f5a5c2fc4914fda89e5ebd585b33d22d0251afe382d391","signature":"f49a7f528e4e42999b277a5ea73799e735e349e562a0ac6c97b99644a13a3ed0"},{"version":"deb873b1dff75e59633350db7fdd9e3c125d248ac7bf2c193a81e9665bbad9a1","signature":"0502f677499fe5b2d8cbb7f8e703465005e5c77788839d14377ee4b3da22fe5a"},{"version":"d9628bca2f50c1a70ef77c452fe293c91380dccd76c285dd3aa988c0f93fed8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"59c0ddf46c0d1d17e34ceb4c253a3f3bc7654c450002d8f8080476a3baaf5755","signature":"f7d9758437ce102b893d78f8a901109a32ffc713b3c2ab288e8e15860dd3a835"},{"version":"7adf0dcdc081964a00a2235aa42fd757563b15038955013b98097c5731705a2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d9df7df9ddd168648cb6e27223b325763738bccb1b57e965ba0d8443cd166fe4","signature":"d786daad1509af6e601e8de4259a2c6abae27fd33287b4936fd079fbfd1f0ce0"},{"version":"99f67ae9774e4bb88839948649b26a55dfbe8b99ec80ecab4c548102d2ddcaf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aae8eb9b4f313c457b2f82fde10a63f117333645b818b48d2ed26fb2333ca42c","signature":"b76cb4bbf6287754fb7246ca57b8b0cfc52c84d5696a3363f193d2a3fa0b1e16"},{"version":"53ed7ccce63fb30e129e73dd0abff74d68929d92fbe3b20f8ade965780b2353c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3abcb2afd6c10eadcd9e5cfcffe3f93172b891ac80b079ad1421105d1574b983","signature":"2cdedb09674dadec42708ff08cf53e8ebfb3dc9402a0aa42464a061d228c7ef2"},{"version":"ade0a7d50f110afaed90ffd5f24f1617bf9b8a0d2f118530892d139ad67f29fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4bc4b9b2e5a66597bed3af39f456ab78cc11601400c5adb4ad46a173bd03da41","signature":"2dc1bb408cf19157f86ca0f3984f3837afca66209bd009a34b84daf16f8c7543"},{"version":"a3311c2d4225d8eacfa5a9e662811aed57fdb4de78b824f1a702311b3f99b09a","signature":"651d947dcd8fb9009c702ecf43eea7c50c9ebe342b6e0619cbcda9d8a8b64e10"},{"version":"d7e17a1b90344a6d9c26f1462f77d6350a6882706064a36e5d640f5726ce49a6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ea2d922ef7dd2904b092b91fbaa35be0af427504b9fc7e14ab5fbb6cd7c40846","signature":"cc4b917492e221996d1271af2f86e5e864c2d8053a299038dcae940e332e312b"},{"version":"5456720ba13d5a5037b07c10816207ca9a81cd79a370af608115c578d61146fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"695b2c91c87bd844d4b97002869db4dae9f2c96de4cc397b6841670d184a3a8c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a073d10bc03e721d0b1ce2620253ae9b69daa32fbebb2f7a8c67e7e1579c6f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a80fa136e8559ddb40afdfe957e7c625614d02ad4a72476a1dd758fc31ed2e54","signature":"a1acf9b04a9f848692e1a5cb1bafa0e53bcefdbcd40c5bf311062ba23188a339"},{"version":"e79d387b4470cc2ef4df34e09b4113eb85200a7c8c6508e4a2f418c63e29ae5a","signature":"ca37703109f463d6107118f4b3d1fa0eca1bab385f6e35583a2fd13ef66b3112"},{"version":"56396e7c37789adc6f28a7c461ae904c01688c2836ac6278a9f9ee864078c7b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4025a1efab8877af2ed8d8edda349736055a800404134c6b24ee95cdfb0c0ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0a46d4afc023db18a3c9d7816e3f90d960122319e2765c5d686f76da661864bb","signature":"b5a97abd79ec3360bbef597b4f34eab1b9f0d3545d0c3f46e3b3e2ec6e91771b"},{"version":"4f649bd1b169333de666f079f7989d184c27109ce26544354957f5be00abd232","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f84acbbf9c1536d22e69d354fae1dc2d43430ca0e524721df713722f9e26890f","signature":"bcea8c0d3b0636e8255a7b6f3c42b075dff08702bc473fa7d6ad74adaef773b1"},{"version":"b5da1cdeaf5fc3b53aab62bbdd5da7d9385fdb2839a18fad0e3b2c31c5d888da","signature":"a43861be0f45c9bb0763d1c8aaa880b6c5d0b2a37a07bc65bfde949ce648ad79"},{"version":"0e30e16f547666851c4fe51505a9feeff6508b2d720b7b8c60691e5158db10d6","signature":"cc094a8b2d9686d5ff268266e02eedf9d66e2389a04c46fc49cc819d23134a39"},{"version":"1c03c99ee1b5f5bcf0704dac9398e922805929cd23a7ad246ad0d67cfc3826db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8adc0df2bc9038f9423e9947d03f490375a9f615cc8119055af8a695bb830a5","signature":"bb01d18cd374f84cff1cc159163df3a7f602a298d8e925ac993012fbcd7e2bfc"},{"version":"6096174ef99bb11f2656cd3f15a2fb649e504782c6ee27090448b681e33c2b40","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"987a3bc405a132b704d415e99a6708c6ea54d0a70766ecf1ae59bd13034d848d","signature":"2d44dcdbe1d297af2ef6785176a9165f4feb886490712c82ab8578ca96ee0d10"},{"version":"187610881a6b1f7370788848d0a2af5a17e94b9b437727556ef3d2fe018a98f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0709632ce350e4970bc7fdb88e656ecea4b7579875a6f20ad20cdaaad26f369","signature":"f9a530c655221c9f5a24fc3421f341b21bd38da824f7612da7c87804306eca36"},{"version":"2b3793a5342b5d7ef5498271aa50c1fd31ce56b70f70d0dc5f9da4174eb1e5cc","signature":"9ae9233a7cd435509757e52ca1503b31fc923e1bf163d9bfba847b1a7dd89e51"},{"version":"1391eb93befc7b56fcc8fc9d4c37affcb37252ce6e91400da018023fac32c807","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"17b39df68c9417376ab9d3f845ad45905499eec1ee9798fcdb980cbcacbf2f44","signature":"f8c081ad7f58588db5940385eaf280c202e6ce42c0117ad62f774ffc421712ad"},{"version":"5ed076225f05deb1d6965f90227ac87a5b06e66b9a30481c699bc0fec1ed6c9b","signature":"883833dec7bf0238bfbbb33db50c709cc7ca3a1f6714d17992c0d7e82a964d00"},{"version":"e0de0a265fa6fa0c0460995f60fe2255ab2ee907915b7a2fd8f509053af31ab1","signature":"3d655def48973efb420a82a2e05119da3a2c45672bdfc7a695f6e569edaa417c"},{"version":"ef25eb1291626f363fdd9b26186aadc8a5821fbdcba58c9464050b1269e27063","signature":"f793afe8c2bd867c272d5562b829000a5df12669a8922a4977e3ea420299480c"},{"version":"6e1b2b7611a6083e43d49888a2437c99c989141e611966568fe0527037358608","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3f072b168376dc71baf99d36fea4aba49a269f5852826888a3c6c95e5c9cb202","signature":"7fb20dbe5a83b73a18118cafefc659b66c75e285f8f6100023eed6218035191e"},{"version":"98ba07f2f211272213e4201fa31bbe0de1f95049cb411f0c7dec9e9de1fc8232","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8d9a8784de549deea41f12c4241eb87f77fe7b7f8222ddd7a6ea05085980d5c9","signature":"88af6abc2bcc060a798687a7bc8f8bc23f47f5bb2ea736e89666093f4e682a0c"},{"version":"365e7300df17dc4f13c79443fecf0e1adf057cf17556c2c6fc20f1021f72ab22","signature":"e18de9a7b62fac87db7bdfab03946f00b49de5cfc11b37f39d95c1f6d05b7dc4"},{"version":"731bdeaef898c61ad06b71022c0669d246ac039c1368fbb38c2605d34d867da3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"baab78e9401b9a82e8fb3634de0d3750cbf4d3d6afb59ba28472ab15afd3a749","signature":"3d01773dca02fddc18139d243b8adbbe1c6c6447b8235bdc3cbbd4b493e7ffbd"},{"version":"1cce0ed0784dfa68a0572c20ceb1a173664dbb3ac59eead22d55be246fdf17d9","signature":"5de2fd3f978ef1724ed1d72271f8d9bd911d19d80a709137225e173127e3c615"},{"version":"57e3b4916970da260c692cda82bc670552fa93563710da8485ef3f1a40fc0cd8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"24772aec0e0f59dd17a2a1c4a924fa4fc228f24c64bee4fee8c5c08965f05925","impliedFormat":99},{"version":"e636cb7d61143bd3901daa91d1c2c3d8a53b677f6bfe51fafbd2d14a51efdfd0","affectsGlobalScope":true,"impliedFormat":99},{"version":"d33b19c5e9b2f8b26b1875c7aa12229cdd1e3ff0e809c89189805a05c626dc3f","impliedFormat":99},{"version":"bdd14f07b4eca0b4b5203b85b8dbc4d084c749fa590bee5ea613e1641dcd3b29","impliedFormat":99},{"version":"077cd7acbb4a3b50b4a01690d6a7d2583ebb39335f612763442a4d33dde01c36","impliedFormat":99},{"version":"31b29d52cedf2cb6be5fe1a8ada9aac55fd4933351ad18d71d77466659c95bed","signature":"6444595fd97d8356ac957d200cf99d4653612f84715bfd4905ec02a1162856b4"},{"version":"2a01af100d878ce1be4ad967937beb31bddb09778227c52f425fd81cee137c8f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cf0f4d9e5edafe3f777e151b9719afb37ca6abaa904fb8289367fe99913f0ad1","signature":"307d71207bdccbbf886a1b1044f39eafddb7b2457a81eb1a1843a81db10e37eb"},{"version":"a30abd322bbe6fce3aae6ef89bfdad79fe165b3dc284d001d8dd05392cf22033","signature":"13573020c64550f2efba1d5a0380288280a85b4da7c0693f1f25c759a8114211"},{"version":"cfe8a898ca6cd1e4e6aba4518ff485036d62faaafbe219f8192067d32016306b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03493681f3175378f73cc1994441b55eb2f178585c613377853cdf5dfb39ecc2","signature":"4540e50e72fce7b0cbb91e773d9c3ace94268cad237d1a032f294b9786a348b1"},{"version":"0ff60462f4b1f78e599d6266ad526e552d85c49043b0d5f48f992585da46ae81","signature":"5c539fd09316939fa4dabeae933f1289e99585d6fc398630670d5d16bb41137a"},{"version":"a0cd91fdf58c51fd2e581171ba5d3bb4a8f374abd39509a597314c7d3ce2c0c3","signature":"bb97d8ce68b8061ea36f60acf43e82f3560c4e53522b05949721c6b7d014d89a"},{"version":"533063ff41e323fba7e5cbdcfee392348005ed117c5badb1151f6b4803a7a3d8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8d096c0b73874d64e56e5fe99fb94f14b5a6fc0824a4a1cc0251147a823c25a3","signature":"06980b548bb6ff2b15c92032296a46d7f80d3e8ad9af172f5c2ccdefa86b3fb9"},{"version":"d330961532fa59192f0330dd430076475d3a3f5cbbb60c2ba196351c069243ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34ef539c1384da9fe858fb9c16368ddf1624a4914dc3f0fabc7a39811bcd2668","signature":"ed6cfc1cf330cbbc602b7a0305aebcef220219fcd5cd3e4493e5afe79058fcee"},{"version":"f7409e1093e57b3f7be327a71c71087e1f7a767333fc10cff7c2504af7222f88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b203b12c30dae8bb84ff0f770a857445942d8c9ec8d6ee472c3ca6b0b5b261d0","signature":"9fc50c5741ded49943ed4b81fc428d0aaa18cefc596400fdb71fdd11a21d8d8c"},{"version":"24803211766a60a0fca7ca541d6a158d376c65f7ae4827a8661063c2e70c06e5","signature":"6e2681371e356de1c4fa982616bd1d26b7c525b5ca9f75e1f688b83332e8a81f"},{"version":"1453393d564bcb47dd35ada6b469c661ef5d6c98f9dcbd7bc0f9eee3470ac944","signature":"22462cc125563699336669ccb959793d6c462626957a1da4ec4a639d4341fb3c"},{"version":"7c56faad4a628f9671b73a1227c941f930b55649699ac62931e360389775edff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"579a472a70260bf6b40d68aeffa65bf00fa5c59a44bf214f2385dce399f5898c","signature":"fb4582d6a3a9b2152a49c918d6c98ace7ce35978ebe850af889e7aaa526551eb"},{"version":"3fa0c656d89d31fa67f25c8b3eb9974b1aab6fd8fe5ae0fafd521e7d6808f71c","signature":"cf64d4b205595fbd260e6fced4216298b35c82faba7dce73a9e205add66ef85d"},{"version":"ad5ab4bc064063efa662328e47c8eb39c80888a25467fefd231a32e874162d6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0e29314c7c3239de9961d460d5347081cc6e49bf065dd0a6cca6e7132a99ee9","signature":"ccb2b3ebb1fa7bd3fa3e02c7a23ecfb2ebad06df9c3c8a9e685113d81026d0bb"},{"version":"614e5cdc3c5f89f035510a0c61652fdeba39a62c9904acde7de79fce2d60bfe3","signature":"bfe40cd3dd4d0d35754643dbf07ecb96362953f4fdb490803e122593e679db64"},{"version":"36e5bb11081348bd0869d683fadc9a4115fb28720594bdf185a13ff19faac88d","signature":"d356e9c1bad769f9e8d358a35c420cc37a0ad01ea4f865d968f3b7fe10c9c3de"},{"version":"4d46dcb6027f62db92924103d77c199455ed38d1dc1c6c29bb65a707c25f847e","signature":"14084429a03e8974f38aaa1890d6a80ea62d5c8f0b627e0f824afb0ac621a65c"},{"version":"1d5a2531ab660f7a4f8b4572b7a19c23fc5a431299ecb8c1846cf8e279b97851","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6a819db1ca73bc3e3b29597b18ca5c36e2dafa02b171e8bb92208b2d50b353","signature":"1571b1b7546d0267d42d0c0b3e1e4593b2ef990541b260a9652427dd82758bb7"},{"version":"a62a7e45347ca1dc80dd4df8d2094790870c03b1c890960cce9628934f695295","signature":"af0b9205baeb5b5d2e4470da33f26673b5c363db0ccfe761276f8af34cfe3e9b"},{"version":"4217680981bab6d62ee8fe0cbac591599bf18f30cf2be39170d344eca5f7885f","signature":"5d3ccf27d7ce9e5f390fa882da69e253103b64bcc4e1af716d4e385d1f7dea5f"},{"version":"0571fa29dd502778997d9453169040a83607ae311f6ab6a7ce90fdaa83f86a72","signature":"6abb8469a763dfe1299c79302eb5559ecc978df41c92c0444a30c1b55710860b"},{"version":"afaea598c95b1ad2bd0ed30bfb6ad867d78bf021b73a048b8fdaeb90cff22d88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1bddf61aee12dc67fa70b5e40a9124f1f71f960b6bfe006fe4078273a7f77e5","signature":"77307295274cc402aca163afe863f0ae8a1d2e94588f2acd35ec24d77af97b75"},{"version":"d579bdba0db63d615c541195af19b783527c13bbcc870e83ea61d198e3be56c6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"10412b70545a4b21b51229be0a35ddb2bdff35e164c35e214f5f56baf863f12a","signature":"8aa83e11d68ccfd7360e5de9cb82c18ce6dd67f9c7dd89c81dc67e55dab60864"},{"version":"ae6af43f3746a31699410d070400739e19ef2281ccaba80b5e4b1c4dd4ce644b","signature":"1fd318722bb7bd17560dd18e11b824febc7904ec355636c7e042efcf18859c12"},{"version":"acce55a78691675d126c86174822bc67282435510ece51f11f0fe3e3e3439a2d","signature":"69db250a4e6aaf0f4ed86907855c93327ae8e663b3b1fdb429fa0405ee768215"},{"version":"7aa764a1146707f1b9e18292969e24f394ef3c347d4f396cc6f90d39c3f3b6da","signature":"7c52c6c55104753b3519528829004136bfbe6e76535ec0a19430668fabd41269"},{"version":"688c5e58ff9137a2c5d6eb1a79475ec4c9d61c34bb10080e21d09babaa30ae1c","signature":"7a49a822cb790c72be6db966c7f0d69c641479732f211b020cbb08bc4f30a3d1"},{"version":"c51f1961b6b22a86183d4e8e166a4b08df4cf3537533f9249a79d3b460efe6ab","signature":"60f3fa0096effac152c038612c40d100ab675f4d07eaade19ab26291654c5322"},{"version":"13cc0e63b3212f43a760f9618ac9a5a26a3123954baa408ada44dd9744d060f4","signature":"6d2de774f7f1930f5a1a0061d45b779777be9dc7e6125661388a11a43f386636"},{"version":"693212d0a67ee305c09bfdd670455ad335e448c9fd52fb8c69ecbcda23eb2b93","signature":"225d95d4c8f9caffa003ab70fa3ac2d8b66e4bca291dc775b2d1ad4b676b660d"},{"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":"6bc9832d675edd15ca0c8e096cc4008e2791d822cddbe218e7fe65d33de8fa2e","signature":"78f739f5b91e1135aadb4752b0fbd6b6bad0fa86b3f0e889900982b12176fc2e"},{"version":"baad5518e27c0ff3bc6192606a3c70d64e52338ecf1a1492a3582c9e8827a7bf","signature":"9d01797abc1ce5d2b2ca095bee592fa4887661c3cb1603e9f126b767d68bb57a"},{"version":"a418d3e5729d2bc1f21789a3926a6e5db364e9f80410207f4eb28b55a5c70cff","signature":"afdce15dde5537aa0c81dab15a2367924eac28ab8f25ba3e403f7338da845b92"},{"version":"71dde8ef5faa2b2f5f4a8f56944429ff768600489ba021017b68473c93660eab","signature":"b3a61d1bb2c4eff882c25e5284189e1934aeb4af535fdb36694fc461cf4b7068"},{"version":"0b791c213954a91e7d80daceb4b7d7b53600a731e2227d3541d88a09fcea1621","signature":"b6e882b417c55fc40bb0b42ad061d8f97bd0b2fdbd2aec5aa2aa257420c7c2ec"},{"version":"c041ad3802a420609f6fbb3200a946b897838cb21b76e176e78b0cafa83698bf","signature":"63b2f936da9faea8c52723d6b78ca09ee0bb769833160bbcb000be8b7cb456ac"},{"version":"9d0c46e2b8776a71972db76904d933f54d190601cbd57b82438254c275808ccf","signature":"95bee50322d4d787b4a886030c691a2317aca49f557c115e52f95938343f65cc"},{"version":"df804257d254a2e00d640a55eefb2ae628da95dba0085ca824a04ea3ff69ac99","signature":"2302a6d37e153539b259b1f3bda1c10d344984b15efa30ea39ff5c83b5825977"},{"version":"74226e280a2991fdeba3808665dcce17f87736137ca79404c5d8d7c668eec8df","signature":"b4e98fe21b2b7cca7ccabb5169df346479a0e3bb6bf46c25946174977917d316"},{"version":"bb520dd5abb511ac234e88f420dfbfba03a6ef74a9c783850bddd833b8235b23","signature":"80923ea73f37baf4c06eb870a02060c68149fc8a8e47daddf6b55af1047d2b0c"},{"version":"372db055c8310930dcb90ffa00df06b44ac8e725c75e0c172786676ea6a11794","signature":"6179a86622a28cdafe5d99fb99e1ab06b1a06011bb9a8ae9d65d4697e61d5316"},{"version":"0d7213f8b71376061118e4f91a6faac51b38b372ad171b4df50bf3559ac2b3c0","signature":"37a718acb4d240ce0d45b7082a821b9bc8d9c523df47980aed0547887beafcb0"},{"version":"b858c7849e256828563264a2345354ac829be7d7afc77e2c04f7683b81ccc79d","signature":"e724140889de1a68b6fac45652942a37dbe94d0951da3249196817e9004181c8"},{"version":"a4c991c3fa2bc9437a6d84cd1b2557b904adf57f6e7098943d65a01b8c57acc7","signature":"4fa89a082213215027fc85892fd3a42bf898e652eded33469c2c31a75cc7db12"},{"version":"9da149c4fd78a4ccab4e68a54c0cec7c3bbe48163c7e4cb550569bbca603ddd4","signature":"f50138c9b21bb7d4b52c5bcb99ff08cab9112b3fa3eb67ba583c1c033f5658fc"},{"version":"baa1b838cd0e200f302fa49ee523d8f74fdf7a16c6d14a121621aec564cc92a1","signature":"fe0fcdbbfc40a17d638651589c6fdae7c4d56ed10a0bf9e04dc47fa42b94ead4"},{"version":"ea99af3c9a22cee8ea6b5754cb9d29c7076588361543519e0a95675747e2c17b","signature":"c77e23e46e99ad3082a3636b3fc4955831a6c50cc6be267983ed20d86c42279c"},{"version":"9bd02ddf990e7c7a97c5b70d4357ed3d8ca8f7bc5061615ea7b6d36f2e469ab4","signature":"59955746d15769af662a431f4bea5f2887de185eaadcf0f8e0df9766d138cff8"},{"version":"4dd94a38316ef3061df0358c3174ae3b95728649913ae705fff8343908b394f9","signature":"cf8dbf5f0b2752bab0df676b52d9848117b17fb7306ea6e45145ab60205b4e25"},{"version":"276463327ef1cf4bacf602f9a03566fa793923397e6bd8b85c4ffb2da5554a02","signature":"88658d83f1351b1364c8b286846e79d8ab064ec332f8c693f8d133347cd2fb52"},{"version":"eb3f998132c1ee368d9196be6771f374f6b809b6693f1f6a75be7118cca56145","signature":"f6a21896af14802ff331fa38713f7c2649cc5e19bbe7c90707dadcc592236ad1"},{"version":"5a11dbf49dfc0bacac057085c4c818507b8613630a6080846a330caa09f40a1e","signature":"887a929e952df6c08de135d3c73360dd80e833b99706ce3aef0c8b64b26ce68b"},{"version":"a128283ceced70086ed7a99436e55575c7d385f95ec1937b86e2d7c725c6e532","signature":"bb7d350c5b0c764dc29222248163f61f8540997db099b636c993ce1ec6981018"},{"version":"954eafdd8e119ae7fd13c652d092ac62f95a3c450127f9bf2c4235b9a5550f9a","signature":"7515a48dc017014e10b59b93449b24053cfe2f6cbec7424292ff04fb29f14569"},{"version":"6d9e1b7a1fa967fb8505a5fa33073efb38aec5e7b75f2dc6383c9f84f3b5c0ba","impliedFormat":1},{"version":"0462956c97fcc2f9a0f7a498600008751aae2b004f8ab4da34af41eb2fb5317d","signature":"8c23d09073975011bf5b8adde26ee58c4c5c27b5c4cc656a32313963f3388846"},{"version":"763e7dd605f87e9ab676c1dbd4ae54ae2bf6f75de50bfe5625b951ecea65dd58","signature":"d546106877ee81adbcf30a6867700a253c87d52a5cacf673d034666e2837a8d0"},{"version":"40c9ed5a63b54bd64ea351b5d853e67c373d730a8243e2fad4757eab3ec5ab8f","signature":"26ab3593d88f84d8250fde332b61ef8e6c9331bf4da6e89698ed83e95c57f7ee"},{"version":"42858b5e9f40b8a0b2f860a6304d779419ecf0c8773f6cf498c882bcd9aae1fb","signature":"2f55fd6804783792ef44c4afb78fd8a5d6a2810a4c02007e53ded6f01e24b521"},{"version":"819a9152da954b548e16204dfbcd75208938e5e1a21464998d2d155c14f08f64","signature":"b6ea2388d7e17effc8c7a702bd5e736213f77468d04bda4d8871a07ff6b191a0"},{"version":"7d57f62963f7f76d3e4604f86fa9e7fd005e3e11bc81490b32193dd9b3f019e4","signature":"f12acaa6f04cc3698628891d95a523a4bf0c03d03fb103edc7e4929709f1baf9"},{"version":"d6f4b9a418add129da3898008b6036a68dab54ec3997b04dd0bee2534d59a2fc","signature":"2bde30c5f8e10d5820a401c68e63e2b81a23077352c01977a10cc10a15f266f9"},{"version":"e918c0d2318411d705b553170e3a914970fddab501547d7e3db4fa9886565c9e","signature":"383dfa02a89c8b915ec43f711d0c60864b9a9f9921468dc25c5e67891c451c6c"},{"version":"e2af71fd224c850cd29f8cf8165e9eaa251df3dcb5fdb19cb10e1a20119a6728","signature":"e7a405682b3c45cda0ebe90315e22f754732efeacd7010bc0f6d8fac3582eadb"},{"version":"1632954ce7df258a88f3bcf040fd8a47839bc8b3fa71b64ad14ecc4819388202","signature":"b99c3b4cb099944054ae1cb1917bd5fcce00dc0837b241497749f379cdc0e9d2"},{"version":"a67823a8d4a16991b3653dda2eb722a15efb2762dec299662a23322bf2394e43","signature":"419996c73365008124de6ed63224ae81323de45079174bcac6b0dbdfc0108b44"},{"version":"2076d2cf1cdaeaeb896e27ec77082c91b5e485d297935597e76c8fec1c08e39b","signature":"dc1097eff258d192d1b76e71f09eb7a5a8c9b4776c6e8ce8af855e41ce73a274"},{"version":"a84795af5152dc3fc5782eedd4031079b9753301847f158ca3c979e551a4ad34","signature":"363524bcf11b6a009efd4becfed098d9fb297e3ce41c225410cc1ac2534b2025"},{"version":"92dc4e8b3d0e8dea1f5abbe30adfd3910a7be441c12ed6fadc738adb59f9bb2c","signature":"322baceb1c9f45aedb9e5100ebbbedd07a164fc03ab9e98c462e32b41cbdc90a"},{"version":"384f588ecd80fa9da15753c86111d3563d56538d719f8023902703847dc198e2","signature":"fe0cea76d1c6a718447bcd594059ac0a4c7f60b9452227e9bd6af7d564519f45"},{"version":"673b4a9dc7a23138c3dcb75f1a77cccf8d2a11167df9587286d6140b2c60499c","signature":"2a19b3b4d3185bf1f4436a1b8e98727005b207da50322e24f3cde25162e2ad2b"},{"version":"4ccc411eab7ab26ee65e6796dc137a43eb6d3145e6c616cc3fba32bd3901c240","signature":"6ae54612df3cf99e70e10a844f31d4aa1629ae828bfe1e915701d5dc1311278b"},{"version":"c37155416601c041802206333c2537b309d8031770da2717d9ebbc0fbc0f1527","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d870a6ebc4af392ccd2875f99d1dd4c90db167636e4b267de2e1d3ff14a6104","signature":"d02c70b928a2b77ba435d387595ed3b27e4466e3115d7b71a79c36c592238798"},{"version":"2f79a63626adba271a461cbaa34c976c26be8e474095b1bc8a80ce6e4fc68536","signature":"019c10e3a4d1413779d87a055ffea70d5dfd127b4b65a51cb2e20fca9e8f1f66"},{"version":"ef68d70baf9635137535142e9df63e515a9b8bdcf9906d6edaaf0e93313ac3aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1d17705658519c7d57ce6c5cf95f0c894c2dd754ad1fcaeb1e2c4207d54e6e5","signature":"0bdd8b0d06cc0910bf6a36f2bdf87794634ab7014e178c7a7eb928437c6e5b76"},{"version":"688842a137a6cf51df9153af0452d1099fa559ac47504178827954a0957f4d1b","signature":"b4617ba82469c6492e4a6309e2cfad3c4a4b8b9feeeabe26293e7b3559362ba3"},{"version":"a8419536a4ca141f1b73aaa6a223d024f4d380c94cf9a014f96592a17c6690e8","signature":"5ff75228fff32475fdc9bd5c7d166c2cdf98f66300937ea4fb7e4ff39f9d0c8e"},{"version":"70f9e74061da378a88537a5e6745381f00c470abd9d6e42ce93c6a7ae63bcfc5","signature":"ab7896a0010701f3ac6de01e3802ee76c98f7e3f658445a3d854394d1d9b8590"},{"version":"c8a733a097280e5ea476cacf56518883a0fd509d36b0f04e0bcc5e5a81b68c0e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"697256913297b09bb0b83b40de56ad875baa198a0d1c03b8bd9a8f8f77c2e500","signature":"bce98e573080ea97bd3c360011d50db0affee86bc74443866897c0061708072b"},{"version":"3b455aef3a8f1084aec20cf655ea99e1f68620df4c3f6071e8eed404a1c379f7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cf8a587e0092b7b11c83927802ac7efb7c40acf07276fe9f1175c5cc3ffacff1","signature":"2c542a6fdafe6885c0be90ce444b56c9f6871bf121ac656b5662e8c79fc96561"},{"version":"cfd8b3c5fe8e27f93868763708b0a25221595b18dae6f5a28c515bce83cd9715","signature":"76964f1fd067c7ecd79d2dc18affd81fb2f0148dce268546b64bb6cb0cad859b"},{"version":"2c17c6e842123c5c921ba98cee5bd3886f3eeffd42eb3011819cf99cb5b02ebb","signature":"fad4e252103942053bbd84c183603d06e19da7332de7d295294f368a69af0752"},{"version":"47762a84ce21afc46f46c000e87fbf6b3035b5944da16ca8ac62de576d877fdb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fa8dbed00530fb4114906cd93f7fb55512c8eb9551d2f2e9796c69a4da4b594f","impliedFormat":1},{"version":"5a3158e3ef43807bcc86f1b8d387e35156859e577cf3ef44f71aa2c3822e5b6d","signature":"1449aeeaf41373c6ebb083751de3477f2dc63177873dd65e1af39288e0f8108e"},{"version":"bd6e83a9e6d18e2fa74d9dcd45f986329d347eff178ad9923a2ebbf3fcbfd97b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"937a3521bff8fc032a57777777feb260c9ab218d266ac3d7723f7de32a48a430","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3858cf95d68efd835700eb41b1fdf881906eaebd35b07596bbd5b7c1c6fec6c","signature":"1671fc31a114078bc9cb71989c1919c504f1af4e0690995b055181a1932bc74a"},{"version":"339fbca5cf5752f3fa77eeef5ec37c42010f1549655b3796eff5f4747e419488","signature":"62fe02bacba35050e65ee17fa4bab71e61914182c3dc9339cb6d40ae242efb41"},{"version":"357afcfd45b1bbdf4029dc5107fbf70fbfb519eb1f7cce5c9d9e5dfceed98efb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c9d2066069488cecf420d111d0201193958022a2905ac6c66689f50ccecda6b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5452448d44362f60de4ad50c0c5eff76066ef5b1b9f2b4921e83fb50a0c568d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1f8647ce956bc178fa042840bd76b650e34d22b37292621c146c8fbcc4fdd5dd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5a7b42900c17657e4fecb9034c6bbd87a02fc402ee49415ae9cafdbe6f9d1dc","signature":"12ee1ad5a651c4484cbbdc6ea7d594fe1f8adc9684006988275bd671f510f581"},{"version":"e1f90140453b0fd52c46cc6ebfec2ebe754483c05e50dc74caae321d41e7a9ac","signature":"da215cd8311e3d53ac952d9a12e0fcebedf7d76b9f5692525046d7bb0ecb1cc5"},{"version":"5563052849dd3f93ba63a20568ac06acf6b8e15c0b57aee1bd452e7e4b1d5d95","signature":"91db33413af7e79f8f5639385fa3fa68c2595993f3f5bd6d7efaa4fe19dc94bd"},{"version":"bb2cef14d223750bf32f070eea09b2b2e176e10811d5c34f7a824628bad9dbc6","signature":"cc4068562a009b8285b75a2c53ea7b7323cc91785c59635e98b38256e80a2514"},{"version":"c5e286949fb1b24d3395196df616ec5f9090c2569534e48d1aa86e14308f6f2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"908b7b3f2c71140accb3333fa49485ce1ad10ac4e14faab10ed51c0c28c68782","signature":"d19e4f9294c2efb124088d0e5dc71b2faceaed9cb614ef974ab0c3b925374891"},{"version":"637c70ab565be71168064142fdc7fde5a58ab95425066d3c8a6c3c592ca7167d","signature":"5baac7ee5e50c4c52bf4905d5cf4f735c939053555b99a56d1b743630788f665"},{"version":"2178789bd22566bcaa973006fa541e2c70d5698b5c099831828c9a1ec141802d","signature":"b905f364397e04bc6a90718495a5af33bf9720262dfdf619a090e7164d4f5408"},{"version":"6fa0ea6916329d3aa5c6056e13512e1757edecbce26dae1e8e5a3334e81fbf93","signature":"98d26fed15c1969891bc73c9dedc7278cdbd15f3afe3e34efe01c27dde514ba5"},{"version":"dae66bf6a17992ce4aaa4a16b8d8c590e84c396a341cb70ddf61ac2fe710e089","signature":"173c629dcaca1da42db9c0a508d079657fcc0cc56db24103f8a8171294902ff1"},{"version":"e7a672c4cf7f2314673b2fded201b122b6b4eda779709e2cb235531e8fac004f","signature":"db1016666977bab29ab1854fb90c9ed76f0632bdf412c73c6fa81412a02bc5b6"},{"version":"8bb437a4f36353bd4f61c7d432a87e38fc13f7b17e2d56b670d8d9b52bb25c29","signature":"8f04114d05b5db969453536c2b5f0b92cb28745a7a03fd47f425146e4b9ad8c9"},{"version":"ad5ea69c890012a5b61d4cad41a2d1c2bf581a023eb58290c5ea86554184bae3","signature":"d73e7d9f551a968dcbd471ca03440ca263efb053defa3a163b94c429ac47729c"},{"version":"7b94368c5875f22f86e56257f96884f67d2b68b2f967b06f52e5b735fd7e5727","signature":"2367a890be9d6752275d2ec6b9afd812c0b856d943b32e51ec662d6aaf6968be"},"6958e241f880588015372a690454d0f7c0727b78e0a9882f493e2ac0fada857e",{"version":"fceca4d896e6fd11de25ba760ff482c087c3a2150da1d841b8092bf8e1dd812c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e685858913eebc48e218f3546127d9e35f9e61eaf110891876c589096b31418a","signature":"423a9884352dad4696190f73f7dcc7316a8b1360ab70811a6ba9642929940718"},{"version":"7deb2948f4295f5751846f0e22361a43fa92480461be9c0b118cb388172308ae","signature":"6011e7e17c36babf1dd074634babde201615d25815d9140579d346b903bc9dda"},{"version":"7885c9bbd5a3cb3082f0a05df1f9c448d1c39e6c1302e935f9f986bc479ccb27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2d4dfa9bb5bbafa31b4423a78c2df02ccb51ad3f4abe7dcbbfaeb8dcf2cb82f","signature":"90b39c231c33d05240cbaabcfc21d94f68e05b5d9d2e972b644363be2133bebe"},{"version":"94adbb305113a8e6572989713200d1eba425e7a01443f1d02f4bd9a66f7f4fa3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3f8860ee39a7c1ca7824f29d948eb3f2160caa4cf4b1df9380a9b9b0c1ea184f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"01637488bd4591490ea3957f7e0d5a0744299e13944168b0b29015e57beab4f7","signature":"9d9ba57804b618dfd2e432502395172f7ccde0e2543ba0633ae9405c50451d7a"},{"version":"10aa66ae348d5ce62f6447d5af00fe40adc1f961e8fd6fe4f331ee6156c547fd","signature":"22039e2144d09a04262d19b5f0e4237ee40bec8322b5c9574403a9103af52044"},{"version":"cbcd7e3639eae69860983466b671c160570ff8bef3295eb9d9cf3a918f7c3924","signature":"22039e2144d09a04262d19b5f0e4237ee40bec8322b5c9574403a9103af52044"},{"version":"2b7bd4c530f8df99a7c513289d15cc3d919182a3e47a509f7dd66f7c0c618c64","signature":"72347dfb5a68565183de9758ca357bb879acff2d8dd025002d023281dbc9b755"},{"version":"700a699bc316498b27b98820c837965a737debebb4fee5d0a027e95d3c4a1925","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a569d1174a24e8305c08d91a99e9f7f03610b40a3639da982b923f0fa380cc4","signature":"a693156ef4795aa6c9cdf5e6e88fd5a8aa3db1e770643c679f5f0bc652b6e384"},{"version":"8292f04350336900ed9f70ce80be9ff51b36602e17de450e43185054c9c9bc32","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b61da04f747568084ac75ba893c009197a7a0bb511ce6e8ea11ec3727b1e0bff","signature":"659c6cddd4e661edcbf460b40c7b690f346714057fd0faf27d1400d95cb6a398"},{"version":"5e92985539c56d5b665b392fd3883c103e0a83b63a79955d940547f494a87f27","signature":"d273813f1a71341c5f482788561acb719f12c65fcafb4f36423a6d409856d472"},{"version":"1e36aa6fd246d7240a3598e917647e1d2ca0380a1b7bb3b8e3945cb26941b031","signature":"9e2bb88f173d3209e25d8856088cd88006b416949bf633f766578ae5b18f8488"},{"version":"205f7ac530c6e5712a640fe3b0dd9f29296ace25043f7179ec1adb56882a1c37","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e9142cb5f88305d5b5db601df9fe78244144630b03316f24b123c2bdaec5cd8","signature":"72a4b4bcd25bb33acac0c8d83f0d4d198714a06e03c49046690f380b9736998f"},{"version":"735334eab2d97fbdb987695b8ce10bb987dc318c3dd3f76f65a2f955bc4dce45","signature":"eea48c3b3a4a380ccfec9ac95ea1f6535cc57b17efa2d13009895942af52555c"},{"version":"b6fa86562861ef430157dbc9d6913461f6bb416c58b7052efa28a7fd503a2e59","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34817c134a9a64cb3564c424f056a13554d6c40d31af05be7ea6b28cd9d0ac53","signature":"cb7b15b1e17883bae1ff4a7a2edc4e33d311a2addd22d3799520aca9c35809f8"},{"version":"a23c66cb088ab278c1b732ff0c39c3f072940848f593eb78807712292c9d4082","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90453ef456c9284945d132c4d1c29753bcc06b0b7b1df7910768f748d4630160","signature":"d4d3b854dee0def611af8377422b5caef70f3b8c2c2d10ee3bb9ffb97d51cd45"},{"version":"404d9825e0fe3cc10db20060d068fd4f33c85c245ec9d2f99a20d05b02291b20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3450fcaa834dde1d97160d430e46beddb77253f999ca2a9730a518cf8ff9b41","signature":"6e557cc6a5e01a72e3f8159ca0c3c0b55d5d85613540b6fab72f9c70458b6c4b"},{"version":"e04940a8b56da3de47e0cc13fc8343b6d61620cc5d888100e2bdf51826f1a2ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d740794e45fbbeb9fcf59f3255e9e6bbee5abf88c3e7736089e534fcb2c55689","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"354afe485d131f817329e133ea768376707f9e4041d68975ba6f8b6eb2deca05","signature":"6fa430bbcceaa6953e336c4592420298d31fe66327f7ca06e6763ec70c20240e"},{"version":"0d0fb8169becb3c35ffb1069d105e59d36e1152bfac10d47d122129c8b6ac89a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7a97cc4473384c86ecdd6b6c2d092db47c5420ade0eac940468dbfee5c21cf9e","signature":"f84c23548103c81ed53b1364e06c7c00104b7dee0573f4f7e2e74e8788222aa3"},{"version":"a08516f71cc818d1a07dc95126ecc36862a01bcb5c8ba86bf347a159c22029f8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b5d29855d82e8158a99ed976da509012e99a5fe1bc877009aff91627250cecee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc4ffeecc189d198af9ce492abed824b47bff7e7e6f8ec739a0eccc849836e4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"598e1063a09bc7bbf1bc527cd19769aadf213b151d89921fafd9eb6c74121fc7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9704b377c8700004a19f328ee8a4f7ec61fa305e24545b5621e562410a63e2ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b05dbef22d051726098dcbc6490886790bf7bdb93aa9f8a46403fabd59128cd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3db9bee2b4afd4032f6040039fed1022b127166da7f0143b4a8b69a8a85d264","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b290d4bb3bf8f9c797f36ed8543b50571df566dbfee6f4234c3d0e63d837011d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d0006e2c2a094ea0fabc4465b2cab0d7e8f5e785b3dda2961c2242257908b6e","signature":"a82c92852eb3872216a45757430fb88588440285e6f17c1bb864abe9f209fcd9"},{"version":"2f1a45e754761c4c17af1ecbd707a35ef9421ddb2daf244d1237aa929f919ba1","signature":"a76cde90a90b5582bffaa8aecdbdef0ee7d82667c57cad2c076404a3bcb741b8"},{"version":"5ebf1bcfa735477bf05c2a72f05efa171db37d28e39a690cc57d28447e09b070","signature":"ff96e4d1e720fdea29de66b9f495391d4c8c6b20fa4db88964df688d5a8538d4"},{"version":"73a0ee6395819b063df4b148211985f2e1442945c1a057204cf4cf6281760dc3","affectsGlobalScope":true,"impliedFormat":1},{"version":"d05d8c67116dceafc62e691c47ac89f8f10cf7313cd1b2fb4fe801c2bf1bb1a7","impliedFormat":1},{"version":"3c5bb5207df7095882400323d692957e90ec17323ccff5fd5f29a1ecf3b165d0","impliedFormat":1},{"version":"ed79d26639d1d98ab19d6f419180e5abe2f7fc6c194877d809282813888c98b5","signature":"46713144a8e07e24962b43c73b40a4f4b16e696eb52b8b519876fcd1f5e6eaf3"},{"version":"02c59c05069a1174a93fc2886fcd4ecfc2686711c1432511940f93d42f5a8dcb","signature":"3e2364dba15210b59a74593c721b4946e89b6cabf1c4852738003ee79509f4a7"},{"version":"26a7fc2c9efa90591c196a780ebb5940a3cfd7a74245698b2c0e648986755e76","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef20d9124a12e824c20d04362452ecbbdc6c9f3de2c3c4b231222870b9586e27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"02ae185acd25001f4af91e9f275661d7d284ca994374867cc564ddf22f8a6082","signature":"ffdc10811704d4d944dd41a35e45ce568ee973c9ac0d5c0ec71fe098829aab6e"},{"version":"535fb697e71bce5739129ef269f852ba83a2eea358ce8ca090f4b1cc905af9bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27cefa9a8df763b7c4e3abc76cb9867d1ddad908ac8c8d1e2bb32c3838616d4a","signature":"ea71b9399fcc1d3c46ec554ae15f397c40bf146ca2d9a58374cfb7116c343ab1"},{"version":"d1520fdce7489a3ad57359fab13c79ddc0a2a6d743940358a4dd3ad8d959fb38","signature":"45b074b67e77cbd4509dbcb1d78e40925cca1a0e67ff79fed1021bc48c262eda"},{"version":"c65bec5967ebb52be456a4fb70ac4cd92ffd671aaae4661cde2062fe3117fb7f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5","impliedFormat":1},{"version":"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec","impliedFormat":1},{"version":"08b2fae7b0f553ad9f79faec864b179fc58bc172e295a70943e8585dd85f600c","impliedFormat":1},{"version":"f12edf1672a94c578eca32216839604f1e1c16b40a1896198deabf99c882b340","impliedFormat":1},{"version":"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a","impliedFormat":1},{"version":"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53","impliedFormat":1},{"version":"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c","impliedFormat":1},{"version":"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904","impliedFormat":1},{"version":"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b","impliedFormat":1},{"version":"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262","impliedFormat":1},{"version":"4fdb529707247a1a917a4626bfb6a293d52cd8ee57ccf03830ec91d39d606d6d","impliedFormat":1},{"version":"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206","impliedFormat":1},{"version":"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e","impliedFormat":1},{"version":"8e6427dd1a4321b0857499739c641b98657ea6dc7cc9a02c9b2c25a845c3c8e6","impliedFormat":1},{"version":"58da08d1fe876c79c47dcf88be37c5c3fab55d97b34c8c09a666599a2191208d","impliedFormat":1},{"version":"e770447d49d5c7ee25f80ccfff0f95003e08bf1147d039f0e8320d95d882c76b","signature":"399eb8b682bd93241cc96cb483306f8634ba94bc17ddb123e9106088240e9c7c"},{"version":"15ba1669f8cb8433a7a7b40422f81fed4f7e037e3cd4ca65b7b4af0434a43560","signature":"4f83f97fe204009c8bbad58d06e956970062930bd694b7ecd88d13a6f85f7e3a"},{"version":"a18970969188e47a48af09738dde83579f9c85bfd731675b671c1f32c5bdc134","signature":"f6c3f2c52494a1c44f58bc28dc1f8f89c7e3b0d005a5c3bb8789f82131996dd5"},{"version":"68ec8a37a3f7ce830a6be8e0ed448f8907f638e02a22a12a0f76a900d9f7b258","signature":"37f7acbfb476967d35f33541ea813afd46bee4432026b3d709b41a0fa1b0168c"},{"version":"e3f54d1b036952cd9973fd6a6e68b5de9ae9620cbdfb9a421c5362175891b7cc","signature":"d5368ef391f2681ad969a5bb0d8d858701641ac38a7de983786492d2e5698ba9"},{"version":"d3f39e4c09a88118ba35eefddfa580674f5b89df79c1477298d4c4de0bc8ec35","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a2ffbcac73b1af75bb70a1fc540f059a227257d9746279d67af5698e9d9c0a77","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e70993be79de2ffc2132f91126903db8573e68b0f5318ec48eec97a5e09c5f8c","signature":"c4c000f5db2334ea4e2bc0b9bc437d27c292ba078ea53202378b878846840865"},"9eed204f26aed45ba513a001aaa78dffd4bf0194ed42fb59fa4a5b48dc382767",{"version":"bbed8132ccf8ed24e09b7a0c103afe746ec74c3f6d497676ce9a2b09a8e0e4ad","signature":"e9805c8a045ade45cf5dda8406be734ed77bce51fe25e6a431345e403964f502"},{"version":"1b046683cc56fca31919c8cfc9a7b47796d986b2df18c1e55615f7f67a464c0a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"954e64b65c8632c8e6c602f86ddb7a855b541f719153c52586da47df81740592","signature":"5747318e625d94d50968119db96e4e9b57f386c0fce3b015e26a5e06819ded72"},{"version":"8e67d08427faa2cd614ffde8279aca632928a75610fab7f0e80eea0481c3ffa0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4dd230e5ffb901e4d715a7507fb671f3510cbcb3781701177e09efce8cf30c6f","signature":"7a3b7f911a6906b2fd8d38f7347bc751ff290914c35f2998438f2985dcea418b"},{"version":"d79917970e2012fea644dd1c3d00e7499579d4adfdd3628bc4d4153c2fa38d2e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eaf4a58ef586c168ad73bf0bf2e4fc7b50b0207046cb03e70f064e23cc7410a2","signature":"dab2cd6f392f32d0e94793582857c06a2d1fc79ebafc631e2b80e4b0c40778c9"},{"version":"08e69d30c063db86ea2221adf2282b1c118723cec16131cbd40024f3f43e5a90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"80ccde47e35b2546400135ffc69d55feb65a4a473964a40cdeda39bcfa10aef9","signature":"2cc35ee4dd1c4f9d97475451cc25f443a692f68f9bc47fb0044f009e356da599"},{"version":"a2d8e5740b1d7e274651ad4e68fd99942d7b33d67adce2d3ff8b976d12327840","signature":"d14e729f535d0e6d801090b439ff6f73f8ae7d713de7468a36d5989f0f10f19a"},{"version":"1d57813ffd927563821c58c16a5a7c35d350415b2b8de5978b370c78b8a750ff","signature":"d5d64072f36683f1af5cdbc66e7ac58d839b6b2d99cee1b0e96df9f4413640a2"},{"version":"70fe6d07a4bad7a73b493a4bfbe2c5b501167449f0e95e3a896261e08d647b67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2a312ff9bf104a2617f538482d33f951d2de095a79bfe2f641f52ddf558a3c5f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7d2792a15bf4bcd948e330c3cb747a075d137db57ac53adc6900f69009dd8978","signature":"fb6fdfe7ee4e1c16d6bc8b3c8da0d22ebd365981b6c4dfe881b391328d68f220"},{"version":"75e64a7fcef4db0c9ff13acc31c53cce109194012351733ce9833347e0a8e518","signature":"a97e6b4712135857efbdd73004c551d3a71d65d6b8a9d8f661f608a47b607cf3"},{"version":"a24154a3954030448c58433c23ca4f6d78e763a3af035de3d9633cc9158d7038","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"72fc4be3f73ed954fa04a52ebc5975c56b5f13c4265392191c95028ec27daab5","signature":"13a8b4fdf45f95814460c5001fc04194f85ca7055d460a9f852eed3fbd5c2293"},{"version":"d3a3a8fa4cac4860d3fabd83dbbe072bd0db08b6dfc5447fbc3f65a480bbb896","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"773df341514640879d77b0b24b636e6a8ccae2e88bbb09cee7383274046eab2e","signature":"3f3e3ab94baceade05836e0805fd32550fc1cad12d3d31a2fcae6d56882ac2f8"},{"version":"2d96663076cc7fea06c11a0165be63c11b533672c6d02ef361bd86f8394ecdb2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95f053ad6f9e8f22fb9a0309e14a768302ff5f8072b9bc24b8decdcbdaaad0ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc4cc5cacfa347d15035093ecc8a2c650968fd9208de260c8c141749d1797d23","signature":"8efda6ec7129eb4762df1d2b2a593fe59c69f1a2d5696d6d7bddeff50c24b17d"},{"version":"de7f6eb89010bc7d22b76bfd8d01ebdf803df6bdf7e7b7528d2705f74c401e58","signature":"524d6c27b0e7b81e021da931ddfc29e60f33e2573ff117ed95e8cbeb32f5c8ad"},{"version":"745615f591324c1ce4fd8a905b5af838474e781548807dff21154e64b51e945d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a1a2e508046cbc9709255c938bf9935cbffa6cfe006cb5bb7f36b9f4c5a3a2db","signature":"ecaff6497b5a358a301ee7363dfd9c78325e9cb23d95bcc873322faedca7d3a7"},{"version":"d09eaa9c4d651a351d0ed84a88a22b35bd41f307ff7aa0fc356a2b7ac41ccf25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"26418c7b2f5a9860191966b63f2761e14f52fcf8b0cd040bb94b9623f348b9f1","signature":"01762a4e0d1830ca6bddf2667adc23596b265be66f3b25b96193103aabe080ab"},{"version":"c2882b00edded3f8e7bdc5f2169866bd6a1c0402b77684c4eba188ae9801e6a5","signature":"745546f965215f793614c1212548989fa75b322859fe83033f26979cd44e5722"},{"version":"8d3496abc1d78cdf10e4e6066233bdef27a1a00c40796f8cf8a5a34ead5e3fa5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d187fa89b22ca44e8ba47b4eedbb5fbfb1efeae4d14f6c8756b751067e4164ea","signature":"ce13f793f9bda7ea5885e373ee25d9140369d5eab45bdea4eb8526ea020257ff"},{"version":"268e798c4155b57314f6fb0ac3ff224e049c5fd8dad1619771328f77cf66e958","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"998e905a8579d49c9ff4cd1dfad86ed6608ac548b240d2ddbee896c013e6dd34","signature":"3e4b13cf490d9a92245cbc3e5477dc486352b38942d390fbafe48c4d9d226d1d"},{"version":"cb96c5f2b7a4c06fdfeafd2b2bb52e5026215b5f9c81264a502b58372d393c8e","signature":"36eb3a3f2bb64aba7182a39a6360214c084660224ab8c23e3a9e293d79065d37"},{"version":"f247ebbdf2915957f47462152c65fef96da9273631b739c263009db4efc13b4d","signature":"7c4575057d5b4dc088c1e7f338e1075351c0cff2877360a00f5102baae76dd6d"},{"version":"ca140e99f367b2a1a666b6f61ebb703d059f3b81d59a710058b6606dec01d72f","signature":"31c64d4bfa7de3f3b9ae69741ad29192023a49595897e764d62fce0eb931023f"},{"version":"265c9ae2b7a62781e57de439be00ccb1b8693156cfb98a0618ba6c5c54596e42","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34330c5f52442c69dd7c50d7a95912d87b85cc01be135026a5d7ac060b184464","signature":"720e771373458011bd56c0c6bbeea34302eea42ccf08c8a6b5840a338e7e93b9"},{"version":"e7f0547a22cdcb3e5d9b0fd91191cc2dba8f75a2694eeb4d45a9ddf2a8352960","signature":"c1f5f74ae95ba44d64781ed79486fe7192478040d82d787876f44bc7e77418b2"},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"eb9271b3c585ea9dc7b19b906a921bf93f30f22330408ffec6df6a22057f3296","impliedFormat":1},{"version":"aa4a927d0c7239dff845a64e676c71aeed2bbda89a7fb486baab22eb7688ba1d","impliedFormat":1},{"version":"340a990742a00862049b378aaa482b5bb8323d443c799dded51ce711f4f8eb51","impliedFormat":1},{"version":"89eeeebbc612a079c6e7ebe0bde08e06fbc46cfeaebf6157ea3051ed55967b10","impliedFormat":1},{"version":"4c72f66622e266b542fb097f4d1fe88eb858b88b98414a13ef3dd901109e03a1","impliedFormat":1},{"version":"15d8dcd70d6cc6c75476a75ea83c53df1115bdd551c73ef2168a9b4a4bd55a51","impliedFormat":1},{"version":"2acad3ae616a9fb5a8c3d4d7bb5edb11d1d0102372ee939e7fc64359fec4046e","impliedFormat":1},{"version":"c812eabb7d2e13c8e72e216208448f92341a4094dd107cbb0bdb2cb23d1a83e7","impliedFormat":1},{"version":"f734b58ea162765ff4d4a36f671ee06da898921e985a2064510f4925ec1ed062","affectsGlobalScope":true,"impliedFormat":1},{"version":"9619b4a3db123eee6912ce9cbeae535739a1b1736dbbc224a697a2a98fee560c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9c5c84c449a3d74e417343410ba9f1bd8bfeb32abd16945a1b3d0592ded31bc8","impliedFormat":1},{"version":"a7f09d2aaf994dbfd872eda4f2411d619217b04dbe0916202304e7a3d4b0f5f8","impliedFormat":1},{"version":"86ac756569f83cf0571646c916b546634652e92a775e964304912ecafa81dc42","impliedFormat":99},{"version":"a7f23fecdccf1504dae27c359db676d0a1fbaaeb400b55959078924e4c3a4992","impliedFormat":1},{"version":"bee66a62aa1da254412bb2c3c8c1a0dd12efea0722d35cc6ea7b5fdaa6778fd1","impliedFormat":1},{"version":"05d80364872e31465f8a1eaf2697e4fc418f78aa336f4cea68620a23f1379f6f","impliedFormat":1},{"version":"7345ba3b9eb2182d8cdc4c961b62847c3c9918985179ddefd5ca58a80d8b9e6a","impliedFormat":1},{"version":"81c4a0e6de3d5674ec3a721e04b3eb3244180bda86a22c4185ecac0e3f051cd8","impliedFormat":1},{"version":"39975a01d837394bcac2559639e88ecdc4cfd22433327b46ea6f78eb2c584813","impliedFormat":1},{"version":"7261cabedede09ebfd50e135af40be34f76fb9dbc617e129eaec21b00161ae86","impliedFormat":1},{"version":"ea554794a0d4136c5c6ea8f59ae894c3c0848b17848468a63ed5d3a307e148ae","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"9b048390bcffe88c023a4cd742a720b41d4cd7df83bc9270e6f2339bf38de278","affectsGlobalScope":true,"impliedFormat":1},{"version":"c60b14c297cc569c648ddaea70bc1540903b7f4da416edd46687e88a543515a1","impliedFormat":1},{"version":"efcdea26e9115d5c05b3f4c5827fe3b32b4fef1b59dbd67f529c6cb685c7d9c4","impliedFormat":1},{"version":"bb0c361fd2b4bdabbf1307f1a61fd14c953f2692fa642391f93276f2df41de50","impliedFormat":1},{"version":"90588fb5ef85f4a8a4234e8062eb97bd3c8114dfb86a0c67f62685969222da8b","impliedFormat":1},{"version":"6ce50ada4bc9d2ad69927dce35cead36da337a618de0a2daaaeeafe38c692597","impliedFormat":1},{"version":"5fbc333346d28f290d42ac81cf16e454fd3947c6e524384dfd3ce59d4ac3af04","impliedFormat":1},{"version":"072163fdea42ece03bd323b907f5d6acf575a34a9dac4620e517e4378d773d0d","impliedFormat":1},{"version":"df6251bd4b5fad52759bfe96e8ab8f2ce625d0b6739b825209b263729a9c321e","impliedFormat":1},{"version":"846068dbe466864be6e2cae9993a4e3ac492a5cb05a36d5ce36e98690fde41f4","impliedFormat":1},{"version":"94c8c60f751015c8f38923e0d1ae32dd4780b572660123fa087b0cf9884a68a8","impliedFormat":1},{"version":"db8747c785df161ef65237bac36a7716168e5ebf18976ab16fd2fff69cf9c6ce","impliedFormat":1},{"version":"3085abdf921a6d225ad037c89eb2ba26a4c3b2c262f842dd3061949d1969b784","impliedFormat":1},{"version":"8e8f7b36675be31c4e9538529c30a552538c42ff866ba59fe70f23ba18479c5a","impliedFormat":1},{"version":"1fe8b45c1564eca8b1bd27d427d193ea8c1a5d64f7144a5a64665d5d0f27a9e4","impliedFormat":1},{"version":"a03c6f93651e458531f223d52eac1a12f2aee8adc2cbc4b4154a3fe515984e5c","impliedFormat":1},{"version":"8d05dbd747569cb1b0cc2ec1018a3378c47d803de0e7d34f7e12909ff48bb437","impliedFormat":1},{"version":"1afb31819f4b7d04f4089d575acd30854a4cc614baea960066f7cc5755e9efcd","impliedFormat":1},{"version":"35cc30df63b9fa7c9d3637ef315eb5f21f5b0dc0f982c736cad20d39e29b579c","impliedFormat":1},{"version":"c5b47653a15ec7c0bde956e77e5ca103ddc180d40eb4b311e4a024ef7c668fb0","impliedFormat":1},{"version":"0528a80462b04f2f2ad8bee604fe9db235db6a359d1208f370a236e23fc0b1e0","impliedFormat":1},{"version":"94153ca0b430f575f45a5e07d66771dc5ab331af7791691855ba3499958c4e49","impliedFormat":1},{"version":"dd361fe00d3033451e4a43c9eaeafcd1b9b6777adfbc8b8f91d63ea56818c31c","impliedFormat":1},{"version":"b86720947f763bbb869c2b183f8e58bca9fa089ed8f9c5a1574b2bea18cfbc02","impliedFormat":1},{"version":"fb7e20b94d23d989fa7c7d20fccebef31c1ef2d3d9ca179cadba6516e4e918ad","impliedFormat":1},{"version":"8326f735a1f0d2b4ad20539cda4e0d2e7c5fc0b534e3c0d503d5ed20a5711009","impliedFormat":1},{"version":"8d720cd4ee809af1d81f4ce88f02168568d5fded574d89875afd8fe7afd9549e","impliedFormat":1},{"version":"df87c2628c5567fd71dc0b765c845b0cbfef61e7c2e56961ac527bfb615ea639","impliedFormat":1},{"version":"659a83f1dd901de4198c9c2aa70e4a46a9bd0c41ce8a42ee26f2dbff5e86b1f3","impliedFormat":1},{"version":"1db5c2491eebd894eb9be03408601cddfe1b08357d021aeb86c3fb6c329a7843","impliedFormat":1},{"version":"224f85b48786de61fb0b018fbea89620ebec6289179daa78ed33c0f83014fc75","impliedFormat":1},{"version":"05fbfcb5c5c247a8b8a1d97dd8557c78ead2fff524f0b6380b4ac9d3e35249fb","impliedFormat":1},{"version":"322f70408b4e1f550ecc411869707764d8b28da3608e4422587630b366daf9de","impliedFormat":1},{"version":"c4ef9e9e0fcb14b52c97ce847fb26a446b7d668d9db98a7de915a22c46f44c37","impliedFormat":1},{"version":"0e447b14e81b5b3e5d83cbea58b734850f78fb883f810e46d3dedba1a5124658","impliedFormat":1},{"version":"b16a6680ef4108fb2982b1d47e7ce36a8b2c382cf76b3e1b500de70f0a62fdff","impliedFormat":1},{"version":"929939785efdef0b6781b7d3a7098238ea3af41be010f18d6627fd061b6c9edf","impliedFormat":1},{"version":"fca68ac3b92725dbf3dac3f9fbc80775b66d2a9c642e75595a4a11a2095b3c9a","impliedFormat":1},{"version":"245d13141d7f9ec6edd36b14844b247e0680950c1c3289774d431cbbd47e714e","impliedFormat":1},{"version":"4326dc453ff5bf36ad778e93b7021cdd9abcfc4efe75a5c04032324f404af558","impliedFormat":1},{"version":"27b47fbd2f2d0d3cd44b8c7231c800f8528949cc56f421093e2b829d6976f173","impliedFormat":1},{"version":"d5426c0e36296daf07cf2f38227907469c33a53473d9c2721d21dc515c5724df","impliedFormat":1},{"version":"cc03a3e284393b02fdb646931e8576f6dbe839a249d172eb3397adec80559450","impliedFormat":1},{"version":"3d94a259051acf8acd2108cee57ad58fee7f7b278de76a7a5746f0656eecbff6","impliedFormat":1},{"version":"fc745bebefc96e2a518a2d559af6850626cada22a75f794fd40a17aae11e2d54","impliedFormat":1},{"version":"199d42a358b3b73312d499dd04d9f855bf8ad492452765de4ef80b8cb6871cd3","impliedFormat":1},{"version":"eafa048ffaf72cdb64fa1d0dae49aa91280a7bb94e0b034883ae48cec27a04d7","impliedFormat":1},{"version":"593bcf66433eff881c9abb75d2e55a7403c57905aa61d818a616bb3c7f076b49","impliedFormat":1},{"version":"3f3526aea8d29f0c53f8fb99201c770c87c357b5e87349aca8494bfd0c145c26","impliedFormat":1},{"version":"6ee92d844e5a1c0eb562d110676a3a17f00d2cd2ea2aaaff0a98d7881b9a4041","impliedFormat":1},{"version":"b9dc36d1f7c5c2350feafb55c090127104e59b7d2a20729b286dab00d70e283d","impliedFormat":1},{"version":"45d3f1d53fa99783a5e3c29debb065d6060d0db650a6a1055308a8619bd6b263","impliedFormat":1},{"version":"a14febaf38fd75a88620a0808732cf9841afc403da2dc3de7a6fc9a49d36bdbc","impliedFormat":1},{"version":"6052522a593f094cfee0e99c76312a229cf2d49ac2e75095af83813ec9f4b109","impliedFormat":1},{"version":"a0ceb6ce93981581494bae078b971b17e36b67502a36a056966940377517091d","impliedFormat":1},{"version":"a63ce903dd08c662702e33700a3d28ca66ed21ac0591e1dbf4a0b309ae80e690","impliedFormat":1},{"version":"2b63d2725550866e0f2b56b2394ce001ebf1145cb4b04dc9daa29d73867b878c","impliedFormat":1},{"version":"e885933b92f26fa3204403999eddc61651cd3109faf8bffa4f6b6e558b0ab2fa","impliedFormat":1},{"version":"22338cc18afb909a95a6c55417f1a67db99badecbac1710a963f0bf63c952124","impliedFormat":1},{"version":"e61b31fd5fd627c73da6041d201c0bbd721170288381f09055cad4fcb2ad327b","impliedFormat":1},{"version":"6e2d2b63c278fd1c8dd54da2328622c964f50afa62978ed1a73ccd85e99a4fc7","impliedFormat":1},{"version":"e151e41c82004cf09b7ea863f591348c9035e0f7a69d4189cbac89cc9611b89d","impliedFormat":1},{"version":"dedf4655c327e9c5294a63d75764946308700825e8d8c1d4318a10602581cd6c","impliedFormat":1},{"version":"b83ffe71adbac91c5596133251e5ec0c9e6664017ee5b776841effe93de8f466","impliedFormat":1},{"version":"61ecf051972c69e7c992bab9cf74c511ecba51b273c4e1590574d97a542bd4ea","impliedFormat":1},{"version":"068f5afbae92a20a5fcd9cfce76f7b90de2c59a952396b5da225b61f95a1d60a","impliedFormat":1},{"version":"18d97e6b17d1196d88d4deb0e37d8edb7fbdd102ae8a5681f03e15030b6b2fd4","impliedFormat":1},{"version":"d7ded5d2060ac6a4404e6001a46d5a704e3f325f95e2cb0dc055ea05404c9cf6","impliedFormat":1},{"version":"99c88ea4f93e883d10c04961dbf37c403c4f3c8444948b86effec0bf52176d0e","impliedFormat":1},{"version":"e88f3729fcc3d38d2a1b3cdcbd773d13d72ea3bdf4d0c0c784818e3bfbe7998d","impliedFormat":1},{"version":"f25b1264b694a647593b0a9a044a267098aaf249d646981a7f0503b8bb185352","impliedFormat":1},{"version":"964d0862660f8e46675c83793f42ab2af336f3d6106dee966a4053d5dc433063","impliedFormat":1},{"version":"292ad4203c181f33beb9eb8fe7c6aaae29f62163793278a7ffc2fcc0d0dbed19","impliedFormat":1},{"version":"aa8e5ac3f73eede931d5da74ef1797c174b00854ac701ead5c4a7d6ce4a49029","impliedFormat":1},{"version":"f1a4ca3688d951daa2d7740da5a0827fa34d4a7709eed7b8225215986ee87108","impliedFormat":1},{"version":"08e159b5ef9d14bdd329457c5cbe181e84f13c4ff2546a24b9eb9129b0c71c46","impliedFormat":1},{"version":"f8453a3fe0fe49ab718357120bec2b8205e15eb91ff62eada60a4780458fa91e","impliedFormat":1},{"version":"06f186bb9a6408ef8563dbf17d53cbe23e68422518b49b96afac732844ddbaa1","impliedFormat":1},{"version":"525f9c06245b5b43b1237cfd757396fd7fd8090e5d6a4ded758c7ce17a04bf42","impliedFormat":1},{"version":"e46b752c48b3aec77516d23b5cbc0b85df78c740c058a822b43a32c958e468f0","impliedFormat":1},{"version":"f693b1fce39951823f128590c6c837b70f844b6d3746ef778b7fae7f1340338a","impliedFormat":1},{"version":"bc264419318f0b174b5dabdd465e1eddb82f872e899b6c696c67217b346e958c","impliedFormat":1},{"version":"6046bffaa17bbb55ffd62926a966a7badce21b27d6239ba0b569b8266bedaf19","impliedFormat":1},{"version":"9376cce4d849f1d6ad2cb0048807c77cfeb78cee6e29b61dcfe74c7ab2980e18","impliedFormat":1},{"version":"2e0dc55ea1ade444d285576a4ed7915834d4a87f71b147c38afdb877ebb0ad2d","impliedFormat":1},{"version":"4edfc4848068bf58016856dfeb27341c15679884575e1a501e2389a1fea5c579","impliedFormat":1},{"version":"0c3d7a094ef401b3c36c8e3d88382a7e7a8b1e4f702769eba861d03db559876b","impliedFormat":1},{"version":"1a3b915d24b2a26df000ef55ed356028dec11ff54f7e93a5c095c313d7016e1c","impliedFormat":1},{"version":"7e3a4800683a39375bc99f0d53b21328b0a0377ab7cbb732c564ca7ca04d9b37","impliedFormat":1},{"version":"b1b5e35575486918e155ef02d995598be2b5d8e729f857f8309ba0b76e14d833","impliedFormat":1},{"version":"8d87de8b839a017541ec1baec68292ddbcdfad0f2f3b5f2ae8abd61f06105cbd","impliedFormat":1},{"version":"7cb0d946957daea11f78a31b85de435e00bcd8964eba66d3e8056ba9d14b9c55","impliedFormat":1},{"version":"b3e441cdb9d9e55e6e120052fe8bf2a8b5e5a46287f21d5bc39561594574e1a9","impliedFormat":1},{"version":"0870e8eb0527c044e844a1d83127f020aa7f79048218a62b2875e818355f8cb2","impliedFormat":1},{"version":"38400b70ac70600c632ad498df2d956ed8ca6c6774dfb0ef69a2d35a9450df7a","impliedFormat":1},{"version":"abab86c01d001d0cc410c7aee59168eb09bdb7d6d9d39d3c0081b36235e2824f","impliedFormat":1},{"version":"7ae39872b4f4d38b9df079cce4223e999754eb3b3f90e4e46b978b29e72c419e","impliedFormat":1},{"version":"dc0f3099379383bf14f2263c7987584e81b6d9b60259c9e31390455ca0619dba","impliedFormat":1},{"version":"6dd704b0ba0131eb9e707aeedc39be6a224b4669544e518217a75eb7f5dd65c2","impliedFormat":1},{"version":"6effa89f483e5c83c0e0063df5f1d8b006d9d0f1de7eed2233886642424dc8fb","impliedFormat":1},{"version":"5c6dc17513298b4daac99bf8e88ad4e4a504310cf69a0cf3cffefa5912b85234","impliedFormat":1},{"version":"d43130c35762a80da2299f8b59a4321b6e64acfb0b11a36183379b4c7b83314b","impliedFormat":1},{"version":"6bf44b890824799af8e20c0387ffa987e890fac5c5954a3a7352351eefe55d5d","impliedFormat":1},{"version":"e61999c06ae79ec587c2e7db514a024d85732b32ee2c997bf4a1ceb2b561c611","impliedFormat":1},{"version":"aecd29a5bc49b1de6b933344e9c96384cd098162c46873673ffa1408e6195c52","impliedFormat":1},{"version":"f83afa274e0f11860c6609198ecca220f5df60690923b990ca06cae21771016e","impliedFormat":1},{"version":"af31f37264ea5d5349eec50786ceca75c572ed3be91bdd7cb428fdd8cd14b17c","impliedFormat":1},{"version":"86d01647c3c215e53729aa2cb15d7bcc2b049088bc76bdb5e04a0bf25f97c386","impliedFormat":1},{"version":"9d3173cf740b742d1048d8ab20469060a2b5e2d426f8ff7df36042e6829c4aa8","impliedFormat":1},{"version":"f1063f0e6ca22a9fae0c0338768b03911c954b8e6ad4fff5381cc6a964b34324","impliedFormat":1},{"version":"4f85d12a28937e950b123e5385448a3bce0f04dccbca7ceb8aef351ffeccb228","impliedFormat":1},{"version":"85e4673ec8507aef18afd4a9acfae0294bdfaac29458ede0b8b56f5a63738486","impliedFormat":1},{"version":"40683566071340b03c74d0a4ffa84d49fedb181a691ce04c97e11b231a7deee4","impliedFormat":1},{"version":"81c8ab81daa2286241ad27468d6fc7ad3ecc62da04b18b77ce9b9b437f6b0863","impliedFormat":1},{"version":"268755fe3b7dd5ca84fc043de1502c56c5cf8ef70271c017964a9dff8af94a7f","impliedFormat":1},{"version":"8e56db8febfe127a9142435940c9a5a1ad17ddb2b2a6d8e9e8984785a76db1fd","impliedFormat":1},{"version":"f1efa458a3f630de51e30c823a8e1109eadf8562b8b90c764ef1fd989329bcaf","impliedFormat":1},{"version":"1ea64554b23db011171f7e0dd59d006b239fd4ec2e7e8b31ecf995528de79423","impliedFormat":1},{"version":"46788ee6b4670d904a54d56fe9e3bb308ab4c4ba01a435d39d8beaebf85f1a54","impliedFormat":1},{"version":"f4f6e61f620861b576f466e8af34e6064a997aa93ad62593c0c3f51489784e5c","impliedFormat":1},{"version":"f92fe945f94fee5c2811d6ee81b1751a1f1970b29063907d48067f1c2389bc3b","signature":"a33394f16beeee3312796eddb1186492dc73074f2cb480027e2de871ddb32a1d"},{"version":"3974befffa7647e5d975081c15016cda5f16062c8957af8d5b93b85bd6b57b21","signature":"8364a4867aade4b7b8e12b3116edc4c0cc374833476df15a0cbbe7b147bb1387"},{"version":"da094bbe2ba0c875d680fa8957a0b4056d806ed8093c4eb84f1d1319bc148924","signature":"2ecfec679572556d5739697241ee12faf6d1c088a64eb646f358d6b908201893"},{"version":"884b3c4b6de733bea0363994edfdbc08f23168c3819ee92eacf9ee2ff38b9e31","signature":"8b18201daa2caa4d6dad664291f923d8607cf8211ebd0dec3986e400f02376b4"},{"version":"d61b3b8b5d54ffbc1159015019c05472841f9b12287ad1eb0febb9d50b3fcf2b","signature":"7bd1aae3ca5e15b45dc603fad958b8d228f09e8c43ad9a4efdc70c7b3f96fc35"},{"version":"4b9b77c14bfa8102fcb57b14ffe92dbff3b513a8c4ba62893ab009fbd4c73647","signature":"9d2c9cbb279702e44a3ea7fe24bfe19cf27352d4cbe4882bbe5d521d27c9741e"},{"version":"aee88de82317641d6391f0686ca4acceedfaae5ade43d00dfdbb2e32e83870b1","signature":"979a61915ecd6734d45f9ab06a423a5b75cac28c23c512c838c10e333ff88a02"},{"version":"9a889402f27da6ba13bcaf7e0731fa06758e971c0d4ed730d6b46f08d9a05f34","signature":"f9d6f6e5c3e8a1dbf9499c426fb4d97386c7aa5b205662a4777f9289ef9152ab"},{"version":"f6534bed93400a60ab02368c4a698062e31ce5ad4eefd0f4994c2385ae83c54b","signature":"9c2f866be60bdff85a59bf2cd9b85041d63bfc369560cf59b88d7a95c6072f28"},{"version":"b6ced0b0b07feec87098d3eb446bdf772cc268ee3ac4230a4069e61dbf75cfe4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bb040b86775b25fc0848d43e3cc03e1b5cf4f00c40cbd350dbce010c06404df1","signature":"6d46cfeb3c048590784e9c099f0dfb8de954141c9fbf25f6d9ec87981ebc6fe7"},{"version":"d20eaead87726a364899203678c3e81614bfc368630e7a38ed0008acf7f7c1f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a2958fdf63b7d83f8d734d08ab6975b2a66defa7d7ee4988c0abfec0881b3a3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7859b822b52969cf5421d8d07df464fabd68c67579733fd51cc994c6f3b9452e","signature":"91d869ffb8dc40ecfa1ed7675197ed893ea5501d5eab07a48f22dbe192cbd9b0"},{"version":"05a0701aab09b3b50154c4469670a6af40d716c1bd84258ab88c4486efccc2de","signature":"cd7eee6f9641bca037731468d9b1012d11858efb65ccb7a23e35377d824b2a4b"},{"version":"cef9a872724202d022975121422e03878a38b6c4a78977b7e277733a2ed5151f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2776443e3c5ce498f62ac5661d0e35884afea55b0d3f6f9306f8ffb97b35e9fc","signature":"c049b08ee071ee35f8623f69360d9b11a4e78f6f903a9601e9f76346ff07ffc4"},{"version":"18981392c502332d353be793e0eee6b4b71b92c4cc159879c76c0e412b50166f","signature":"85a5f8ec84196d475ea68d0239a7ee678d96c40274f16d0e940937166e2f9fa7"},{"version":"ef39108e7cfc70e36f0d3a795f5ee50fe39e5a9694626b9b88b3b24d505303e5","signature":"49497b292dab6540b4e9ffc2e066aba503cda20ff5407de0bd946016f7acfd01"},{"version":"ede33324139612cc144cb9ab0658d31f633fbbf6e5654b4867ad17964e494463","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90bdcbca0e0bf6c2b0bbb00673a340a12647ee9e9c0b7ffa08c1cbee51683f6a","signature":"081dc84b2cfc91d910771f621446c86f99ef16cba361a195bc5b6415671e3940"},{"version":"c3a6e46122a15e372681357101c151aefc21040e65611ab4edb7366b6694b2ea","signature":"84a5f8d870d0e3a83ea81b7fdd41940ea8af6ad244f7b5a41347a696ce8ee863"},{"version":"5c6fad27889b29555ec4270fa34f6d318520fc36e3c368efef6988c2240f943d","signature":"e569b4fa591b1a8f2e8bc65df628ea7b8314ea9e9fc4de877bae1db7073f82d3"},{"version":"75d850624f64a90b0709ea1dc2742d4b189c106098f94125af7cdfcbc9db0852","signature":"2bb5816f801dc67b86549dcb0c662be56b248c5dd1662c1042691c7329148f98"},{"version":"4e127bfcc9d5d93ab906153cc602cfcc13188847639ded607d4e83fd57838e12","signature":"f91e8b73b979d76dc70cf739261a03907357f4b464c2c2b57aa1da3ef60840c4"},{"version":"43fbe80cee30066d6ade0e64b13f0987cd6b23946ec6265728fa2adb27146000","signature":"017bbf6636858e6e607294afca49a452e39c06854a04aa20ad3850defd0025b2"},{"version":"77d37faa796f4c1b4eb99dd6645176245701ac08aa52886f8f7e3f340f3619d4","signature":"a615ebfd3685ac1590d379b501100df211539f37bdf6c544e83e35b14f69a057"},{"version":"753dc412c871f3fdc65bfea46ee79b435fabb41509238f866f6249d44f7c1dcd","signature":"c286b503f750f73cbf22d1031c189fb27e7d8a93ef017dc18d17bbe37fd5dd9b"},{"version":"4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","impliedFormat":1},{"version":"23cfe48b32326ffa0bc8a8ebcad5414bcb7c3f6b7a525b3cd30c2652370f237b","signature":"eddd8bad575c2203156baa8df91727157606bda7ae0d100246ae7deae963bc2a"},{"version":"cb715246977191dfd17c33a8b96ae65a8c62199f06a09d36501951fee268a28d","signature":"e922958ca59e7c52c6e0dcfe9748dab953af2b18d58d716884620d408935efe2"},{"version":"d6ad5279f8d296ebb50264aabcbd50a5296edfef7f23ae4c159579028935d119","signature":"48cf5a32fd77ba27774c29824c8d1bf27cfe16081169b1a013e0874292c3709b"},{"version":"6f2d007923eb835494e65dcc1034da47cf8e60aef0554d323273a59b8b8c2f86","signature":"bcb9686b97930d312e851d879aa0ceb39656e4e49b07b8aef72ec0eae03cb376"},{"version":"406af28178f025030a57332cb2a36516048ecab7acf102b84f1c1a84f09d77fa","signature":"aaeb521b6f9317f1358efeed044f7b8c9da2de643c9c444c86efa4b5974707ac"},{"version":"00e2552694e9ca66c48d911ae3a46b5ec592ceaf1aa11fc892a11ea68e8f61b4","signature":"7edc93fe90f8fabb25092054cd2ba3454b5665dc1470229cd49300d2787f9256"},{"version":"72a2cdb9a957dd93ac6c7a5ec584a41a2eac59df7693ee69a8cfa93503b7d8d3","signature":"385f9da32da6c7c156c4c273f1e08ab2fbe264bb424469850b65c9e6ffa9c9ed"},{"version":"fa8896708f7c899af3f718f77f46489b8d3efd15204184f74b878992dd516270","signature":"e89614e458edec1676ac424f0a893a6e87bf5bf38d34a8758b3e4823f0d2b48f"},{"version":"85b1d0061b1268cbaa7efeba177d96bac002d38d7acdffd7a023decbaab2ef7f","signature":"0fafd7d6cde3e37c7c4045f298b00355745dcb2147c0e8d0f1240fba867274de"},{"version":"bb5660a80ad6edc1e4a7831bdc38cb4f70adbf718846aa3bb936a27b62d742d6","signature":"e017494fbef6d93da248b22d25416f95d8412cb4a4e8cb12c7e64495f16de3ec"},{"version":"91d4ebbf20c7ce05ce56b901d34ac84f18c5de49cdcc8b4e2e79416bf5863a52","signature":"00aed0049902c591b92c49af96b5a8d1b3e202604017f34241bf72cf89f80756"},{"version":"f7cde16e51986d5a1361c4d7e36cb8f8089acd60e7b43b7c0cb7ec9d3c58bbb8","signature":"fbfd3cb405fce3aab2cc8b6c68371f03f340b5bedfb22d1a0b46408ca184aa4b"},{"version":"0f55704e7fce1025a74958ce04d7d099a3605ab1ba105c63b7fde02139a17eef","signature":"69652f240dac09436bdaa4cedabd63700a279aaa035b43ade48742fbe5b37d08"},{"version":"4d8d7e049c7a369a07b41963903b7041bd8c88560b55af2b4b6c4fd7be645cd5","impliedFormat":1},{"version":"83f6b233e11c9f2855f7f318f608570e9a45db007ae924278e7a581d7ef99b35","impliedFormat":1},{"version":"c450dc59cb99ddbba3191c8964bd40dc732d13be5d9436d3557cfa38417939cc","signature":"1fc7a196b7cb9628c96283d1c55177082524e81f5e607404a5ca9a1ff53e45e4"},{"version":"231a843f95abff5b70bf76ded015c4d7c0ff006544d27c9747471a495743c2ed","signature":"1507e471793e1215912dd1ab92c0797ae9259ebf7fd0f3146e2bcee42b776bc8"},{"version":"deb4df42f640706245617d22c38500d0d24e34689f405224004477c47b30a287","signature":"84225d531b0d673c7dee0a7abb7592e937c207fb0393e85d8e9808505e415642"},{"version":"05196723f295c088f92bd93f9edf2f9d72a0e6fb7a468f55aa270214519857a5","signature":"1c7c3f06b140f7f31f69c3f2a6f87659c1a487c552bc733f9de6ed963779a17a"},{"version":"6326a3828a9427e88b8f11ae4fbe842af33faaf0fda37cf710d8f7a8f76fb912","signature":"a0b2ed7ed78ffb63bdb8c45c49596bf2792676cc3c527c599be027c2d772c840"},{"version":"6f4aaa2857e98b57b5f3239f2b05d4c0010dac665952f9b57c2b11f4b93ab550","signature":"9813abe08f8dc60f627701e1576bfbbe8498fa01840b3500ef120ffbe3ece69b"},{"version":"babf8f17c539cd8e5309393275eb17fa2a6790a848f9b6736e3e75b69ca12ae6","signature":"ee79b4e030d4b005413044e47295b78001ccb4849995c4dc59e42e65c509f21a"},{"version":"41ab97f834dc579a841339dd2d5ce54bd7458891414c624cadd12c2ed7985cfd","signature":"d703ffb3cf86f2e1cf7460554b6fc0a3a0eada0040fc48aafeacca14bffb7ebc"},{"version":"cb262ae73b7b864a9cc5e62142dc12600f5afddafa458e6c26218259d5ff67d7","signature":"433e57f0df48dbb4612309330aee7b075651c0ba5d29c483b17bd92e81cad910"},{"version":"f060e1946eb32ff62b101bbac21a6cd02835440c0892554566d0dde5d4838cec","signature":"036240f98ae8d5e07ad6f648996ff0630d6112eaee5b53fc3b309a1acd7c0721"},{"version":"a8f8ddbbd5a595a3a45b89108072fd7c11afcc5df839f3b2d234ce93bf5ba511","signature":"621d7479105eaf0b7002459dd4a7746134df8f621a6d9e62ac5c69f4b73902af"},{"version":"25a9445362108d35961825c730d1385aa52655c253523603fe3a514699a08308","signature":"0a82088daf1f69f93a6f03b7ba430d6605a8b48febb578e7ecd2c3564b8d235a"},{"version":"8ac5cd94bcb63ff4ace80712b0b2da1d8e3005c15c6fc2e96e3964e2cbad7221","signature":"6e0962e848047bf57be651109d9ee3d4e499277d114392e6efdbf60a3863fec6"},{"version":"b92e316d7caef01a7d96aae2fc81bac3411d81aa08c08369fdd79b79052d0804","signature":"a15f6b3477115f885bb24033267a6e06e889bbc393c5ae977513f0ef2c29efdc"},{"version":"883e6a16350e6a237822deb193859ba6f80f68b5bc63d37932eb5a222afabcfb","signature":"4e4f390cf28f71013350ead1ba25290872b936b31244feb495c7da040c655c54"},{"version":"20eeac8a87d7e85f13f2ce118073cec7275054be646bd47823f1e9cc8951ed4d","signature":"9f2d02e65e22f5bc32f727fb091f17315fe58a8792d8280ec59ab072272e3376"},{"version":"88f2985b43e7af3d4dbcba54e609861fcd28cef3ee74ca4d54e82917a9165b30","signature":"2e54daabbe58c730286e014d2bfe4a80b6d533a2bc9c5ab6fb1e3e654d3a4872"},{"version":"417c3d98d4efb99cd7f3c683c2caf02ae28758f18fed72ae0389aecfdab29878","signature":"2a39da52aed89ee43bf5dcadf72fc7ab5d16b8dee17ff890bf0ad3b72a0320c0"},{"version":"15e9ece6b9f5f2ce89f2ec8a96bc9303b35f07374b94005eb2443efaa0c6a49a","signature":"46676fa7ca6a5b6552a61d40d41f41eebc81cf838c14933cddd35203d298b874"},{"version":"0aaaaf9e39d6225f0fcce6949faf7254a473de642dd96f1b6cf5501b87347546","signature":"1f8e872ea16e6ef3029e47f25725a22c286734fcb4a88ea2e13c437e905f0c21"},{"version":"735af8f14d6e3866b5863742bec289903bdde848ba8754fb628afb54af74d5bf","signature":"6058d5942879388147f8aa5e9c2713af05d0f1680d7ba91d1999b97dc6b5ca01"},{"version":"69e1aa237956759cc510e91cf22b8b81ed371fd80d864593b86acb355e43795a","signature":"29df4852e710dfc4ccf300cbbe4f3ed1e109cc09bc55d540eb40bf2ef0906d0e"},{"version":"e49fff7bc7242932d50b6f1d894784704a65bec211535046a7b7983891e30972","signature":"aa5b770dd1b4e7ce9fea3c83330240ac673f9913a0535d02994bc0511eb85cf9"},"98ebfeb0805807ae08415404af1b664e76353e70e5e71e6f086c7ba264b76fba",{"version":"fd79331caf4d1c981f82d179a8f8ee1f5f9db5485b5960d2ac5252ff91ba195f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"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":"b02c8a9ebc617e95159a1d928fce2fbc345f3e9ccc9f7f6684195d8f8d9bab5e","signature":"45e169847975d5baedaaa5fbe3da4bc92db0b90a305f2536491b7a4a2d262341"},{"version":"963c32aa76aee7050bf4b5749bec7c775046f03bcd791d54992b3345f7b80e2e","signature":"66be142f9806a1a6a875064f7a0416e1ccedcdcf6a9a209b1c633e475b975dd2"},{"version":"9e473cec8b5dbb77baf8db593da0a943701f1edca3b3b1ac81af9ce178dac9cd","signature":"219dfcb98664c09e2a901a0bebd0a1990dece13622fa81b99a4fd16e6352c936"},{"version":"10ea972b401fc77b7e35429345f02bb02dde34fc9d7d1fc3232a187f5b52facf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b85f8b41e677c1e4e486b8856355198d5266556bef1fda2f1b0cdd23aea308f5","signature":"f25e6f98b3f2f28fe6ef1e8a2d53a260e20c60f99d311e57251ab5dad00a1e3c"},{"version":"eba4d3bd1b2ba75f33dec7019769e75d9cb5ab19609cb3181e08ee56e1c8fd45","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1ab7dfe2e40a14457c44447646438563ffbf187e60a175f258af4189bb414e9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3616c07b4b641b8456821754d23d7db606498fbf52d04c97fb8cc285ccdd3347","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0d29ff571f05d9f1c9ecabd53c10cb9bfcaa313b3b64612593bec64745c4d224","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cd5944f91eaf3e04d8c66d1c7c44508f932ae86fc033403193a81a0e3a95e53b","signature":"2630a9fc3e9a2205f1df08e9d39ac89290da5a35ab782d1504364baa70c67104"},{"version":"3ab2b6455439badb3d984aef6d2519029dd8595f19f614654072798269598876","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8f3b78fbcee37a708acd2a86f1c22645cf34b444cfd7459be341415228f4b63","signature":"14fe776ea9f72086fe119d5df096c39513d6bdd3ba1615b8d9f5cbce35933f54"},{"version":"f14799e6e43275054eb876159fdcb6c55b4e76808911ffdc9f81a2e3e5baa564","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aeabfd5da8290656189b20d20600d0df6381dc3881c381b815807e9fb745f5d7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3dc95ec98d2db484ccbaa31a47c2633bd619a4d86fd655739ed248f081f49f07","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"647f3c90fdbbd31bc9f337d7674f74f40f32c3adb60bde42ad85adb0de922b48","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9ac8c2249f0a97698a155031023e87eaa74c871229e36b51c3c83fd1a0bc92d8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35d7cacd428e674f89a928ed99f34ddc7c36958b395627a9196a8ba22618a29a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c51ed8506a74e96ec467047bfb51a97c31209bbe090f330fb29306a19cdba765","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69a81ee2b65949687067f23e7d5ca0fec2110620ff93ca8b92bc56740340b760","signature":"bc160a1badf1d1e0e9b92666b4848dfdc428b553d00ac5d2304b7ad80ac4cbea"},{"version":"5f44765c75000e8fea925fba6c2ba696386103cab9d813e72070cdcf45e1f804","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b11dcc6b3a1e92851fa7626c01c543833b96a9f37a29d80de6f11b320b626c9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"63e34e9210807a4d1af057003031a6689dd3295f8f2524ae7597ab27f326335c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f825e992a9f7f8576702cb198f6eb82c0fa45188b57991f5d23ee5f0bd16e85a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad7e4e17c67808e01a84f46591f2d4b763173ecc84a4863a727b7058b5786945","signature":"b7902bfba5bc8b901152f1ed5f8d9c2cbf2ba2790d351e5e5d61e00bdebbc624"},{"version":"3e5b494cda4d0ac2bce024928f0eacc77f8d9e4ac3d51eadde967ce542efc27e","signature":"b94b0bc72e5a26387c9314dc4f72106bddc0e5056469dcf4a43a351c1523c49c"},{"version":"1c230948c8120cd778cb258a0b70eec899b458db58229de16a2951a7ebdc57b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fef5a24cad5c4948eb776b00fa114e2560ce3dded3abc27db2e2b2b66832331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2cef84bf00cbdb452fdc5d8ecfe7b8c0aa3fa788bdc4ad8961e2e636530dbb60","impliedFormat":99},{"version":"9e2f5dc3da9d83bf4a0a9e5d39d8c9918482d586e0c403a44021e4ae7662697e","impliedFormat":99},{"version":"799003c0ab928582fca04977f47b8d85b43a8de610f4eef0ad2d069fbb9f9399","impliedFormat":99},{"version":"a62e448d3f09fee63ec1230acb23fb54f8f6ccf8d6f0001c7b94fd51594b7c9b","impliedFormat":99},{"version":"5366549884acc57185eeeb64561c2060af008230a8ea645f048f747cfac6549c","impliedFormat":99},{"version":"cd3229a2e4ca10207178e22f215c8e196c837254dd34ee440612a2a14993ffc2","impliedFormat":99},{"version":"73b7e3d5300ad64f9231f5bb145fca4892574d85e2d1a015ce095628f16915ca","impliedFormat":99},{"version":"42944c2dd3115e25cb0aa77aa05fe9e3d0f8a3b4ac251896cc680b7be41ad60c","impliedFormat":99},{"version":"7ed8ed496092801dd5f25f39af223ebeddc97bd64a7d9a5621f790dbc836eebe","impliedFormat":99},{"version":"abc549dfea982be25e0379cdcb6ef2aa6716b0013c11d8d6b14c814cc955d7c8","impliedFormat":99},{"version":"0e6ba4003cfaa90748b69ed0dcc9f99299d1af70f4bd835a872e52705b0c850c","impliedFormat":99},{"version":"3decd4c8e355126e76c9a43cc7ae08017fbcf1d766b204d696ccdfa5128de1ac","impliedFormat":99},{"version":"40410f51558d0b3d635584333fbba6b58b4b7f74037f59a08c0577828539637e","impliedFormat":99},{"version":"096350f9446ef08832b935d4a97c66f74a9133faebd90a40a12abd5f8bc7eab2","impliedFormat":99},{"version":"26baad6aa356ef75b2e1ee150ef6325988be9700bba14249f9e8d0f66bb36087","impliedFormat":99},{"version":"237dd4f246265a3efb18c3d40f54f98336ba2c329a9f9e30b4bb0f1a27baf324","impliedFormat":99},{"version":"3fa62b954262157916a65b3dd57faf6cfec7544579e673204da30eba00852543","impliedFormat":99},{"version":"8ab0b13972c8018bd18d49236b8c08448a38c823e28f5620b3ef0b43ff521589","impliedFormat":99},{"version":"065dff95b2b9cd6f5f7404222ecdbd371f15c22b844b731eb32286540f499d2a","impliedFormat":99},{"version":"d1c03f0339b8514b7d5420e075684e6b1dfb9d6c27a7fc6fbb09bc3f25fc7764","impliedFormat":99},{"version":"f8900ddf4a4944cad4a81de965c4761094758ee39bfe24198668c397caf5db3a","impliedFormat":99},{"version":"b2b8376bb1ac24155cde89574c32edfdefdb926845d9426ab52815421b3d19a1","impliedFormat":99},{"version":"4e805f78a8acff48feea70836df232a6db887b2e376492666f6b70985fb706fd","impliedFormat":99},{"version":"cfc5a66408fb9a7dd136ec2afd50a3eced54baa3321c473ee4d29046e761a3a2","impliedFormat":99},{"version":"594201c616c318b7f3149a912abd8d6bdf338d765b7bcbde86bca2e66b144606","impliedFormat":99},{"version":"35c190fc184fc2fdca132bb8aad00ac84819f135428b0e906c3e599c74125d24","impliedFormat":99},{"version":"8f567d63ab28f074ab2be3ddc2da27107de8022488f4a3bd91609752045bb612","impliedFormat":99},{"version":"f6c7ae690e2d224a310c8f967cdb415d8c7c55791a30c00da30e0c19ef4def49","impliedFormat":99},{"version":"f0ee7287284a844f4d04b80ae7a955b12cb50f85fb0021a78cc7f20a90459823","impliedFormat":99},{"version":"956e7dae5b888d02ec65dfe4113b541042cd2c70f96f6b9de0a5465bdf9565fe","impliedFormat":99},{"version":"75722639ade81b4d9a9a7f67f9cee2abbb68c52367322fe4fcd51949dbf60706","impliedFormat":99},{"version":"f90d3104f554535c4bfcf9d429e41318563c40b3b7e0827c0975624722546514","impliedFormat":99},{"version":"c61b9b3f161eb34fe5ed7fd3bb84f0774d74445928a06e9089ddc0a152f2a016","impliedFormat":99},{"version":"17268b7c5aed233ecafd22ac3e751c3aafc101b7ff982de8617bf19fafb7058e","impliedFormat":99},{"version":"bf6060c0585e76d2670629cc4c592e1dd938ac356e974916ced7f46587ba8181","impliedFormat":99},{"version":"dfe68566e870382e203fbf082e3e094b3d3d6712a3b6bf56fe66f69271d27cce","impliedFormat":99},{"version":"f230e4b9b3a7c27975a8af6131b08f6b17505e829073a3faa6ebff4a163090aa","impliedFormat":99},{"version":"e89ae5ee53771a98d89105723fc4dc73205bd96bfd2a784597b5ec6c2ed35abb","impliedFormat":99},{"version":"7f79b823d4b2a1fdee3a799a6a46792a21e4400ed0c2f45f1e1a9bb8de21d18c","impliedFormat":99},{"version":"42b828f21d7b672495a1f538ac49e93ea12da980d07d28999c7eb8dc55f297e5","impliedFormat":99},{"version":"35e7486045f8a29b25ec8adad02823bb82e0876fcce76228bc683e0da0726e98","impliedFormat":99},{"version":"a43f4964d97d0feeb6b33944f750707dbdb539e1c9c3a0496c40789a90d7e0d9","impliedFormat":99},{"version":"8dc6b9b1f772053689d3b298f089ffedf29ee93be2eead0d9c07d77e68aad9e4","impliedFormat":99},{"version":"b44e0ca6cba9c3f98a1b277e93dedcc31990c57c08f0fb37c29eb929afae3a49","impliedFormat":99},{"version":"e236485fde7c092508a177ccfef03ba15ec72ac697b50e241802d68dd99c5f73","impliedFormat":99},{"version":"b7ea66bd111288844e2d0cfb12abc02242af0786a83ddde14abc156a7f80d500","impliedFormat":99},{"version":"83fd9ba9e82b881f410b69b30d4fa9e41b1b6e445e4d7c7eaec836d4cc5a5712","impliedFormat":99},{"version":"ba2733b454a9756b8207e110896e4889859d4a23581e54fdd659f09267a63ecd","impliedFormat":99},{"version":"d94b9c4da700bf7e011fbd442c54b5c88a52db58bc71bb69db67f46a1c525320","impliedFormat":99},{"version":"bb19a13fddc505d633b9d08340c851a16638a3a2c6ba4971d538908b0cce8671","impliedFormat":99},{"version":"da588a0328ea4fa648563415d1ee4cad0587e3d1e1d29cf54d761fbe83ed9670","impliedFormat":99},{"version":"673f71885a78cdf431dd29b801ef2f811a2c793b415c44e64a55489ad010f6e2","impliedFormat":99},{"version":"4c0c16f5d60671e0654e560a94cc549a858a5fb9397a072d3f9935b3068be740","impliedFormat":99},{"version":"2cb58371baa22dbaa02e2abfc40b5640f00e7ed203e70e97fa20226a776b2e16","impliedFormat":99},{"version":"29db777661a60ea3a85cd21ce29b0bd877bb44f52cd583f9f3f7580ee08d4fd1","impliedFormat":99},{"version":"cdf79d50d5ca102a6ccd1ead392b0f5ebcb9b6c8b230e4f4931f0fab8b6ff3c4","impliedFormat":99},{"version":"6e21729eb1f94c93f99d1c13492b6e835e5c2d2ba552693c1c699f0e34d1fa1d","impliedFormat":99},{"version":"8267fbe09febe68384466808d3feaf055ebb7b15903728d23e7fb4c01949148b","impliedFormat":99},{"version":"54e45f5f4f7684c5c49d3e6367ba73c55c69f82973ecf7aca793a86bea5a99af","impliedFormat":99},{"version":"55a9664e49c8e8db27d8eb413749957eb222485b91b1148840a73e065ef6c028","impliedFormat":99},{"version":"af7945629e88f161817436aeab27906b947cea60102066575eb31071b4f84168","impliedFormat":99},{"version":"847b7eec4ffc81b7eaa1bcb473fd5da4aa73ab7e56944df3caf7d284317e95f3","impliedFormat":99},{"version":"5dd262cbb746c2a4d0a26f09369b3ede4a1a36e15c272adfd0289c47cef81ad7","impliedFormat":99},{"version":"677e4d55a1353f1b83ad68faffbdd91ffa7dbc34d67b1e91e88d3ac71b88be0b","impliedFormat":99},{"version":"21ba9b6a4c6dfc6dc403884d34dec961eeb965a4e0c99521ba2b3f9929e26b75","impliedFormat":99},{"version":"452a373c93cae3a20fb8f8309ac48b40cb2a33f05c3d54b090582ce3b8ae96c1","impliedFormat":99},{"version":"112f147e1f4b44b4a4f186cefcae4e58c49d6a0a61faacf7a12f55694b9f2232","impliedFormat":99},{"version":"c293793b601177e19a4230a9ecdaa167e6a44c93147da549941eb8e154510f4f","impliedFormat":99},{"version":"82ece43251947dd304e6f5dbfaf8b97588e5676ddf0bc0fc1a6a861aaa3eaf7c","impliedFormat":99},{"version":"f67c58823afbf2590f2c239d09a46aba9d3456327eee05b593c48ee248758ce0","impliedFormat":99},{"version":"e2647503f56e5c6d41b256af0b17ad3b98455cd8b852ba7336221af5fe99d805","impliedFormat":99},{"version":"c2d12e71e905f9ae80895201ae4b52b0082716d3177d794799f0140c3bbdb65c","impliedFormat":99},{"version":"668eaa98e8d54dc5a22d7a66d659a47f0b152e7b109f798cb295a3c3dd817dbb","impliedFormat":99},{"version":"81d447a1f248a2345a89673774ca673e79da5df8e25c6fd6bffb495d3b704362","impliedFormat":99},{"version":"900f1f5341752c6c2824ea871ae941d60be1359793a0284e56abcf277955a511","impliedFormat":99},{"version":"00c8b548f04329a012af189dfd8e3f3ddd8d4fb187f4fd22fdeba5e1eb740d92","impliedFormat":99},{"version":"919ea552c5b52ac5c8303a96dd7357986a2597de5760416468550b659113bad6","impliedFormat":99},{"version":"8255114fec0d6189524bf52d90580a2fce40bdac621215e562aa5f5b058fea33","impliedFormat":99},{"version":"6020d3e324725ee474aa4637005d2449eb8bce66e8aaf85163d683df86384dd0","impliedFormat":99},{"version":"d95e4069a535a118c22ac66a8b018818f9f74ca7000c8eac977dceaf752d0f95","impliedFormat":99},{"version":"5d8097f4e2588d7912d82772ac6f05ee6def5b738f5e4605f2e9bb24d26b4e86","impliedFormat":99},{"version":"6703ee0cb2405fc9e98a8835e4266ed4131fd25c31bcc0c302e66e9b05271eee","impliedFormat":99},{"version":"4b83d4ffdcb29aa6562749ca797b76a3b914d80f54819c6a08f1014fb6841623","impliedFormat":99},{"version":"41ecfbc96066dc0d03f1a8139e28b4b3297bc231257d27a7c5796d017962a438","impliedFormat":99},{"version":"46e060979c9bb359578744342c37b843529c284e20ebc219bd71d5fbc04b3704","impliedFormat":99},{"version":"720b258293ffe0939688db7b4729d24f64809718157b14ae50fb9e2397c69fbc","impliedFormat":99},{"version":"1273795a90591a538b11c91a7840b1facbb5b6d500146cc055324a16f58c0346","impliedFormat":99},{"version":"a06814aa3f18bf501a7bbd1cf3ad9b1fb090cb89b19375debf6ac3b906ad9090","impliedFormat":99},{"version":"785afd3f604c75ef24a65c0f2ce4b3ce2137f941773c201842abaa7385b12e3b","impliedFormat":99},{"version":"ee3bfff84df83f9e3cf0ec85aff97df52fc57e740e41fd4780de1cb3f9e73780","impliedFormat":99},{"version":"2025d7779d9356a37ed4142da93898d39f811d9c5937f8c107f44ab2344e87b7","impliedFormat":99},{"version":"471b3d02d1af08c6b58a9a2ff5c85da205910f782a7783d7a1f59dcb681ee8ea","impliedFormat":99},{"version":"e1902decb3f07a58e9be70b5136e3d715997025e0f0f20cf7e2610363f38ee04","impliedFormat":99},{"version":"323156c80e3ac6175f4b75952ed871ead30b58b9ec463131e368e572d89777be","impliedFormat":99},{"version":"7c54717447fdfa134e43c6f1a71f8ae4e955538f9e59a8bbd60eb65f5bb965e6","impliedFormat":99},{"version":"0d153b01d0b1e33ad2b8c778765c3f3539a3ffaa595dc3e9d53d91cfe5615f11","impliedFormat":99},{"version":"0ef8dbf7f717c2d8912df768687073cda1d7ec73ce2861fa8ee30ea8c15455e7","impliedFormat":99},{"version":"355ae3751ad1378804c850b212bbfed1bb68af9e4cde0cde857b86c6cbbe2140","impliedFormat":99},{"version":"06b02b230ad18789680a5d286d55d566451973456fa33b63ddff6c9b2c2ab41c","impliedFormat":99},{"version":"971f0be2884711cdbd2dc522224ba68db24abea620e9089b5432a9ed73dd406c","impliedFormat":99},{"version":"1e45c92c3241e189027db53310d5b3b8d713fad08ca6ec5f8e0734b275b6dd76","impliedFormat":99},{"version":"a374180a9dc60b15b4fea69423ae9d8e3cdfdf604e8cb314325db23a2a8e3cf9","impliedFormat":99},{"version":"acc82e49137ccc0be7e523164613032cd0a35a08b38721138a228926539a33f8","impliedFormat":99},{"version":"990951a94433c2efe6e42266ebd096f63154115a37c1f4e5bd37bee57bbd3563","impliedFormat":99},{"version":"b65b675fe2b1ad0d621ce5ad94e9fbdbd16b17e8afebe2863361e0d028dc73fc","impliedFormat":99},{"version":"1bc87b80ef30a78d0cec6f6c56ad41b68a8f03d30a7052d1a0f1e946f5eb5150","impliedFormat":99},{"version":"79ace3491ac2d2585e2e3748827466f99d0fe06acfb8cfd7bb5ac6e272d9b742","impliedFormat":99},{"version":"f51bf6581de40babf85946efb37bf4bab0a5357b46b4a0cf904278f3b8234350","impliedFormat":99},{"version":"c728002a759d8ec6bccb10eed56184e86aeff0a762c1555b62b5d0fa9d1f7d64","impliedFormat":99},{"version":"586f94e07a295f3d02f847f9e0e47dbf14c16e04ccc172b011b3f4774a28aaea","impliedFormat":99},{"version":"cfe1a0f4ed2df36a2c65ea6bc235dbb8cf6e6c25feb6629989f1fa51210b32e7","impliedFormat":99},{"version":"d94d06e50f58be0a417ebc0336be0c51e5aeb06cbb59ae7d5d4cba95e4948418","impliedFormat":99},{"version":"02246d22f0fc51c76534d953f606aab7c012d1acdb182f822c8ac8a37926a72c","impliedFormat":99},{"version":"0166e0f095473027f6f8744378f5ac5cb6557e788540fdad76e0abca9eef2567","impliedFormat":99},{"version":"f950a4cec73ccf53ee3c56f117e5c585872bd13328c487cdf7a614246feb075e","impliedFormat":99},{"version":"f325583644b63525d1c4d22825633c220e478411d813f134d5930207cdf8aab3","impliedFormat":99},{"version":"e25a05c0fd866cf73c00a281ea11bb51fa8d2a9955f2edf8a7b8f3081b37c165","impliedFormat":99},{"version":"2c86f279d7db3c024de0f21cd9c8c2c972972f842357016bfbbd86955723b223","impliedFormat":99},{"version":"df6ccc0d7f7324035b05a6294404b310a23b2f07fbbebe1cd298f88647ab8b6d","impliedFormat":99},{"version":"8cfc293b33082003cacbf7856b8b5e2d6dd3bde46abbd575b0c935dc83af4844","impliedFormat":99},{"version":"62391e62217e8a22a4d5f3ff123912bb4d182598e051f31b287096d187cbaea9","impliedFormat":99},{"version":"81895ab68da9cb1656eca90934f01924181d57439e980aa3df8c788488363272","impliedFormat":99},{"version":"cb1ee5692cfe21d8865ab74cc64aeb2f3319f2c2ea2f63cf2662b6319160beee","impliedFormat":99},{"version":"ef6ed27ceed062efa353f3c108dd31d3e4e83e222ed9e18566fa85ed4600e366","impliedFormat":99},{"version":"faa076baad26c7c20856aa86a22c8afd9113f0bc47feeacc680a9e6d4493ea5e","impliedFormat":99},{"version":"782d76ae47ae31c1169c04d93f11e6e13b50c704833517ffeb933516abc4dc12","impliedFormat":99},{"version":"9036dd2d0b09989692fb0eb69b5142647a709aae1f2bfea464701df33758345f","impliedFormat":99},{"version":"14eeb7d5737bc074d1020b7648358ad0488dfb576aa82937e8447586c1b02bd8","impliedFormat":99},{"version":"73b252fae9083ac46b9f2fa376c3a4f5d2c98f5fc0d31922e6d74e7a416f1034","impliedFormat":99},{"version":"0c35ff99747044453c64a4fe3e0e813adc45e67c16d6c47a39cda7d5b2c45764","impliedFormat":99},{"version":"a127363b7f50b5ce89ee98b2faa52a3e7247af32785f937e827e9ee32578d803","impliedFormat":99},{"version":"ae9e8befa5a81361fda14b5c44953b69a6b32abed1e9c62c533230796ba2b39f","impliedFormat":99},{"version":"f3eeac608cb47badfaf2218914776864558c08a392fa626d3a0ad678b0fbfe38","impliedFormat":99},{"version":"2c05477216d0da559ce805e5b5cb8f3c72e1897110886a0fe22808ac37a2f5f6","impliedFormat":99},{"version":"6307d21b4a02a9de0ec25ad7c8a36bfb3a25d38adb1dbe877f7e73595a4a924c","impliedFormat":99},{"version":"cc78721e9ec12b7b62352b8bfa1e37abe055c17965d5fc956d4edccb1bf4f673","impliedFormat":99},{"version":"53565e07ff42ff137d862dd402cd1799785904a50cbd75fbf8402d7ae76fb6b8","impliedFormat":99},{"version":"c8c4e8de61ce90831b7342b6e4800a3e70f4c06eadb17dd743e652ece3562ebd","impliedFormat":99},{"version":"37ed869a9de36bb1ddf343286b5cd0e0afaddd892ba28e82fd652b7ee2c46dec","impliedFormat":99},{"version":"5dccac21bdd7a3a3f399f2a0110bb1bb22a7bb002e3c4a3403f781299faf3f53","impliedFormat":99},{"version":"976ff2cb836f3b64382f2090462966b6b82a059b8d90c4eba54ffa2021e5c150","impliedFormat":99},{"version":"9432e9ba2ed3ef0169d133a2fdb113002be901691ec78ec9d2329c12c16d5065","impliedFormat":99},{"version":"f7e369493bd11921421f51025608f6450675e5d5fba73a1f5617c96072449ab9","impliedFormat":99},{"version":"0919c74e404e0f876c1687425547263ceffe5cc184404492ed2f8deb8a13cbcd","impliedFormat":99},{"version":"7df13a374704470d39a931dd1fa3602a3bd1cadf064115784e4acc3b25e6c24f","impliedFormat":99},{"version":"36944fe70fea641703d40efab3585844c0ed20ce7e783fdcde90bad50bf77f5d","impliedFormat":99},{"version":"cde65d40e64bf0aedba644d8841fba8fecc6f4793d7e4a4364be954bf273ec0c","impliedFormat":99},{"version":"ab9a48af27d31f50da02f40b83b2e8695c4ac28bd446f37d34d5ded0443aed3e","impliedFormat":99},{"version":"0b1a50c36805a5f3be773ea73339750c3619a7ac53c0f441f5e9f1cdfbddc695","impliedFormat":99},{"version":"b85424e3eeb4843556cc1838289e1d3aafc8907b44fad864f228e2abf1af55d4","impliedFormat":99},{"version":"0bdeb9f8d6472b196355591ea4a4313cef5434d24bc79c6e5e733132380b87ea","impliedFormat":99},{"version":"91fe1b91f77a6080c156f0f6af3f6b12524f04604b6e0925432c48f7ef58cfd9","impliedFormat":99},{"version":"9866369eb72b6e77be2a92589c9df9be1232a1a66e96736170819e8a1297b61f","impliedFormat":99},{"version":"e84281e45703810be96251405f8051317362e453f39f26e078cde8967fd2945f","impliedFormat":99},{"version":"0bcb04a160a2a2a934480e3b899b1d2255970b25ffc7408a5d07aaa07baf2878","impliedFormat":99},{"version":"8e3a9c17439b657424fc7e311943dcf9444fbcac73f3b9b72aec2f449a11e203","impliedFormat":99},{"version":"a6c3df80c7c5e8a15e302df97c8a35b1deec48f6a8639110663d6c85ea562fff","impliedFormat":99},{"version":"4c69a93a4645185c445f0050939645592d49f2b8dbc999ff63176c607f3dc319","impliedFormat":99},{"version":"0e2d2919246a4491005fba1612d101a68dad27a5592a77baab1523b2de335cc2","impliedFormat":99},{"version":"c32be5821ff157b2845dacfb257531e932a1161b933e6cd1cd0a4de9e057bdea","impliedFormat":99},{"version":"eb14bc57e220517c752f74ab7c810b72a80632c26eccbd7af690ed9ea7b5ee03","impliedFormat":99},{"version":"ee0de1f85e4fcafe9019c89085cedbde41a22d4492bab87623eed5afb91065ec","impliedFormat":99},{"version":"588b99d933490c59f0ac74e43491ec1b71348b049b1a391f24318b84bdc17b97","impliedFormat":99},{"version":"d78f57a7b922e855a90900275fc93805e07f8cfc7689039840118eb6bf6f0057","impliedFormat":99},{"version":"3c20a3bb0c50c819419f44aa55acc58476dad4754a16884cef06012d02b0722f","impliedFormat":99},{"version":"82c69793fa09d8b58a3589f08c7d16163c566cca5657dcc45deaf5160f2c0e95","impliedFormat":99},{"version":"b89268c927a997e32030d8d8daeb0ee65a7c7db40b167a39296459e114ba7511","impliedFormat":99},{"version":"fb8bc4e79a3b9442dd3e8b1bea89b3e0ad93dd154f94fcb7ca81f511c7c06b65","impliedFormat":99},{"version":"b6c9a2deefb6a57ff68d2a38d33c34407b9939487fc9ee9f32ba3ecf2987a88a","impliedFormat":99},{"version":"f6b371377bab3018dac2bca63e27502ecbd5d06f708ad7e312658d3b5315d948","impliedFormat":99},{"version":"31947dd8f1c8eeb7841e1f139a493a73bd520f90e59a6415375d0d8e6a031f01","impliedFormat":99},{"version":"3a4b1b3e62543a3955e1ad5cddfcc59b25074f722d5dbf7aee1971a43de8acd2","impliedFormat":99},{"version":"19287d6b76288c2814f1633bdd68d2b76748757ffd355e73e41151644e4773d6","impliedFormat":99},{"version":"fc4e6ec7dade5f9d422b153c5d8f6ad074bd9cc4e280415b7dc58fb5c52b5df1","impliedFormat":99},{"version":"3aea973106e1184db82d8880f0ca134388b6cbc420f7309d1c8947b842886349","impliedFormat":99},{"version":"765e278c464923da94dda7c2b281ece92f58981642421ae097862effe2bd30fa","impliedFormat":99},{"version":"de260bed7f7d25593f59e859bd7c7f8c6e6bb87e8686a0fcafa3774cb5ca02d8","impliedFormat":99},{"version":"f9d8848e3c6d82c1e348a9e5cc531e433be58c4ba233a6683a4e9bf6d923a462","impliedFormat":99},{"version":"48a3ae8b6325c87135a210f6d6a7ce15d58417870a2ad78d70858313c47eee99","impliedFormat":99},{"version":"9822da8046d00ef9b8a230345cc163599e58629112081ba55cf4f8d88ba5bd93","impliedFormat":99},{"version":"260a0f4a8a6dc69a2dec8ea672d702629ff7624d5684b29be55cca02a3e42e7e","impliedFormat":99},{"version":"9789d7263d261044cf33f0bb5fd31a2f4ae3a4cc2a010aa45db6f7d01fb019fa","impliedFormat":99},{"version":"d81f0485800e8813d917c2edf184ca3a7fdeada1472cad6dc41e43c37e240800","impliedFormat":99},{"version":"f59c2a64fc652509e0cc56fffb59d7b81f4c7950c7dcfc2da44b681637604797","impliedFormat":99},{"version":"ac0d6f9d09ee9ec076ec3045d20f0d6f5b32300d5a2fa05b5c5a9b6492c0de1f","impliedFormat":99},{"version":"1d8a6497f663251332519c392c6053d5b5e93e5a2189e2669620851b93fbab65","impliedFormat":99},{"version":"52f2d4cea9e3b8e4821b6ca71077ec5f41316d1b3c7d599ef10fd7c8c839ee09","impliedFormat":99},{"version":"013d7f1c5798ac843bcf24e6f3d97efa42c79f038da9cae4fc95ec686b3087ce","impliedFormat":99},{"version":"83b28136beeebb45a635f0179b828e0d0ec9c59330db43060c5958d796e35ddd","impliedFormat":99},{"version":"81c1ea7f9b00460828ef1c92fbbcfa9ff0a7bfcfb2dbfe2510bf7916c914fa75","impliedFormat":99},{"version":"6cf0bf08cc2ffa6d25c7a9852e58f7de9b26122a42380a89105c201e8bde13c8","impliedFormat":99},{"version":"07350c1be768f0446138cf700b47a8aae8e2f6d828310e519bc500200d519a92","impliedFormat":99},{"version":"4253e0bc9530f4c0eec62d1c566350dffef04ab26d0f72befd2ddc08ccb61925","impliedFormat":99},{"version":"9237ce9c67ba997f8cdbc795be7628c1eafefc3317260c38c1e2df4ebd63a62b","impliedFormat":99},{"version":"1ae2b7f6a1352e73754401f16a7894c1335f3fd199acf4c473274243f89c3230","impliedFormat":99},{"version":"ecfe3af749f3c44ab0fa260d7027b067332f0841bcdca1c8db75eb9b1890bbb5","impliedFormat":99},{"version":"94899ca690be8b491a49004460b79426162b218ef26948625fc025cb40a092e9","impliedFormat":99},{"version":"7fd2e48e2ebd92a381e745c7cfe58003969296f7d0cb0109808e6e867bef6a4d","impliedFormat":99},{"version":"98d7fcdd7c0c682528a70f6781f7a00cc0f314b720d1b15996f223c74dc0cf69","impliedFormat":99},{"version":"155e18326afb2fb26a380b480e0c892cc85cc9449537b3346fcf5aaceeb953a8","impliedFormat":99},{"version":"523d1775135260f53f672264937ee0f3dc42a92a39de8bee6c48c7ea60b50b5a","impliedFormat":99},{"version":"e441b9eebbc1284e5d995d99b53ed520b76a87cab512286651c4612d86cd408e","impliedFormat":99},{"version":"f67db9e9b24275680e88888b618e0d6514a40cef9aec2b6ea8eb1de899f97933","impliedFormat":99},{"version":"0968374af7bf8bf67301b89a4fd4bc8594dcb90b16b4be06ee57d26a708bb776","impliedFormat":99},{"version":"f57e6707d035ab89a03797d34faef37deefd3dd90aa17d90de2f33dce46a2c56","impliedFormat":99},{"version":"cc8b559b2cf9380ca72922c64576a43f000275c72042b2af2415ce0fb88d7077","impliedFormat":99},{"version":"1a337ca294c428ba8f2eb01e887b28d080ee4a4307ae87e02e468b1d26af4a74","impliedFormat":99},{"version":"98a667d4585d5b040af90fb5062e31da7c215abcb47521ff57e33f62755fdc17","impliedFormat":99},{"version":"c29e02568f8b68e62b83db2243e4bbacb5ced2d6c8d120e322b56a018d1070b8","impliedFormat":99},{"version":"53c8e58bcea418aa22f5ee013774c08dfe15f0df9625868b7ce5a7201de29785","impliedFormat":99},{"version":"d56ca5a8aa5dd4937a82df98dd930ef154f340d259bcb2980d36c28a47ff2901","impliedFormat":99},{"version":"3d421ded6ae2260cfd45b230eabe38b6c8498a1a35db809384c85ff2bc3ba822","impliedFormat":99},{"version":"ead83f43dcb956f13b924b9e43e7a64380f830efc67439a9e9e479bc985df8f8","impliedFormat":99},{"version":"d04ba54e15442a067fd28679bf18d11ca2162d64f3b0695b9ddfba2b8e1c3b59","impliedFormat":99},{"version":"6b6cced9b26444d621bb62a1b8cb65911c22505fd559411b5a57e699c7aa519e","impliedFormat":99},{"version":"dafc53212e800bc9bbfed7f3a7732ba8f401516b2bc0c71b2f601ae2583a007f","impliedFormat":99},{"version":"940e51654c3c1967f34160a9674e3bf1dc436a5d36c5d7833718aea235e52fda","impliedFormat":99},{"version":"16f2023402fd0a4eeac99edb5d75d3dd8cb4b2f25f46e9bcdaf0c0bd9670e77b","impliedFormat":99},{"version":"9d7765384806b08a522ff85c20184667eec635fdb809736184da23e89533dabd","impliedFormat":99},{"version":"d53593a008e289638eac5b0a0dfbd4296233e395205831367992a87e81eda13b","impliedFormat":99},{"version":"aa0981acabb92a87323aa1579664c293a968138d9377310fde29429e92febbc6","impliedFormat":99},{"version":"796d8fd55590f854e79d3f4181b54f28108e90118314c858726163bb9961e7ae","impliedFormat":99},{"version":"b37e4e4f8f34745d839c334991d9cf227c34c2ed7fb3b297011ddfddf3ac7d68","impliedFormat":99},{"version":"ef55aaa329259ffcb1694dc5d0d688f05e5a37eec2ae34510ef751e9d608b90d","impliedFormat":99},{"version":"264b53b60d27b252258cca58f80b81e143b6299a866402819d5524fd20febd0c","impliedFormat":99},{"version":"714456dfe665ce8b398af312b56b68a927a8a182f8e78dd7c1ef5cfb596ade25","impliedFormat":99},{"version":"27c9ce7c539db9b37ec0d7476b4e9d9ba7439dc41549e466aeadde43746e8390","impliedFormat":99},{"version":"903e813fb2d906d278ab54626f4ade4f43f96f4e636dc66f5aced69d1afb871b","impliedFormat":99},{"version":"c80bc9ee4fa024302308d14084c0f6c3026301db9abbf6789e6b1caf686ce35c","impliedFormat":99},{"version":"8cd470e7936934cb17c70c18a2e03282980d8d047ec08467925a31bf99ec1bf1","impliedFormat":99},{"version":"c09f5d7d8cdee279972790105f90d6adbfb18efb905cf04815ac59d033f7bb7f","impliedFormat":99},{"version":"e92673d9d3c39fff66b14270f144fd32d2ec6fe92e8b2c51e65bd7b4a0e5f355","impliedFormat":99},{"version":"d465455e9f29288b7c879ecd390256571ba306f8b947698f03b1429d6300ff67","impliedFormat":99},{"version":"108b9e022f7dddd5e5ed8165170d65b752fa7b21ced5dd1005ffad3c36242c57","impliedFormat":99},{"version":"1890b77d7c36efdd18174e345b295ece38e66179dae192fad21e8c3642b993a1","impliedFormat":99},{"version":"636f9c9b34b3f33b2258704da1187e271fbf36081a8e22da97be5b53488a9863","impliedFormat":99},{"version":"fa6693c8ad74ce099f2a93ca8d1b0a643dd7f6026f41ba4b244d440d8dd07f03","impliedFormat":99},{"version":"fd76be177303d35dbd29c11de5f935f5d21ad605d34aa4aad9e309ec494b51a2","impliedFormat":99},{"version":"f31af014cf064d7cea0392f02595f09d8cd4b9d06c7794397cf3ddce13111d81","impliedFormat":99},{"version":"d15de8944d6dfb1c8fab88ed1d56947c4ae438b9fcbd9be18f7840b78c9c3bbd","impliedFormat":99},{"version":"040fd90833b34b59436ca6545a00a3b5988f5a95e6cce0a378ddd66bd2cf44f2","impliedFormat":99},{"version":"f332d07979b46f12410417a97153271e1bf5ea11677423718c59010df71a3f2d","impliedFormat":99},{"version":"06911ddbb7160760c75015d2d6fa0f1c0f94d9f0d61265b2d211238b571a3ff2","impliedFormat":99},{"version":"af0612a0e9b7efc543168628fe60a8d3f4d7ae8d97fe257788cb60bdac2459c3","impliedFormat":99},{"version":"9dd05d844e6b99e0a3c8ab8e37bac8f6297d531a844af0738f9b1eaf4aead087","impliedFormat":99},{"version":"5f7be41a9ceed0632c19b7cdb5ad9e07ac19093cbe23a738fe0f1c8c2f27b036","impliedFormat":99},{"version":"b33e84f2148cc81a9afa6d4177a27a1d246fabea3c0cf391aebd3e62eec04f4f","impliedFormat":99},{"version":"5dd273430ddfd576316532f118feafc41f18d5128d7d84e674d98f4a57107384","impliedFormat":99},{"version":"a9c40d74fab8e810c62cfea99a21d09f529fe6a0e60c39353510974c33df980d","impliedFormat":99},{"version":"2665ad2e88b3633b417e176af058b1c20bf5645327a8c4fd4f08e35636b72f9d","impliedFormat":99},{"version":"2321ad799e7ff9c6c6a886dea5ab208d08072a8d33da312f1b9a10ebc888765d","impliedFormat":99},{"version":"8e2f56264cfd71093034fadc1c788d6f46d58036a57e7189e8eda9a7f87eb9d9","impliedFormat":99},{"version":"06deb0a45f5a6dd23244cae8f1ebfa2400ec7de804980f044316d2d9d35a6ce5","impliedFormat":99},{"version":"b27242dd3af2a5548d0c7231db7da63d6373636d6c4e72d9b616adaa2acef7e1","impliedFormat":99},{"version":"e0ee7ba0571b83c53a3d6ec761cf391e7128d8f8f590f8832c28661b73c21b68","impliedFormat":99},{"version":"072bfd97fc61c894ef260723f43a416d49ebd8b703696f647c8322671c598873","impliedFormat":99},{"version":"e70875232f5d5528f1650dd6f5c94a5bed344ecf04bdbb998f7f78a3c1317d02","impliedFormat":99},{"version":"09b103d94e6bf3723cc3642b164dcae50bea1d1f0ab1f5cccc38dfed3fb2beda","impliedFormat":99},{"version":"e3c2cc25f470bea78afb3af5ca6f30759cfd44e4b1212e84657fb36e45a3a1d3","signature":"6e0fa1db91df6484a033340dfafa435f2e98844b66e876a01f963ff84a878646"},{"version":"16bed64f9c23abdf4e18425ab522343d5c8f180214832e5b6d40135d838f7841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e6e6f01cb776da72cb3ca11adf3fa20c16d0f68841c2f550e485376491fe584","signature":"981a1e9bb280cbad4485d10bfb76e890079fcc75cdf11f502bd995e1065d2616"},{"version":"2e0902468e1a220489a3f33dc82d5eff8f70521cc4ad8eb35abee7a792a14a6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca55e9c482d5da0295fc69d21ed6822af32439b9fc3b1fc55ab593deb4a83880","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a0395b4c83044d52eb3954c29d53ccba5aab9acf9765dbe663f8f95783629609","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abb13a376d731db2984464da46235b3dd198602a97e200bf687c9a9a2bb43593","signature":"909a9f6b4a08c0af15d0c0e3cb1f290ccda985ee205dadc0c735d3bd1467d5bf"},{"version":"1652f9e9c84699da6c0050e0818ffbd6093fb9dc67e082bfb5d3df1fc92012b2","signature":"e7ed13b72a29fe7f0c628bb06ed80ba591e327268a8acf0cb66fa1725ce5e930"},{"version":"ccb92614f79729906fc8a9f5c9c18f163c1352d2e549cc49b42c4302fa591f30","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec90498fea3cfaefc1dc5badcfa5d2c8f05a73f96abb856d63707c0cd25351eb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eedbc489481913977cc00e055bd0f493c8ed3115928ca006929d1b353411e722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1635afb57a6aa4a470f3b256348826c36e42594db09683647dcd00391b2521b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"73a98325658b7d7509ec766ae0f0b34b9a4d9819b388d4e5b3c74ef8af5af185","signature":"211206a1cc64d3623a54c919fe8e7e8cb70032936ce7dd2625ba2f185580334a"},{"version":"8072581b3b7e9ce43d9553465431ebc422579042d0a644394d018c6803c45918","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"938165e53c76725415bd8152fc8363d628ac4a95e04d4c2884f75349e3d337a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c868f50837eedd81fa9f61bd42de6665f74e7eb7a459135c6a14ac33ddc86798","impliedFormat":1},{"version":"42cf6b642a67b27545981d06932f7e5ef948a68dadf5779cdfa9e052e3a13d76","signature":"41302973852bac2a0d545eb886ea0b819803722d9d6344a011477d235854894a"},{"version":"cb61a5aafcdee23a7ccf20343670924ee6cf6ec6f631b65a3ab249e27d9db542","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d2e1e7f9ea69da6734503f8b7077edde2e9fc91596141725e2beffba76ea2ec3","signature":"0f87709207a3c70d4c4dd8ca7a866e5114b412c6629abcd9f4bac4a7b91495e1"},{"version":"1e3bd35220cea102b5a84d579f9bb1adf4dc20dea714829473bb3aa87499a64d","signature":"230d47db97c6f501ec507c267dbecfcf25a3a8c8c13854734008f4294a0da41e"},{"version":"ad3e839b384c5231de4906ea0d62e778d95f1e46937d9c005487b0897ffc48f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebe9b7b5b1909551f7fe8a5aedab9f4c713b928f5ffeb7b83c9ac876861a74fc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dae983fc2e940a628dd197d10e67ca9cdaa071d87d7018ceb8fa5c8a690eccf0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eeb5551958a1e9c5493e02cc7a0eaa112e946b7590a018f1bec0e29de91a64de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d6fef48c89f7fc4804cd6056fa0e5495a24ff66773aecbce8fc3445564796888","signature":"a10492e4eb6f7d58638216495d74d70dfe714fc6977cfa5510ffb772d56a0c79"},{"version":"eaea9c64fced041f983824a099b8212dca0270f05c328053ab935b9a7546f0ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"29e02239f94241d9f26f19f570a5eb688c86873d1e77e43868fd69f6a38e771d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"681ab80103a45e835b91035d733228aa210d75cb0cd45355dbe6e72fcbe1806a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfc4d1e3e0f3fa0a4a3de8483598fe4ca1f9677de760b092b4919748ca383fff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aeda61f5b4aa7621f64d34f54d63b5d05051ad117ff705e19069b5ede8e50a16","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"ac450542cbfd50a4d7bf0f3ec8aeedb9e95791ecc6f2b2b19367696bd303e8c6","impliedFormat":1},{"version":"8a190298d0ff502ad1c7294ba6b0abb3a290fc905b3a00603016a97c363a4c7a","impliedFormat":1},{"version":"5ba4a4a1f9fae0550de86889fb06cd997c8406795d85647cbcd992245625680c","impliedFormat":1},{"version":"57c98d540df72bdc219a9d4e23e7c1f844459414e4e62eda3439067fadf743ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef4a6c603a685a8786368a221e0fbabdc221135d6b59eb1ab442a1e0d260da42","signature":"b5744d0126cbd200c1bef21bac2f14fa3223bafc24178f26d27d834c9b4ce7af"},{"version":"723d1d05be7e263d358580c9bba607944fdf6e5093e7bf62a2f578754b779390","impliedFormat":99},{"version":"7a59476a46fd4b3e1522e9c6ec6cf436b6d5ab8ac97a17ae867aeb9cdf0371ff","signature":"57f1ad6cd433ecc0e78e4616e780d4db68642604162b65747c70a6142d28e49b"},{"version":"ff3e228e751934dd42a9f05cfd75bccfedfb529eda504ee0c4f0d184da345050","signature":"4a1201a691800bf407a2703017b769c5ce1a53418279b7682e4cde1afc7dc6d9","impliedFormat":99},{"version":"ee70ae40394baf9312c35363c42fa429ba3e037ab10cf767a184ec38d24b5427","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b5b7c87e72f384980ca1d92c4f54d6c30b2f099556e3843588073cfe0a0a893f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f329dfad7970297cbf07ddc8fce2ad4a24e2a3855917c661922ef86eb24dd1f1","impliedFormat":1},{"version":"841784cfa9046a2b3e453d638ea5c3e53680eb8225a45db1c13813f6ea4095e5","affectsGlobalScope":true,"impliedFormat":1},{"version":"646ef1cff0ec3cf8e96adb1848357788f244b217345944c2be2942a62764b771","impliedFormat":1},{"version":"c864801e02e8547ed49024b3a469d6fbf600ee240be6bf413bd6149f26241348","signature":"90ec9100c29e008c3d9194acd818e2cfa6dc6e177154bc8e10c5959aa35619ed"},{"version":"6c8c958cc35f90494284a36edeedf503f3a56a93960016a618a1e587d19d86c4","signature":"2b02e2635e94d92d8a4c1fb05177aa1f9bee04c362dc8600559080aafe963e14","impliedFormat":99},{"version":"9c947051913ac9feed2de4ec57656a9f38ef4bccd22518b765f5877c69894082","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0260838d2473bd7872f0fcef24bdfebc247cdf5c95217670ef50931bf93f2e91","signature":"2affb08b140b8e89210e4b39ed75b00cd5e5ccc3553a80bb3e83514fd2461e7b","impliedFormat":99},{"version":"e9e4ac4ee6a2c612f408e17bfd9bd5398bab08053196d8e8c6cf64d8a7335a51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15128feed70d09b1e4f994cee399f093af7c7c42e224db77f3ace502a457a2f2","signature":"c7108b0b3c30b5aa5fe1fb0c2399dbe7da3e7730cfdd42e7403a0402394bf466","impliedFormat":99},{"version":"3bbf19210a7e08f50ce1518710ba0ffa8e13a0d55d78fdf3cb62cbad44d30e1b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca273ec7d7789662ab7ae9e00a4556a0e42416d6f8a13702a5746b5ea6862061","signature":"dc89f83d1e61d147d010a811cad4539c273b3ed227aabfa8a9a130b4180d2cd0","impliedFormat":99},{"version":"2e71945b350a81ff50fd4e21a3660e7e6055a5cd5691d6d8d867d0e6f10cf313","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5d0e499d96f070dc2607f23d55fe54ac074d1a840b6505e1978f70f57232cf7b","signature":"9e4d212471d83031de81b7c76834be81b4d32b5eb573cda6c61023d1cd5f326f","impliedFormat":99},{"version":"f20e59aa1f8ad6e7dbfb10f7c7147773dab8b5d8e4d59eeeca34944b51e4dd14","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"84daf1ace1a44a36500dd4dbedc8a92e50c4a1c1e935ef732dd18b7e2fb0aaf7","signature":"5cbfab9a555788720d027df70fa580bd727ad40aa2d325eb0b04ec4642f9faf8","impliedFormat":99},{"version":"1784d27f3095418bde9b61739c7ca7bd30b1bf05c95bde803514bfe48ce23f57","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e62fecd6655ce82858142ac7225caded25ac9b7da81632bec4c7c054983bfc68","signature":"2bcc2d03633b291af104b7774e1f7da0ba4dd09809fceb39db956b6a7e127ae7"},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"8589487932fd916840218fdedbb143741d22216ddf630c70d401ee674c448e1e","impliedFormat":99},{"version":"c0fc68a185e7479c68bb3304bf208d87e9d8bbe9a684302d06c40245670cabf1","signature":"42288bb7189ed22d6ecbacd5477ddce0e5ae1fbc1dfe48b1038c58af794199dc"},{"version":"31caf28f1dc08edfe3cb8e7c4011ced09f4bf3f5f725c50e5dc0b5ae573b498a","signature":"04a996928d0f8d5efd87a2990c4f4ce70e00fd0c975971fcbc570df7961daee5"},{"version":"502c011687aee1a48fa221d356f8f2d8eeb035c0706e8f8e9ef0104660cfc51d","signature":"1a734856e43cee0599e8a537f131cbaa1e9290b47f2b496fb504f95e252b8495"},{"version":"1a7163e59864fbaa14672752a70c8b38086117e5a14afa00893611dfc2fa803c","signature":"01a977ade994fe0de990222140f158a0dc3b03529994c449aa39333d0facac02"},{"version":"348b8169a6c19556863ffe85bf1fe1ddb0006affc951bee6eeb7dcb3a2d6eb30","signature":"f12359b22cbaca86f938ddee38c0c33924e768a93042ad939fc2288f2471e5e9"},{"version":"43ee1831235987ca593e76b22b4116009f1ff6fb0e7a3fa6bf1e5df1420fd6dc","signature":"a05af3719b211bbf59b553f0760633dc3095778bb0171502d7bb7342a54d3b15"},{"version":"b2e86ee920713d5b287b9b175ebad35f82e979a456a9b276fa16bd4df708bf20","signature":"138cf0cf601045c4741faaf79f2d7024eca30e59e3b533a4d60e01a99209ad68"},{"version":"ddd3e5c54eaf7ed7710f0b95669f32c7b1693bb3289a4d2d559d03de0c62ea39","signature":"a53ef0141acd28c738153d5b7b9c637142cab2fe0db1dd0d6a55b06aac99925e"},{"version":"45311c218ffe1c8393be29ebab04527a9167c2e48a5fdb15adc0f22cd541614f","signature":"dfb3bb27e47ca92752033b3171dbe6a1f8e9404b34577d1b16eac221e1745a2a"},{"version":"093616375ac2af574eac9fdfcd18193c3f9394e1b1d4d8c79d2e6068790ac100","signature":"335f66607c072668fef3e0563ad5619a7632e75eef9f85740e84e35523d1ee82"},{"version":"383fc1e3823bc2d2cccdbf51be644b7f2297d6d04190008c1ef7ccf82eed9b76","signature":"77658513755ac8d8ad639e6f969539b6d98cdc9ea85a2eabeb33fc94a839f395"},{"version":"eb2ca2e825628da3e61a98cbf8338f9af1da351244346ee4955b09972d60e7c3","signature":"0a759888cb435532132e0066d5bac2f2786bc7160a24f07e7b60ee958d45b88d"},{"version":"2aff6fc2089561043f6978a98e05783fd6fea921b6068a4b3dd36e824221cf0b","signature":"0f2ab2d398a5484d267cfbae7f4512671debe1dfc0056d474a6d6add63a148b6"},{"version":"a3e0707d4c2d3cdc9afc01b85b6503f0a9d52d5910ac5766dca6236f401cd6e5","signature":"257d9dcd4c3e61e552ab3f34ead65de21367da6f92f9d62979a26fd748982849"},{"version":"6b43dfa5e9c9d89bcaca0ffe7da88f34e20d760ca158398a3276cef61f738c4c","signature":"410aae1dab008177682aafcfbeeb27bb71cf90e2644309d0473a9f4840d460b3"},{"version":"4bbaca1506dfa9d1c4075c7c10b707c2349f2017f751e32e6bbbca1f7e11e2c2","signature":"511d2ed9fee12f9ca9d9cb4bcbe6b0ed455f5da5473733413da9b4d10d916a79"},{"version":"63d9dc36da9bc05dfdb5ccf23b5738648c073c545320dbb619c6b0b27ce304b3","signature":"859b36849fa1a6871f9dc68605252132a625792e315c5e58d893b28aff84c7c5"},{"version":"7f35d5d9a42febaab1e7079785cfae8c9f977d777dfe871730f0a360c254a356","signature":"0cc24adad526e7c075f3223582ca642a555751bddc0088d47a1fe62ac19ebe31"},{"version":"bc7bc237e289f8d435d34601a22322d303d64d497e25d80d555f06f7acc34e4b","signature":"7da246bb1c2b2ce4879114715c5bd7714bef80824031c70e814efa143acfdd51"},{"version":"38a9567883eed6a8174f841b784734b949c25fa1f913d1b96eada6bc083c160b","signature":"f4ad04f8b4b9e4ef78fa12961f2c36f0032035ccbac259ca95cc05fecb70d3ad"},{"version":"9a7b469bc32fae75951dc069e760b7945d91829873247f00a5ede47eddfc5d2d","signature":"acaee283946e562a6a4f999558a47c3d5110e5e2ae0581f90b5d2d4e35dd74cf"},{"version":"5b8eb6e16859a5d0b869e2607f6510cbdb93ff3b24942edfb5098f2e6b07e773","signature":"03b23eb17ac097b361cad4f90128b223cfc584893f86a350ad9337aff15890bb"},{"version":"983793b81b9d3f63b32a2b4aed4cbecdd215d0c00487729c5ee788f9d8a77c13","signature":"afd02efecb9f6288c3098659c94182a2d6fcde4620ebda7c2aa229cc5d2c54b1"},{"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":"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":"3654ba818fbf4ac2c49aa3dbb050b912277acc71b6d5e4f434720c27a1a68f3d","signature":"02d33dd7ec31c9ac3c91582f2d0a3f665d587d5f98aa667ad74d4b543e626610"},{"version":"6f3c420a59fe4ff53cc42fb1aa2b2702fb048145fcb3c26515d3db35e3854e2d","signature":"1f71e9c9d089eec515e086adb2e10e09414ae876ef4744115edfcd57c6684f7f"},{"version":"2a7a18a2cc9b4656d9eb1d5f4fd0e3f3466f600c32ea8148643dd8c909bb3476","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d9cb0facf05859f0f35707063253d8b55d8fbb565afb642c0edbd72ce77817e1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c8924b198de81de4222b2f0b171e9262f80bdf62beaabdf8ee7aa13b27245871","signature":"2f5adff38c8a75301b364bad4bd26f79cd3a86bbdd3cbba4541673d903d47b4f"},{"version":"0702499aa6384244a89e13df162163ed41949de76518a8580c8044e783876dea","signature":"401f1208590180b74cc9007c8a894d499d48b469bb110c769cb004aff4819b3c"},{"version":"534cced4db5dcc639cd555583be09c6891c0633dc395308c87f60b47dd54a6b2","signature":"33ecf206edccc488e96cfb5177f19809e8bbb549ed0e94ff66d1cd1ff1a1fcb3"},{"version":"9f4f376e778fd1560de1f3afa4b8ba1971bb8bc5f272324ea61e65e15b685f1c","signature":"6b211c08718dabbbcb8d48382a8416b20d9c90e3a7a3a9f8dbb192baa29018dc"},{"version":"b16a9573271f151e37a10543a8faffe67811ac8570d87054108ba01799b73ba9","signature":"c274af3f97f26f9143c42701bf431c06ff0af56cd5b14e86c661a294f335d8db"},{"version":"48f96604f28e1d321ea8c94e7e5cc889f4ab3720d92ed9f412ac7dbc2931a1d9","signature":"0d095606a67e17da85041e7a56c4d15c377ff643b56ca69eef8b42d670748bb2"},{"version":"1d899a3b3c762069c87a2363e38fc467d3fc0c17f6d22f98de3a98e1a691540d","signature":"57bf2ffbbff6d58bb1422d725989e22ba10b14159c98d2e3185361f5d609d9f7"},{"version":"3f56d8959b17508732d17ec607714398e73009d9eaec652c8fb9d5891d1c7c7e","signature":"b4f0b3be4ce1aab443b18ffd19432c63b332180881e573f620cdf4d257b5426c"},{"version":"0429300653bc8825a3a00ba906eaa03d5b322bb198eba44726cc305936446bbc","signature":"9da79310a8073ee98ed1de785aa8283fcbee7e7481b71408c5a99cf0e601dfc9"},{"version":"11ad149a0b977ca7a0518fbe321b005ed1aee824ebe4ae74800837a4700dff2e","signature":"bfc2c8f0a629b259b1e101742e40731bf7af0e33a25c1d4de46cf85d129c212e"},{"version":"6463c4d2ab1246111f0c7be3929d40188e121fa90b62bef6468e455ecad7cb1a","signature":"69b5aa3a3485b7780595d1c9474a3103be32c05414d7409b89972d21535c60e8"},{"version":"978ac9ab1977c957fe99662e28e319fd04ebeaf373bc16fa6adfbed404f61b75","signature":"be1719b0c3e5f1de72217c7107e2106c1e3762ce3ca52d3d26db16dc36c52150"},{"version":"33c2eaf9f2216da640acf1814500bdbc10e08241fd17d8e19eaa203c50152815","signature":"155a0ce1ae5dea70b7c62b88bad1d252f860edbe6973cfb381851ae84fbc57b0"},{"version":"7c3c3e194f59da1a744d6d5c1090d144769d025c068b2f84524dbae0fd481d97","signature":"5fc8fcce3719297a5a3c0d9b41aea6db99a3fc963c76fac983b32894e6694193"},{"version":"b38b60efd27019b6a729129142cfe2e6853164174eb3513547c9c13837407e30","signature":"b4f67dedcee400ef1fcf589dc8a3f74a60f77640595d82519762104c3162223b"},{"version":"eca7b0894466635ec8350b68f82f12ad8f5e538a14a2c6a20e26be53f2775a42","signature":"036a6610a766102c1728437ec9454c572a29f908e5cbb80bcd71040c1a48cfa7"},{"version":"75b6d54e60e07ccdc53fd68958e5fb4dffa6e3cef5e13d478ac8901bbdbd814e","signature":"990169bd34d817d6b9bf57e56e3173cdde174fdcce0cbb5b5648b0aa8fc83f76"},{"version":"e1c0d6996b82a7c4eec322c0b62c75711d6f902f75a5a755b5e0cb7ead8bd72f","signature":"db1738fd62be473a0e572c91da37c7966dcca15d00dc7f9694bf78b170275ea1"},{"version":"dfb5c15f9f6428e3f92028355a9b2fff5ba76aabe338fc058f2cda201b866d8c","signature":"a507a22f915815dffd2fdb731fc8fc2d3a0b2fdee404c673c8340fda0317022b"},{"version":"aa40d71dd57a81028c76d4080716d6dde78ff51e92ad1460e5f973adbfaa193b","signature":"b8df9c14d085533e16aaa58dfa061788e5dea6b8d7e3a17ace1562e6d904cd85"},{"version":"955ae27fdc755f32aabee0f82c2db6b3d8505f99551cc8376df389eb90e7c84b","signature":"4675797b0de56fe3c5a6e468df193709c7f066a244e2da0d02690f193eed5345"},{"version":"4eb900416055b66a7063f285dc36561ccd1d276de8a637165beef04a3b3aa162","signature":"ac2b3808b01524a4b3ecc52121b04eb3b74c6d267ad7db0c4082c2934c8da0cf"},{"version":"7abfcb37b73f3b4fcca65caa3cfe40a12b8a89fafcfa783f93605acdddb0cc25","signature":"1a574fe33afec63182b358ca9e29944cbdd13c69413c53fcbb8e924018e33b8c"},{"version":"4bbc8169c9196d2768927f96e35712502c0445e653a5d427f670aac13452f77b","signature":"0112553dbd79407a27c58713ea3d744d72a100f4d89bc8c817d0a4d027bd8d34"},{"version":"6e525359ee1b437f87a241ee6e5197cbecb25d0a2a575840886e4f0ce4d27017","signature":"ca5e8662d40a41d49018ea294491f822a60db9fccbaabe8281ecc138a9e955d7"},{"version":"2344ae91463701eaa9402c073e769bd7a70557449fc13c0bc4d52ea2d95f6d6f","signature":"a87a0cc69a8844e8d64858edeb2f5b9defe9db4b03f6081c3cd27e2979067a80"},{"version":"964e2cf748eb23916c541b47ca7aa8621f6f99a9da2ee2b53cf62fafa784243a","signature":"7e126f3b1bbbca7fc0092253c0cf8cda8ecaebf818d91bb35bedc35e19f75fc8"},{"version":"e99bce1c616138462e9ad01d669d9667759a66a549795aad43cbd8d3829eabd2","signature":"bccd3d911c3cb5fb8442848a10723e7ac2fba4a94c37c8fb2700ee31645b28e4"},{"version":"88cdbc3bcb4689a70130597de7c941b2450bb2760674a02ae816e0667a1958f6","signature":"6dbaf13dab6dc2db0cb7312fba7996ca7f548c7929bb627315cc89b43bf93ada"},{"version":"93fdaea06f53eda94b236d54909091dbd7046bc96315b59d224962d4a95299da","signature":"71e0ae0ab79469ca19dcf4d5566240e5019b5a59f0aa86a6d1b5afee5edac2b7"},{"version":"5437c086fa05daccd0b205f10e71c34f7a5c65a60b70c449a77d71c547777399","signature":"6a891a6cb7835fc4aec6da5acfe7e699a7657630100a6eb7670e371e5a4d2ea4"},{"version":"9636142d048623bb863eed744dba8c1aad681a8b29574d438931ce5c3d465c2a","signature":"ba520a1e00fa71ff0e6e101beb05e138cbb2dd908976441ed96beda042500f77"},{"version":"55a9358103d4d3fc812e7226cbf54ec885ab5d274c3f0fd7c7b89138761420fe","signature":"09ac449d6431aaeea979c72f664e0179ac698d66b9dd695199bcc985f21b10b7"},{"version":"a64240132dbb4eaab110bb3733845bbfe14bff96b09fb6d46a64232c8dda2b1e","signature":"476b4071f1aac8d5027274bfece00a4fb738c3caf9cb2a033600c06edd10a0f8"},{"version":"9078a75f8fe253aef64d238341117aa3019d80988b1a6c58b49495a42b392505","signature":"4c3c517995254a3515b6df45737e5ce8e1d8debfe98dc634ba28ec324e591163"},{"version":"cf6b889353036a253a6fe8ff0db6438a559bf559d9dea64dce99840eb83b191d","signature":"56e9849331ad4578371de0e5c8b3203f930d0d06a0d62f5209e9c17e41693bbb"},{"version":"f2ca6f145f319cbda91e6d4b0a05184127040e7471ae54e22c4c13c457940bd4","signature":"c44b0bd9da5f7907a8f132f289ad93a7e7b57a9943b661612900ff609dc8ebcb"},{"version":"99a509f3f5decfd71596dc886af5c93bed0ab51a1e69855374290ecf66636dcd","signature":"4edde3cd15f3e6efd0e5d77a9d8b78997e2faf00a87f5741161b140472c267b0"},{"version":"f72683c914bd21a52511b4dfc3f25f622bd7ca5dd0c1c0dfea0c747f7466a7b2","signature":"82795623788e3260d9c6ee7f093c27b61c7257c31135e0bc833258ebfbf21f25"},{"version":"e86e3055a12f91b39baec8443daee70ec414ba65a82282c62163ddd6e968e45e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2564e83977f854fbd3ce140f2d86f6992c1332945634a2f803306596ee0bf69c","signature":"2e3b8ee6e5682bd9e7cad45bc2a5ed071302f74f8bda226965fa0693fa761f16"},{"version":"b66cadd5b2da034134f0112a8d584d76eec9e8025f23eb6b556dea5aa74fe3a3","signature":"10284337c30baf75130ecfa1c52aa566eafbbcf0391f1bbf7e21cad62835a0c9"},{"version":"3402b3070b7f9a2c6ea7f3082c2ed7f2f0d8c589badb6f3ec62044c3b7f0184c","signature":"6f3a722497ec70b05e83ac5087cc5bee72d7b19fc760554762b40072bf77fde8"},{"version":"dec924e208b541616842e85b39e55ef79d7a97062654794b62d56622a67bb974","signature":"ac6b01a79d5ff4dcd12aba55bb4ae5b0886bf9246467659a3bda4620813147bf"},{"version":"913dd719a5b5a91dfc16f29bbb6af8d1f8aa0f2b141eb4320cb2ff4f973bec35","signature":"18e3bf7eab3bfb15ddcbd0e06c36856ace9c7bb9b7a179505fd0f7a9f15b5c38"},{"version":"a4150749c6aa9db1224cefcb07931a35d19f1f8f00f4b79674f6d25c5423f181","signature":"6364708272ae524befeb1cf48d39cc0539e266b6062b8d26e89d41f02afca5fc"},{"version":"5eb87fa9a117af0d5672f63271c070d9294dc2592a8a71c7f67dda52635bb8d6","signature":"71f5409f85ed8b4c3910cdc686ac98abe30d2807197945e3844dc7bf5b9d6479"},{"version":"132f976bdb7a85c0fc4a180cb4673d199394e4b38feaddeae1d0939c90df34b1","signature":"11794a33220970f2e2b523767a9724247e154a37985d933321a00b9b31d6223e"},{"version":"2fa9260ba8c9c073651025b09d81b2da143ed8a4d7334d20a0f7f7eaca3c3ec3","signature":"5012e56859c8f84faba4532e014d0fb50542726d165cb40e5f1f5d2207e1d465"},{"version":"63d2523c2cf8126e1a3c3a11bb79e28fa66c6f816359bd57b87688afd9b3d350","signature":"5307c1d3d1cd0c5977d376721cded10dc36d16d06d1e83cace7e70cd20e49b08"},{"version":"33f4a53a232f13211090183cb571ff2fd60748eba70de4252d62cc6d7a17f1f6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5f2bb4ac13713c6726c29cdb23be59f025c6d9b2d9a22729187be2502a069978","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"311ae6055f6a3f6c98a79cf2f957dec6858d5ca3c4167f403ef2249775f0531e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c742723bb689a361dc0e32cdacf7f4160145254716deb013292a2f45e6f5e1ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7b6e8d32728e05107c573c5dc2b6fe9cb14332dd7c82fb530093a840d6b59dc7","signature":"22146890ab30bea45bc289ccc48192249fe1cead53510eda7d9af2b09e065189"},{"version":"7a81b4127658262d3b44f32ca2fb5589bdd370f3c971b7023ec2bf0fa80208f3","signature":"0774366c811ec1c799b0c0922d3a58dd6e81ab902ff9847ef804b1cda0b16cd4"},{"version":"0471574e07ea402de091b23741e7759f0293fc476645407c75e8807fce4d508d","signature":"d621400249ba8e7421459928f59ca558d53c61417f4d07833f994947592fba99"},{"version":"54a60dfbef03a8f34a21a1b21e6f8c6b991390b6bdca741071f0e9aa378b4610","signature":"2e695038b1f0a6040ae88a1a869e11cc466e03a7b13b526efa90b3ebfcb0068c"},{"version":"62b5adce04fcafc2bed80017c0b0edce1ec7219e0d019a8e9c6c8476906dfd80","signature":"b86d5d8bd5104f1ab29d23cd5be61bc514b8146a091257366678f5d99000a957"},{"version":"503b83a8c33ffdf3a4fae4b560df55b7e98c0722c4ea69e32b7e71427888f440","signature":"4d981d6aa5d8dc5af9b343ecb2c5f4c5a9e1e9890c31158e049df1c31bbf7a72"},{"version":"ea6437c6eda871607d5a01adf7cc5afdcd66f674509289cf2c226cc8b9734773","signature":"6ff5a08113fb520f023cd78f8c7151bcccc3824aa41cc276419d8f031e790082"},{"version":"61a5d2a3dd261b3c2b751c713d088f6548be6199705c2c9c5775d12bba1b8fcc","signature":"df57aab767d70420721669a994c9995859df1ca3188599bd5d693061c9a20367"},{"version":"24863e2f4b2b1bb3a3450294a76b5e0eea7b3a2e295f2225745da9c8592ee216","signature":"861096a3a6ca8f6ad72022664dd68b02ce3c37ff2d8f05354e1cd3fb3342b366"},{"version":"ca5c0df4cf20a1e1a7b2961248f35767785a03058ed250be5afa76c2713b202e","signature":"5f733c3a82d525121c2b95a101038ad31ca21b64d9ea2d5841bf71a2b5a931a5"},{"version":"9859454fa6df442ae16cb0ac31d0c02a0a85bac28c82b9783e8f370adb33b245","signature":"f6b87832d9447b2e9d26a9676efe78dc75cff9279ee64a499f3e4360d22f2730"},{"version":"583137ad8d520191737844c217f6e5d839105c7ec976abbccd46060ed8cf928b","signature":"213e8f64d2aee549df8047a587e27018fee7674c72407c2a191a634d8e05ae4f"},{"version":"92f78731c5130df45847dfa1a46a00a27686891e38ba51f116c586e520498ee7","signature":"5782b5f14e5b6835f9effd28f0e567b0b5e5c6901453140d43d5f88f07c9928d"},{"version":"25cc87856525e88d4007f5f84251a00b6c47b90fb435ad8459037f18ba6b8a11","signature":"55dd73018b5b47f33dddfbd384f86789d5d3a081b0bdc1fd2fd0f81e1e4287b4"},{"version":"ce0d61b977618ef61cee89091bf0bc0ac139c64da5b41080486c84f0002e755a","signature":"ca04aadfa23178ab9d04e4e66d60d149595721be7e7b6bfc49ca32bacb93ec40"},{"version":"be2f617d92b80f8cc4e567b59cae553cecfa618a81b93ffd974ee7f2a94ecdfe","signature":"bcbd39c8414cf019ff5752da5e81763bdc747423be425b5f6c7b1a6233076f92"},{"version":"727daecc99427aba51aacdcaf9649bbb8104da32adfc2f0eb01ed3347a173078","signature":"78335135acb2abe0a697de3e5fec33bc89954af4ff036f25bb26752170b1a755"},{"version":"4cd146d14339dd6298c8a257f30e55f207f32c3709deecf4234f32309c2cd23c","signature":"9168d159f9f1ad6b869e06877eb05d2b3a544fb1fdc76b4003ff52f5ede6992c"},{"version":"f016ec19b667fa817360e6f772723b590c5a507b5cbc0de0a53a9635f1846a54","signature":"1b866cda605b122b9310e604b6a6408a903636b8cdc7a0613aa9f9ebb6c6e2db"},{"version":"f264d234b8645ae1bdd723fdeb71a0314d77e06d8e7f6aabeef77c6607acd56a","signature":"411c558ddfdef650e590b3eee15829ff8efc820b6456dde9bacdaf8a19b9385e"},{"version":"a47d50bd2f57719021eb5184bc1314ce3f5837f2f78c4d25858e15c721e07ad8","signature":"516fbe6606f98a2736d92faf0b928b6f1084ed15368ba3cc8f055ebec38fb818"},{"version":"b26e8bf9c6f7701c5fb76c46235e05380573408867c4d57f68000bb3f543937a","signature":"7f80d74fc54976e64175d4796a5077a43b3cd982619d82adc5dac2a996e6a3d0"},{"version":"8b726542035580da854bccfbea23223e0fdac7df070292db0856bc04cc3989bd","signature":"c86f51169bdd99d2a52f43ff7126410a099a75cac62538f1c1f78e4fdaea824c"},{"version":"96164479311e65dfb12975f7cb97fa997328e7f94a0174377f4d6b8884e9ff83","signature":"a107421e44626e27aee78ecbdcc5e93e37b9addb0f6761d5cf2041e5c249f5b9"},{"version":"7ac8e07828dcc1a5e01fee4cc13c788dcbdce430795ff0eab6d39e7b3c095254","signature":"533c37afc84f4a66e5d320a3d8bd4d8fa4d7756da0712e42abc776962f08ce84"},{"version":"0925aa935fdf941309523ecfdc70cfd5650fddf52edee72d25f53aa303fe0f43","signature":"14b994430a17c83325fae751d73b4b91dc638fb2a13b138935416812efc5b08f"},{"version":"f71d2d69ba2857a6ba490861f2ee808e7c362499c19e5f2fd350d2e48b990d93","signature":"fbf6a03985783bd32574b3166c2a0e9fefaef80c4616e8c1a709ff554cab7be0"},{"version":"85a2c2bee88d22f7366d94f7d7b2c78f36d1d67945e7b88ea8348175cabf3a65","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c09242a97ddc30a0ed86ad6e481998869972d43d60027ea2dea569dfc4ff79d6","signature":"0fe3236fcc755ecae3aea84e78a420d59c851fc19f1623254decd6408be9747e"},{"version":"15f6b22a1a9dcb5d6ae6b4cb465b0c628f5d065489e0250ce46921de4c343df6","signature":"80366674fad0d2eb8bac45ad76aacdf3112cabf2e032fee7755a61ee0fd9914c"},{"version":"ed078b6e6e7eea82b93d3e16aecf4e5264db34569ccecf89ea244e130a0fcaed","signature":"92c9c93878f36fe51e3431455c359340aeacd788cd1f7dc1ba24faeb4fa87d3d"},{"version":"1d6bf45b076d03144b3058c0df777f1efec117c18e32e691f41bd9787514eea5","signature":"ec384f17e55f9991111747d49fc1dec792ed0ef8f3780416b3bfd79f4f2178d2"},{"version":"0192d556d58c264cc8f34e445f124160bf192380717c784cd88c27349fcce061","signature":"dfaf8ce103eb00ebc169bd1cd3e26987962b4da62bd249268a3193f0a7b9f688"},{"version":"7c4257d9e5829e9da31d3a15b30507ad7abaa83062fb7b54bf4a406217ff8dc2","signature":"3ca35b3c39d9a46ce3eba317f661fbe4fdf88afe33cb8615f00ea04adc902055"},{"version":"7c7e71e5e39435b48e0271eec28ab242ed6f1a65e740a29932cb83b9e617c83e","signature":"321ff8aac5ff81a75d851738cd323ae2ba1c54955901b7ca936485d93377bf92"},{"version":"cc60fd980e5701b006200ca499fcfc09b7ac317785fe53307bc9a50fc4bec464","signature":"7caf7749ce99278db7ce5e5cb505f29d838da91038eab7447336688cb42001b4"},{"version":"f989d2403a82ca13a448a70d7fbbf3e31542848ce822368c0a470dc148d231bf","signature":"0fc0c1e35e42c295ddd822600742ad7b6d8469daa236585225e2cb4416abc996"},{"version":"d0b7e2a5548f56597acc899917e354c348407549ded42ee13332c83b5c045bfa","signature":"f0cb4703a6fe127422dea8d27cdf77e8bd0f58b380945bade496723a537d8832"},{"version":"26828d7ec809e32ad1bfac8f039b8d2bd214a648a679cfd522f550e1a77a8b6a","signature":"3743762554f6bcdb60b48a23d63898d7c2906b9b64917b05cdec068049b72343"},{"version":"29228a2fd8fa9e03243e2af185473f8abfeb407cdbe4f72ed329bdadbdc484b8","signature":"d2315a4871f3b1af40dc6e9ecaca5a7271273bbcd91f00496b0038c3be25b671"},{"version":"84ee6c19db9aebc0f267dad9f38b59769a344e20ae762030ba2d8db629f925ce","signature":"6a8734b879bc7d5a8fbd40ea622c74e40431165154b8d043d0d54e59081c26fb"},{"version":"1314a35a2551c127f4844fb29fd49321ffaf3701afc6ed7131c90833121593aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec29995066c87e2f5fc0d6779e5be2d40ea3cae986f780cd1d007dc76767d6fd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ab2f5059de5eb22a286a8bcca17d8803b1e866c115f2a9170abcc882864eff10","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc058abfc4773e645f0baf1ef95422e63b6c1d9962bb9d4c6540c12a650be499","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ac37a6d8ed49983b7045356b04ad84f58799843ea2afdc53a08f2614c11b662e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1fa13f317d3637fabb663edd46b39ccdc420e0c5a3913b7fa4e906d99497cb2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0a3ea64a69a40bbd8806c9e1a477bdf18f0ee91624c8d9c9a658617fca971a89","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ddac272aa9a5f0f716b38e2fed52714c5cbc10a6a4b60a2b5586d6a75dbd4a1b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"62fe41879cae66d14c865c973556b0e24a904d9c6445557f5414007a236ea56b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0e226d432a81a3d1447e3eef012f251f196e7002e654d35fae4342128122e36a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cc39c13ff8d9df5c8ef59f0e717e3b5b4e96f7dc05859a531f1f703718d4192f","signature":"e6ec51d846f163b420d420782dd42e40aee266aeff314d141b11a0307a86fe09"},{"version":"10acfb644142d4c7da056485bd721efacd6ee61c0543c5762862a88f4ec9be94","signature":"1857ecaad23982cebb7ec28e547ecdb341d40713e95988b7f7d9da4c20f9646b"},{"version":"d337b2b575efa0ae09ab5b8bb94ca907728beb48ad4f9a43c653c247ebdf871b","signature":"be1b859329d61b0f74fe1393fd56c1542d1807ddf5a035b27a7153c471f53e32"},{"version":"9af90dcfb3df248fa3f8abf701c073fa30d6ee7b5758ba4de460594c56e4af8f","signature":"5b09eaef203954c253a646fea5d827882c557488a4ec3fd8cc50493e9ac5ef4b"},{"version":"c44bd1c97aec9b3731f94e4ca33797b718f355040fd1a3531cd1cdf72a092f98","signature":"a646dd3345b4cc02b5dae88b89ddb10adbd4b4158ad8c2a6f72bb83d0b38ab05"},{"version":"bd82c97d17c1bf3ae541b1dd8f1de5230455e9dd0128afb3694867baecea1407","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9d116a47a60bd0dfe34a66d2a4857a9a73bf2406915bd5b19bab3d1f42b8115f","signature":"6d6e3b1d30af0c368c85a34df9c95d2f1318f7080ef2e5749aa4bdaf637f073f"},{"version":"a0460c3775eae1effe1641d510f8cbd74a3b430951edbddf3b8ca9cddf732bce","signature":"ff633c25e6b6144a8904e3f82d41783e674fe44816ac76c8cc92dfdd8a9c8367"},{"version":"6aba6fd003ec6b75e94e40335a2315213295714f33e38b4164aaf7bdb2a3aae0","signature":"d17ae8ee1e9f7c65ef6f4c78ce2b6a7dd5fd1524565c12e6044ba3db661b8ed9"},{"version":"b72a531a79d4cb645c43c6782dcccedaa609b2c7efd71547a56ee74fad0c3dd0","signature":"7350f43a093be766aba20830ce8da6d5e1196d3bc17184977283e038cd281fbd"},{"version":"53cc94938d41698f1994b5de600edb7e89aa936944ce1d2955720f69be6d460b","signature":"311e004a849383cdcdf5bc484d374e5c55b8494a7a0b86f08ae78a9aa7cd0871"},{"version":"259cc7fcae5804316e63f5d00416e69fe28d9a3bae59dd20c767d714626dcd5d","signature":"6f96022250225ecbb218212131161d0ddf026fd636d134eca2e2d4a16637e9ca"},{"version":"f5c2a1cb2d8619642ba9bd687227fe3ed43787235c8e980c34aa844645728465","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6632cc5aa98bf6e8dbf04703a67620807ad7ac96c006255feaecd6d98d37eb14","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0f4d43d34056d61a57ff787c29fbe5b2ef301a333ba157449ba3df4f0a45649b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3bb5a48b30edfb6665a89bf7df4c42dda63a75cfa27c8018f859f2281ea05d4e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4edf0a9027ff9279ede897f9c304c9f7e42c93170d2b2f66570698048e887ec","signature":"abe72455f516e18ed06bbb7e01ea1450572ff48cd86d4153c5853474dae5e8c1"},{"version":"d45ebed0a7af7351812afbdfe2cbfc7f88163d72bd79807532bce53cea6e9cb4","signature":"4ae59f31cbb1d8f65520a2852714b43fa800e651ddf50dc1a3e68c56d6537f9c"},{"version":"13bf5a8573fc1891a43ebea36a1ef5517d59f06c22f6e3bcacd8c4fbfdc0be76","signature":"654865d2998e7e7aa50e64fba9f1dcd717a7f378ee65b7e40311035c011e91ae"},{"version":"71722573d8814adfc67a2fcbcd61d3c4250e6c370dcbe927a6bbcd8d08471560","signature":"0e6ee02a5692f58fae9680a1c9b1dc94d3af9a97456ec14bea39bf4a9e5931ad"},{"version":"2f33f28160bfb02bedb63ddf4f6a8241cb2ff6967041643a0d7ee0909f75c3e6","signature":"e7d315801dfb219e04a94c847f0ae759b7d2b451783d38974a72e7b695436803"},{"version":"04fd50ba4fdfc24324446f14648d1c95fd08fb7c3f91b6de6a17ef503f052e36","signature":"b79d4edff2b414e35a3bd893e38505d7b8fee3cc678f7c8c06d3ded65ec13913"},{"version":"fa160d0c5713d8259b2648497fd70ba7c7b7a6602a840c574eb1c0a6f46e0454","signature":"4951a5459b063778e07d022547e89168c941ebe6bf458f07ea66f68b5f2e8de2"},{"version":"e74268ffc9270115d1d343bcbba879e819fb149e693a0e0524e1f321bd55362e","signature":"72dcdb99ca1e3ca76a476fa8bc73a89768a7404721c1ff2266d2c649bfb9e11a"},{"version":"ee82aa0ef404999ad87bb7a2baa1d75b0fd94aa2a0ff93bd673b39f7901fc37d","signature":"080b3addbb0d6625d7af627d88f46c15af2dcb962ca35a4715510d924cd470db"},{"version":"9267691f6b1c001d1ad417d316eb19e3448db243cd5eccd9e7fe1933dc80303d","signature":"a9674a62883f5e91daf466b8c3688f5bd9b54750ea57cc07a0318e56edbb9ae6"},{"version":"c256e102702b489676e3738666b34d985b2bed2835c1c6a7da638a2442ac8d88","signature":"70ab92cd22bc23f6f464975b988f1abd8fa3c78cdb14f620730d735585761f93"},{"version":"1fbf86d5c06434863bf58d1e0b464481274e989244d9553ff867d4f742ab0832","signature":"868c432b61889f1028f7b0d5ac70541268dd34a158b7def3d464c0ebc5a0306b"},{"version":"5bb0181380d7d4f24d5b59efd31845ed2835be7a0e6ee2fa113735e2d14f7be7","signature":"98b45d50fc16be69aedeac7631b365ad44e5c1c85f8c535df06f90199d43e64a"},{"version":"42f84fb7fb1bdea79ffd6b67b36c9906b21f0457783277abd39c047f053b3e42","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"65c5c1e3cfa7e96ddf00b29103d558810220aeec2c5e15bb281ff6bfb7e61148","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"279e1abd50429cfe84b8dd7cb57e9684d8ca7864af5c3fcf853efcacf680830c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"09687d78bd8457aede9a7577ef5f833caf9718f2cad6020261caf32b7f237845","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dde235d0a151d2c8ea509d77e32c185b6e3b996563e29d5817af11ae5c09b9bd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9ee3a3696c5ab964b6ba7d41121d5b4d91ed7d70d2ba7cf0dbdcfaa617d19735","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4473be7e8a5b994db4e58c3d4ad7f50b91cd00c3be1c8c3a1867c0fdc08a6e54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c22f3ec19a761c9989950f01e38fc127ef63f2c0a3300cdd0b3b54cc28dc75c1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a31abfd6a1707f3d3fa8fcd6380a7cabf458285d7190030215d8a92b0c360827","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc5ea5422be0834017b7ea3550c58d61ed1f7f976feaa321634d7fe60a0f26e3","signature":"117ec0eed14f00ef3524ba8069fbda8cbb45fde70d22b16ed255473b2108f1ce"},{"version":"8de2610573a2f40a3405defab8d036042b069cbdaecd61a2b813fd75f3a2c3ae","signature":"939b6572bef8a2c9bf87136e11498758aa328ba7dbfa32b7387ec8905cb0744a"},{"version":"22d768ed04ecd7cea3fc40851466b04fad6078e979dc2ea835646413b2a05acf","signature":"91da61e42b3cb07db395436e29d0d6569f0ee7755098753b533c9f2b20023e98"},{"version":"0ef3b705c81fb51f3b20c828fc50e9d2902644ce8343281c7a5c057da23c5f86","signature":"3c4e06cfccaf61e890399a0f86638295927ab217e0faaac5e8e7c2a830604f9d"},{"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":"4e49c2a5cd5b413d6f345797cf2db9b1de533863cc4ab32c4de16d4866480867","signature":"4ea82f415bb35563eae553ebdd9cfc541d18967c536d1bd821f38ddf50836ec5"},{"version":"b49285ffdee55942615f0dbefbad0034203e214cb288d2cee09d3e7b011c92ac","signature":"1ce3453cdf163e11309e394025bb62220b69cd2db35e2d0fa33e14cf38efe226"},{"version":"d46d48d5ccca19b55042e2d48a773fb97d0bb9769d9f457112c8273851b84d0c","signature":"7b3fe3dc7a57dab64ad89df76681f912b6782a94c9bfd6f8db407b657c6433dc"},{"version":"565e7c8592a98903a22c5caa7be9df48b5defeb0f9dd5c95cff6cc02db46add9","signature":"19f13e301afd7de9e6c815b06b16029cb6ba524d50bebd2b381b4b5009521f72"},{"version":"6e3cc8174feee7c91df7b15357a2a608ed4389ba83455b70278f0ca5630cdfe7","signature":"1b4f6432935df03e81a8939fb7c4a6db593c5c4bb564504599aadfab1addb27d"},{"version":"a4de4b6980e14aac3f0c46ad86db25711cbcc735d7adb7720af6995fbe045eea","signature":"76bfe2b4ee9eca5bb254288b19e87b463765fd1a10b33269c4d134ad898ad9b5"},{"version":"d15c20e1a122549fe870b5fce2c85714ec80a3d3b74f2536bb8a3fcc20dc4eea","signature":"8eb5d3569824aba69bac738c173d655d8fb61b8585dc4417a04c591d8e1b26bb"},{"version":"954ba07c66ad24d7d4bb222993578083a4423c0f92a9bac4fb9e736a3d4eb813","signature":"0b4872603cdab838437f754c0ab373796accb15efe9d82e3d45782ce193369a8"},{"version":"bae8f47ccab731836cb7117c0a8609e8c02d0addd4fd4c7009e8cecd476e818e","signature":"723cbc31e62b22b09eecfc383ee07ad39e535c9f332b022fd88ee66532c124cb"},{"version":"14a5129ed9a94b8e4a84095dfbc088e5a713ecdb391ee5bd7b0a733e64d69301","signature":"d9bac9f20a21ebebbd29475f51b36635cba15ef0ac64757307d85a4bc3eecf79"},{"version":"8166c477f254219baa01afacf9e1c7f90a4afc2efde83183553b666f582fd1cc","signature":"f4c94ca77daf02588f850cb2f4b5a1ed661d547356c7b49ddb688df1d19aa9a1"},{"version":"af0cf510af3d03a0b9fe72d343822474a7fb9d983a5055e6ff3230b7b5be14af","signature":"fcc4eb2a4b4b3c403097e96ee78482251afad86a6ff172e8104717c80c1475d7"},{"version":"014f38ff04103744e6afef3477513156f3764c2b716e29dc06654ab68ee9b20e","signature":"db3297cab37c1e9cacfe2a3592be82a2a209d1dc46b80256578f8f2caa76a385"},{"version":"b46c791b78356c3de6414d22095ab43fd2963edba051f4286b09859fbade6b8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"affe446b13aec93b99f351f9ee1e431308f5cdeb18f63302cf24cbca89182e1a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b57647b19d10fc0f2b076f761caebe20f272aee93647f1bd8c9978056211a07d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8d2915aea530942db273b35206c1352a68a651976b374cebb38c01c27dd3160","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8f8601853f7e61892c40633650d5ab6fe8a564f1f9d6172095c6ac1d544e5c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d0fa6fb03d1d5584fcb167aad7269de2625bba64cc45c92d023558309bfe6552","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8ca0ab1ccfa1229324946caa1b9f3cde6e5bc13c68fa5423d5424920c4e71fc0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"041ab714ffa87e79e6efd79998e2d537af753b1450011b048f673d2f550ecc50","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5fe27a379f724d75a3db7c7b0745f6a14140ab74934c6b0331f08c73171f9f98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"715026154864fc6f3fcc36520f09209416001d92d9f0a244ce52e8e3c9fbf0ca","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6dfc25ef25b3a57faca3f159634d5233d8fc0bf9d77e7e71b1eb3e0a3594699a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5b228259001804ec6228a9a0ebd02b8b549529319be68558801a8d93cd50a5ea","signature":"ad332b9a10d0249b8cbce5d8d9c10f0ff8f585d909d8c4b4987437c15fdb6569"},{"version":"662f4f9aaef37a862d00552a59d1aa314f681e424eebf9576b16df78419903bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b24944dbb9cae7dcc4282a42546e31fb53bd8a2f2cc7f8ae6c272d5924a2ba55","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3b1cb47f4d87126cf3f2973da87105edb404a1c98c0aef3a2a289b98fc879029","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1856c2b5c7e6167bd7869d46273e730aedb23f80c1fc013f9f018cde1ac508c9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0387f0bfbd708bda5035a03775563836aa22508d2459e017f75b415c5f6b3452","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dde98beb8bef53cee95b020cbfddc90d0012e9d98fa19595035191cd7d2cc1ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d06d3fee2b986f19cca9483a4420497ff3909f6487e229467e75e62e283161d4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f78fab13e0f5ee19bf3e2ef18b5ab38a47dc60899def7a82dc05860915155308","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3eba367c1921c8f9e7f231a941cac022824cb666e652cbe754ac1e50804cb11","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2e0c7c56fd6742b25af440e2a83916cff12be55ca6c91f899f1b4fea9827a69a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0257b9ac5edeb935209106b79f9b4565fc6bdef9f2b4c5be6bed787a60ffdf1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"982ce1b9e222732ba63b4312ad1702dc81cdf4e277bea0040898e35fde086058","signature":"1af3e359f2c3a3b25e6cf0532c9b12b27c8ade0e7eb582007452ce06db289d9b"},{"version":"86e3ae5677e1c3559ed9078231e1e54111f3bff63b1adea3d14ff48d76e2ef0d","signature":"bf3ca96bc59503b3214f0618af79741f5a28de7d7ac663c13578af8a12fdc385"},{"version":"169351f1ba2dc3837650208074687baa7b7abdb18e43d3547565ac0ca05918d1","signature":"dbd0c498b5edc07924a5f7ebf0ae90efb9436dc2229792eaf175e16c03248f98"},{"version":"87bebd1c58e39741b573ee8d32b69b74d318782eace1a9a6d9f80586324b2d3a","signature":"800a7b9dc46ee24c46db2afa893e0bcf0b8c8bb1bd9b8db5361e34fa3c2aad18"},{"version":"06350267a4b707023301d6af8e0011c9403dcfc5eff31ef7cedb13d8f97c22e2","signature":"b7b254f81d9a367bcf98769a957c2f8dfef2267573a862b52ee2748647af39e1"},{"version":"f1844ef5a7fe33fe520fb2ea00ee03f96e3ed528392ca36de5f42964490a70cb","signature":"d949e121e0b71673df6140eda41341316408ac47555ba72b02b4abd3f0b6bc3e"},{"version":"eab55adf55f35c0e404ef2ed03340e5bfbfcc9f8e631c1ccb99d28686b79c60a","signature":"dd569f5b0cf0ca74aa2b1b5f2559d99655fdb41881b534f6d27e226903a24880"},{"version":"013936b36694b71563cf2eeeb8a159cfe7832432802c33a2d991b0932c81a36a","signature":"7bb19ff78f5dad94999cc2e0debb65a5ef8812b4507a7652b0b3fc455a9e8ddb"},{"version":"80ea52c65ce80ac3d8d81821de8e8675a7497210ee37b23efa79f21bd57fc86a","signature":"d96bc2df413362d899c2a26a8be1fbf15d38eb758a9d65112d7eee95611f0bb4"},{"version":"a384e31103a21be8505d837fc43ff1a3653f70ae795b4f46d1484bd9e2623301","signature":"214644d2fea678926fe214494d5b88720514df481a09f665137efc5ae653499f"},{"version":"bc2c8875db5a1430437c82f060faae49d0eab2295f7ff81c5b82279fafa8394d","signature":"5cd36275e5e2e7c71e522a445740890253664d68f28df4d62a4a13c21e3bf45b"},{"version":"ef40ec2b5dd17a51783f18514fd97931d6b673207bc23dbab5914003fb90947b","signature":"ea58ac73dbc859ec9bd2c6e497b70d04e0b87d52aff26ec74c0b2fcf0e4d548a"},{"version":"414844c14d31371280f1024fdc10ff268455384385eea30dc5ba252f3e4fbeb3","signature":"da8aa5942188ad3147f0afacf4c3f11b24942ed40114ac1a2fb9444119d69e17"},{"version":"ac002c49c6dc6a9a524d074a6f4c324cbd4c320e222eda80415d53150d3b10a3","signature":"ac68bf7e24525499431c6bf39d62b264a7708d2393d1aca05a3b8d153657b2c3"},{"version":"7516f8012b8b4fceff405a25b09facf3eea5aa640fd6bbd91c169ef0ba7119cf","signature":"49dbff2eb0425c00c48128b4ff64bc5c8ec07f8aa6fda343bfb9302a2398392a"},{"version":"61df7340e77582676d6a10c309862970af30ffbb6cb10e86b49005764fea89db","signature":"9a1fa87e956dd9e945a728ecbf0bcdf59b5bc35bd4ab98618c8f51fe319ae756"},{"version":"547ea3ada84754869bc28f5822c247c0525383c3d8805f342a512ac2ed139f0f","signature":"d3fcd7e5c042241fda26edb5e44b7987838092f2c30b9fcfd5fde8cc5af1b958"},{"version":"8758d5e30c12540491d40282af28875a47bca5b8bd5e7f3136ebffb4d57a86c7","signature":"30c7af840c72864017bd24ec10cf1173f1c643359a9feffa51f6fa141d08850c"},{"version":"2645d9404e6f5fe66144a16e69d73d297de82df97280772374e42d514a6a075d","signature":"799433a95f4bbcb14479e6fa908d6ccf8c23fc369fbd7c6b5143026e698e1156"},{"version":"43ff1f43dfaad43d87026e3a953b74f70368ec1ba49f67eb8df40164a4ba3056","signature":"4a26aafff5702c778bf7349914063554cabacbf4b74ba530aea9fd2b5c060e1b"},{"version":"c954faf290c5251991902e64a51f18bf0a99836430e50c38126a7ec753629bec","signature":"e5d7539a72d07ef9c3d686776f885111a063fc63c60c975d027b6b643970a358"},{"version":"be035cd5d01eb15b85322a205f090f64d333dc047ca1082de84837dc31c31d97","signature":"af8541ad25caf543ae81e642a69d481f7bb2d0b642df88c46a1fe8910626a935"},{"version":"4e9d357be94551ce5e2d42ed4b0ed6546e32243ef0504cb771919b1f4bbb586d","signature":"562105feb1d69fc9516ca37ffc5e5af73d1339f62eaac2693c4856b3a06f1a21"},{"version":"5dbd0527243a6d622ede33b461f27551614d1d4071c9dc1b246a7cc9db850cab","signature":"5136f18880c11778e02967105e9fae9a0482deaad8f1583230676bdb59fb7ab7"},{"version":"3ad1bdb57b05fd29dc468a42e71c4ec8f12781a647edf5029bb60f5a8afee701","signature":"5ac419d5eeb2a884c1d260bf31248fb2a853d3628aa0d7c3a99757ef99fd6c2c"},{"version":"678dd9537cd28a491bd13f7f3177c851120fdf39f27e9a93b349979bb21641af","signature":"e425fc0486e0242cc540bab0d335759f9f3f7ddd1d8ed233eeccfecbfc5aee61"},{"version":"0d010c0b5a9166166771c8c48bf48e48d9d037de37903d2b2aba860d1108a2a8","signature":"137de1e22724e42c5f197f61b17ec1264467852dd37b27811c311c97d705c138"},{"version":"5d70f802801ac149a5830d8877cb4167e9f9913f7e788079e587b5d63a624d81","signature":"dba53de0cd1e77ba275a61f6203783f10a2b35ff248306fbd6d9689303d50f03"},{"version":"aba095f915652dc697979c0b9ca5a3111b7160144f9a1e18efc81fd485ec9c3f","signature":"f19a0c7e1142fc0502d9e0014961ee6a6fe8b9fc26c4602a72aea8c904f15349"},{"version":"fd1383591235471dc0499e38fa8f0be6bf354c4ca3aef2bd052f93e34c821f38","signature":"2ea178cccad298208dd3300fecfc1e882484d9fdfca4a8c473cc345f0a34eed6"},{"version":"2e496f56e091df0cbbd36aab6e55e16c37efc31091904e36e71ccc3c34cd4825","signature":"72d589144cb568b0b803e59532ac2df4c04998cc89d175d259815ce6a1acd5ec"},{"version":"464597875def0d40d6a0ea4bc5be50373eeb35af3023b4901fc539c19fb088c4","signature":"b8ddd1cdd822f53a7a29b4fa58240afd0688de547a5c640753bfaf99a37c93a7"},{"version":"560ec4980f9fdf84e4df149a31c676fbc624df75f33af2159b30aaad1d624506","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a31e889fd61c82203d9d046edf845eb54c12d63b13d2028e5c1f16c26c5a535","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0875a51dcc08220ab37ee44e0d604789fb0c24c6435826eff6522f9af79faf6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b8f60248250f207cc5481619bdbee3e9ec7645ea74d2b257bcbfdc27037a68e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2f9d111343117be248f5860e96c68b5c55e402894408fbbaa4b031ab12572474","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e8252a1e88c45e4b76044e3ced48484fa04faf5873eeb2a15e88813fcae79808","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bae7d0911c58609a404bcd7255d5c80cdda6d568c3b95fb189620ed7bad20843","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23d90e3d7b8e5a17f760fff35617a57ecd7b7f042602b3f9dbe314e938c77330","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"00ae1a801699b73d425782db51a2eba53741776741421dc8446480d09091377a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f89a50a4a14e6ef1a1c81b263997c2d728ce5b56bd1d93dcb907d57114ccf955","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"830b21dc28f068d3d362d407c17d010f37a9a29cc412527c274b8254c448dbde","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4492c3ccf40d889bf6eb454af8a7fba4199af810c53d10ef8d0bcc16156e72ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"60efb61013763a932a1ebeb1ebbdfba609e6f2483bd2def49702f82decb94fe5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6fb49bb8359a76bbd80e39616d5cc6de09d3a9ff938cf58be22c155e8ff42916","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3cc35b9a2be3c9593d41df15a4fadee9a4cc7c145fd9d8dc485ab95eb1015898","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"effed161b9f183f637fba8f96864ffa67bbad3a3339b18d9d368438fbfc00bd7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e2f2f7c6411243d900d1f4199d20c94122f03d61de95cd5435856a125b91d134","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1afaf4cea2ba6a2e1fc5c696e0420842fd52a5a024f77fd63106e46f430d42b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d872c4f980d7e6288d80742c84f1dc087a0ec7531e18cdadfb47448a669c2f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"179703b328f92994e719755b197ff2310945583fded682cb02b88aaaec0b3d33","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d2e752aeb02ae1be73703cc7834f9bf1de14b84d32121fef58982b29bb138019","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"954fa93b29dc7267fecdb55b80d28bc943cf370e0165963ca051c0cc6899e114","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc8201479e29d49966186df4e5c359d507dbbcd4f772499b365e6836e500bde1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce4cf241091329ede4bf94c365874f20cb8309b02ec32980d9bb47f6527e86c3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"522ac15e66211cad975f897a7eb70e77ba20b34ba8f9c4babb8f75f37e19c24d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9bbbbd6f4a35a22eefdd4d13b639ad27d2b1316a6e833e262a126d4310d904ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"80c14a78262fb095d375cbeffe6a6b53a300098928410181ee1140a3a8869a47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"66cce2ff73442b6f95408d5847e2c8748bb4e47e44334546e94e52be58c0d163","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad628be53a44b47262b560ab15866282ad4d257f2f214369e5f8579c84d503d2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"98021cc3dddc6c8ce5498e301424f2314a9f17deb130c609269c900716a6c129","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d4011e0ecac2f22d7f639baa671cb23d19be79b0dc64c1cedcfd026469e5dd46","signature":"444074570bf4108baba10fcc87aa17bbd8f6661575c2c6784199b147faff4e80"},{"version":"ae9060e96d1b1a768ceabbc133424cdaaab0b34210ade597f476ab2106964506","signature":"ab27ea6c0a82fdffe3cce64f5731ebfa2b266fd04fcd0b85e27bfe26699ea15f"},{"version":"622e6f18814b4016fe1103587063f23f819c36bd42158ba3011eb2e12095941a","signature":"f2edf0a07c79c131987b90c4fd6e571506e2de2217b1f01edd5440c94143c463"},{"version":"560ce56fa31cec56226446c1422aa0861d70ef0b1f92fa90d501eebc22fc9f3f","signature":"01c8666120a2b10fd3ee7bab4731eaa395ddace6a8133ea44f410db0846b0f54"},{"version":"72845acc3528236682c69a7b735ec85ed2beea38d8fd4c0b1e7779521b5d4ef4","signature":"112f2f56109f2deccf3073df9c73bd87c6605a307265aef3e247e6e2bd9fd4ca"},{"version":"563fa16b249fb0bf5ed14f72e40b6ead283ccb254dc1ecf0304c0165ffd4dc6c","signature":"506df86169965c18acf5c22cb324fcd3460cfe230046f06de7ad63860e014c1b"},{"version":"42d5990e1bb01e3e1c48a522cba12b25bf4a3b87fa7490bf69d02855c5019390","signature":"900375f92b808a9c742d612bc93108ab61fa9adb4b4cabee9b45a7ba8d30dfd6"},{"version":"92d32911e086e087141b1aac3b7876089e26ada9ca8758a91280a05b4efd3a7c","signature":"6d53e68963aec64794baff110983e875c60a42a3e3d1bf17ea385752c914c1ec"},{"version":"e1c9ad51c0bb65697455db21f2d0a89adfb65c96288dff8254d8d8a88638f671","signature":"b45cf13a19ce92456461eb346ffb6bc8bb229d8f04521fa539a761470de6ab30"},{"version":"7d116f501462b8c241b1c1d042feeacd041b9c48de3cca53984e539d2eeeb3b6","signature":"b61620ca847f6b7d40ef82faaeb0dfff55ef897fd2ab60024001a674f4d91e08"},{"version":"fd3e19108e40b4bd6502bcd08768a75693473f1ac31649f1f4ef6ffd7c88d36f","signature":"0b2eefc3650c7cb2c277d27ea3a3290f5835e2ad871b17041ff92843b06bf99a"},{"version":"b14453b02122266e37e186d1935cd337dde89929a1417cb87c6b962b39af0d36","signature":"d390eaec15d04e3957d9c597121ff483b28f79e54db4c916fd164dfd70372e82"},{"version":"7247f127bd65a32b0c8d5f84fced373fae4e4c541630ae7b05f1eeffde33b9ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20a066d0baec26f8ee4902ff7cc7afdec57496053b60b5d3bc5c85732a14597b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c216d4cd926b1cd512c039ee12dfdca10a292a08b76ab11198dc2293eec74ed5","signature":"64718cf0d577ae9ed2926faff603162ccee149cabf0f7d6c3d2eff8bab3f54fd"},{"version":"c8fe61044fac5d42706c4c8854e03e5eb073792202ec4e7180f7397155e34f9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"970fc7d6d0e9d5716f75362209aa4747f46dbdf1c601727027c3a8b22a9ebde2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ff33e494e30ea1a42746069c83751d1a458d4d8b2336df2c24089872132a2be1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cedadc70983ad1d699a7efc9abd8008710dbd21449893d7273795f68649a4547","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89af2ec4c45ffa09d872b1aaf9a98fc0c6ab8cce06c5bd6347d80ad8faa69f3f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"65db26f870db2f36af509737119f27bf6fbcfe7aa413b169dab0f6215758243f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"52977bca0c3c391efb84678029b1816a997a5069d91f8de7277079ad39b00c53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d15dbc0f96e7b8141a77749e01a4e920a5381ca32a2aa58132bc5f7223f291d2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"544ad2754ea5eb052e793f75425624b7522f638801fbfe50cc252e8bda11e0ba","signature":"47aeb932730902d4d8c41ec941269a416311f709495b5d20f2ddeb6f8b483073"},{"version":"376cc50be40b8b36c55a073dd73a382908b575f74972789cdc3ccf786bc91d30","signature":"ac35cd214f851f08cfe6b29eaa9fe5780b0f124b1b30c0fe772a1d7f8b518d1c"},{"version":"f7483014a4b848ad25b27073b061599f520dcc6d57e6068e7ac647aca74eee12","signature":"1416dec78fa5f6be6be2e406d0cc50d6f98ce0c77dad79080d5e5a80be18084e"},{"version":"dce5de27f8737a33d7ac4aa50f6874b6cd16bc7420b3a9b97dd3abfdc263ae68","signature":"e28422e9a6af42ba47f7aef0833e002816fa287c438cdfb33754716547da6bfb"},{"version":"b7277bf592b4832b905af6bdf6120ea14f1b9a9210efb7a4ae3803a87287150f","signature":"3922dbc2ce29d177e9d0c1abe636860f8a1559bc545abd790c0060803fe2e1ea"},{"version":"9a106f225ee7bf695ed69744df6cb6982083b3f9c2fec9610d5ea74e2e49f6d6","signature":"9dab80bdc4cbca67c3eddb3cd102b87f111b1e4d1ba1b3a0e27a38258e31e426"},{"version":"d1df8b7eb29b69426f6328b503a12b4408d4ba4a3a305ada40af859fd0d1542f","signature":"c7a00bbb89a2cb3e0d5521251755518d81666c561cfe0378d03268570d5bdc23"},{"version":"71590a10d662a3f420f700c10793764811c558938e36311c61eefb13033a21ba","signature":"4645d41794484aad552e40382c358fa91dab4452296914190ee23be7af3970ba"},{"version":"ef07c47a9f22bffbb585da6ab96f379d97e6b72fbb658e78c03b11702cd1dc6a","signature":"3896cd910e22538e767007c0f988c5967112fbdd06cb050421f37d80c3736229"},{"version":"e561876a844b5d66796e60c5374a55e3666d17b8026012a3de1e78dc03e045a5","signature":"14001c191ef845d0b28e603f563f4c8d73166db39417fbeb04d227ab4918296a"},{"version":"2bba20822fb6a665abb0944bd00e587f93272c5aa1b2a513eeb9f2fed00a7e7b","signature":"785da1f883cb1f23d0ea0ff209153ea69a2c92d6fe7cd29f8c60fb9776e679fe"},{"version":"fc9258c7768321dff71ae7ff240ad1b5a6b204acaaaf8c088d37f1c4d644f20e","signature":"cf15966bba8aa58508d7159937e65485e4a40ab41fa2accefb0598833cef3af5"},{"version":"78fcb0f3d8effb684b13289aadc4680cfd9ae3628b1f39775621ec50448f22ee","signature":"677d31b96b2ed39787da58e41524dac24a285d4847b9413d4ca54e165afbc66e"},{"version":"c78318850aa730e4e4d4900d62112d87546cf76bd8c0f8a5389de62a8fab97e9","signature":"99f2cb08736fb3f90c502f329487966ef577077791b334b1d4ed5f0ac57a4e86"},{"version":"e21face6fb69732353a584c8ca54d4c4f32840b9d976bd2ed16e6e04ddb1b689","signature":"ff61de4e1af35108cd592760b2ff1a5f58eef3a4b29f4172412ef408143d3ae5"},{"version":"cb29061301b3205ef763d9159d82d81dced1528068d82d9248aa828b293473b2","signature":"49e666988a2e38af956233e189f9cb4667ee80b4bf738bd8ab61b8cecfee2e45"},{"version":"47049411d18af8a4afd89d6507e87fa1e1f761cfadfc49db94ff0fda85e2db4b","signature":"696d0b315d34950d1c089eec0c54c6ccdb7f2c19eabfed730b115fc4f63ec0a5"},{"version":"ac87cb8a327e1a7e15b3597bf1ea9207128645094941d34c91a4f4294cb50c38","signature":"834bfde39ed7879cb9e282fa632acbe344fe8d7efa6d01d05c6c6ffccfe806ea"},{"version":"897ec00e887fac246f9604b78bc3434fb61b57f6933ff36c8e5167d8fb4be86e","signature":"b68511cd2934d124b9d935ae94f90d131221ff96f95f2eca61e963926cbd69cf"},{"version":"2aed22685bfd84e88aedbc3c1d0943655e851ecf53da220041d42fa63e52eb10","signature":"51b1d705ced6ea26b44f528941620e1b0fe53538c5a245505f55960ebaef5dfc"},{"version":"b552dd1e51bbd18d0d9b4904dee01cad165d7f0d3471b42f00ee36ca78cb81d1","signature":"c1961b1d48bc6a1c7f3d115979d6728a6a8ac59869688a5bde08933c18adefc5"},{"version":"0e233393348d2a4b45ed807756c2d967887d7f689c522c936403b137cfe09e47","signature":"def7b08005b8d1ead7ec1cebff3703d1edd146dce701e3547a25d6f830bf1b6c"},{"version":"b83570a2939d33a6ecfdd2766a3e416ba612d0e8d6f83ad11156a004fa033c77","signature":"2d58b00459deb63b953324cacce6a50bdd0b7d7487eec5e0cb52c21a94e13212"},{"version":"3df325ba47f75517dd4720f0dbf492a07168a52264fee2acec5d81b68551b323","signature":"2141658926fd33244c616646eb68fc34928c2c3e76cb2f0fcc49f66b7a6c2e71"},{"version":"bbc074919308daabfa9d8eeb4636d8d82db13ecf1d497ba88070e64bfed7c0ee","signature":"722928e1b1078348b1fae3539d0944ee9db279ff7731a5c90baeed1a7afdc11e"},{"version":"a6bc0506fd785d58fc01916eed093992884c77047de6ef24a1984e870958f3b9","signature":"6c67fa30e0db9490403ce70c9bd112dc16256f74e793f48501b0af56cf31c2f5"},{"version":"ccee7e5b2e10837265d6396acc046566cc79da4210d0a50e1f1795cf86b47ccd","signature":"37d3ec125450ce03a992226a8a4952bdacacfa88aee5d09f821236d9c63b20a0"},{"version":"419ca4ab657409f45b8db6cee2d5d6888a2b08a2fba70ddda952d968c69a16e2","signature":"072d63362c70c19e5647e1dd12ada4492213157c48c17ccd13a008f9c6b4a12d"},{"version":"8231663779bfba7f580018479f74d02df7c9160b3e8dade1940a569ed9d80ac8","signature":"b19e055eff7a9ba8d3416874c9a679d800a5df0a0c5219cdd5aa5335c6b8b072"},{"version":"7bcbaef9af060a63375362bc183bcfa7633039fef24e75cfcf087591154693c9","signature":"0537610aa50477efe02a1d81e01f834e498a0db8f1bed3289ebdfd4775774c4c"},{"version":"3e41afb4ef179dd27f926c46f913abc3bdb66001716ac56256147179f5f15252","signature":"bf27ec3588059046cf735262879e81aab81ef0d702250e085ce6c07b17bc58b8"},{"version":"a0cbcb3cf77ff16618753dcd282cbac664597f8d6d775370609f8e128c598407","signature":"0e094d3f18ed4a44baa44ef3264239439eadb03b3f8e2ae278d766c852fa0754"},{"version":"ca572f8634347350a00e96f47b44ede6cc62545d87e839f959bff12bee235037","signature":"bc973f44ba5c54e1074bebbecdab061751028be94340dcc5481d06befed1f855"},{"version":"889c6cf5d2f17ebbce07ec946521f4f1a8fc55d9530b4959a7ae954ce7f0300a","signature":"170e54c7b03aa71a92de1afdd4cf56b47c9df01195f98a8d933771b75ecff8f6"},{"version":"bbc45438a3de93d2d44f46fd0cbded993b3f8afc82779a42d0a6819a10898fcc","signature":"f376da706da2e3ce62334b6d086d2d91040531879603f039d3cb7682d8d889aa"},{"version":"c15fd275a051a6770515950834e07dc22b7ebce6a9e8a93bce69d67d92f39e40","signature":"6f166a044fc4fed85f167f611d19bef8a2070e23281ad7aec01e77836c80287f"},{"version":"f2733e4721a9ff2047d46ddf9f771aa053a8f482f93d4820401d0be58dde660b","signature":"e3afb59a25c83c15f7f195f4fe92300ff8710b2a60850e9f869a07ec5a228838"},{"version":"6026573111eb1beffbdd6e3e3609ea711d4c2582469a258b7cdb6e4d36aaf28e","signature":"10e113ec036dd44b69d961f2b6616239ccf7f025823f75eec292640c3b4a793b"},{"version":"7db8deb452f9faf63b51a33fc3a09dea5a305e4dc231b770aced708f902dc7ba","signature":"2fe7ae68eac160827cc1ef3f71109e12ae1ba4c407fcf41d653877c7a3008970"},{"version":"8dab908f81bf0eeb9611fbbccb2508c2b4a8e1d57622968cf98993e878a972fe","signature":"b01970e81b7e682cd2d51def6b76c7bffa451a1b58fc54b528629c35dd89c9f5"},{"version":"9367e99e6028dfce0d37891b19a17bf1a3b04fb2649a89ae7ea832ffc7507b99","signature":"ba000331a8a0915160cf82ffd04d583bed6ea5547a117b8d88ed7d3ff6eece7a"},{"version":"9c9c3a0c8c7de68218d26f06c8a16c2901984f7486f1b2a494454f70ae694cea","signature":"24203ac0990f3fe71e252d5ac9b73e0c6e4ddf584be3cf38d14b1bcdec5176a8"},{"version":"3ad4c8f86a215282206449b63fcc1ddc7ccf6cb8e5b80a04bf64d92d2aaf6fb8","signature":"525d97773ff298b2f2fbdf14a791f0587ea0172827b6ac8821f3eb70b20aaf1e"},{"version":"24892b8255b88ef0102847ef8b231c6bfc0ee618a69b17e40ff1438f9997f2a7","signature":"59203658389170eec22beaac1509a33cbbdb6dff49b69e34593aa96c90c7de1d"},{"version":"d644c33e3d80969acb5b187976c8cf99eb0a259f63bcef80a6ee38da18e83247","signature":"2769f26e263572cb6b16ff1b24f373ded17e74e710e33725c98a22a0b7ae79b6"},{"version":"314650281c03451fe80bb91889aec0b247946fd5b52a318d51c5faf64cdc57ef","signature":"5fbeb568fafddc09e602cdbfda7df5cd0e561ba1dd8443318f1bb3b586066c9a"},{"version":"a45b7bb6b8126f59efdf77d2f8cf8ef3483aadbe34abf5d03038b1876a5cd1c5","signature":"0ce70420a3f859e7ada24e14d433bf75f91954ea538e9f25cf32a9afe7e86539"},{"version":"fea326b4e1f45f43699900930e1a685557728c14a394fd847072bbb227f7bdf7","signature":"e3575536a31286b081d4db3ae027a171f9567fb73765c91c67550cd330650e49"},{"version":"656330b9d0697dbe04cb1d8b8402b3ba3953dcf48e4dea01887c992036bb173c","signature":"24733ebd4c83b4d7b05b39d79f1eaf60c6edfc8f0da5c2f848b01517947697f7"},{"version":"8c4c8c4467f9519b0878333232f88ea38920588b21fad94d09e7d191c1fac691","signature":"5bbc828ff668bd2cad6f88b3f8bc1e85e3ab4a84af3eae83b3931bddb79d5d5f"},{"version":"b7567dec5ce2d27ed70feca5c5a53b033bbda727b3d65c1eac4d5256adf09315","signature":"31f22cee584992be54d06fdaa9dec55d060c358cf67c4d162fb2f5fc0c98283f"},{"version":"7354576cc5cd9410252734f2a40c4fff01428a753a672f354975a958e7c63329","signature":"b4444c70cf4bbb122a1983ec33c297027a05fc1dfc23376d3b140745a58a2ed6"},{"version":"3e8f5153df2b58ffc421a7d8440d3f92fe8ed9bade9a7b18bb0ed161998b40f4","signature":"15c69a20c8c5420b76b7c62d82cb284a1608ad67c2e0d1a71e3e3caf90bc4201"},{"version":"4d87ee3b202e0f2f91804622d86dc5cacdf3596c0fd62e4debf04d02ae25bfed","signature":"fa281b36685faa9c4a9d379f8a1ebb2f13f7a09f19e184becbc8e58848dc2396"},{"version":"6788a1deef524d1bb463645a178f02627169ebb47346eafb1a61faa5cb144333","signature":"ae6b5544fce2c65f20d0e7702aeb8e5bc2faf2c4e813c4ae999a44ca2d6b9929"},{"version":"2ddeb4d8ce27590153aa6ee84b36bf9764700d7260124167167a2d2a32166bee","signature":"68597f354e4595d3f1f99cfed58a445a66dd9b5d4ed5d8133e445ee2a3de6cfb"},{"version":"f7f56d7774204ea550efee0d9e05494e8df297bdf32634dd601fef7fe45f54a6","signature":"1aaeb72b56fbfc7fe35e5c5195bc4b102fdcb25a30fa39a8dcf26e4e03afe4f0"},{"version":"2ac12549f1ae0aa1775782876baa9c06e9d845be26d99ce56a36276a8831a395","signature":"39df2da2a2737d9f0561b052a23093c44d84bab8f276b5bdf2b3e41094666a45"},{"version":"79e36ac740e122de9323550df934df06c908a26820f221c758dcc16beade6618","signature":"24bc52911181a6e9ce7ccd5c8fc3b03b998f5a3ea71cf80e3c93051b68523ac9"},{"version":"bbe55ae5cb40ed5f38ecbfe673ec070dacc7e0d55dd02472263f7903b3c5ffae","signature":"21566e332d1f7e6c8890b6bc364f4d7e12afb504b71651d6fb92fef4d17835d9"},{"version":"671715f351a1eb2abe9ed42846a3c47ef495fbcc7c45891305b75cef89cf7dab","signature":"aaec910c194451376eb035a9e90aa018ce9121d164eefb346aaa3e19fb2aa90f"},{"version":"c10b1247cc334d64f4740702063dc4dc4251b96427e0d846b5eb9a7d0379bb1f","signature":"b204b8dc8a299d4fce994ceac613f46bfffcf4486c9a5b9f457f42f5f518b0fd"},{"version":"672ec17aebc02c37f3bd6a75778652f5cfcc450b0b2f4dbd8d5821ccc7909af4","signature":"0799f99f4e37567f2fe31840ff206efb30c29b21bdb0af72d55aeae15c70760d"},{"version":"6c6bce5fd86564171cf1bfc4122e6b4906a820790b4097c87723c2fb92eca8a1","signature":"1ad6ef3b1c1c48d5cf24ed8ff9b0a5a5592dce6ade6d6827d3fceaa920f6c500"},{"version":"7bd42610639a14bb0c854bde2d3bab07cce272b4f9699027258eee83d5c11a73","signature":"cbe7252f19d4397211500df1c2861e7c4ed9218b8d1614ae2d11ca03679f9551"},{"version":"b7b303f6ccc15e4db96956737e538d893c25b7092a159a39c0aa8ad932d3c636","signature":"c2f55b90471ad64c25a4d547225d37cec7d8f869fc5bb4cffe6c71a8b836f4b0"},{"version":"8b2eedc0f7bacc05c6f0b56dc41f46d1b06ba1b9868fe0fe77e8cb22bef6f2a9","signature":"198353c3f827b800288c5a5cb74460fd080c22ccc9881f49e9f5ccc59b35ee84"},{"version":"0c7459b35e2665327b17a7693b824fa83a3cc5647510a2cdf09a6635b4561c60","signature":"da68b6e91d25229268f69fa9173920364f23c6b50469e9e01e663e0de32fa6ce"},{"version":"674282b47fa4cca2df2e68b4a3674045b7a51ebab24287c15286ac61afa52110","signature":"0d3a59858de0d93e48bf7541964e12ac7d59c6d970c824cfa307bb3ea932f66e"},{"version":"e0c10ae5e38df160cb240dc9e46ac464dd22ca7432f783f75d77b1b0e1aabf46","signature":"a53c9821c1526959393efaf002082f8644ab2054f490e9aa6a0d23862d0ecba5"},{"version":"ae6c80232b4c2c4a00fa2f7dc51552a73683afd6acd88dc6c8a745cd39a823ef","signature":"b5184ac9282a657b51d247adf925cbc239c16ad3cdd8b4dc54dd369673e9a321"},{"version":"2a4f83a64245f53cbd1eecade0cd429c73f5b4e992439b773fbf2e8680ca4572","signature":"26dfe7cb950c6dacabb59453b56e2a71df14e67dc91ba3a35402e37a109393e5"},{"version":"3e0238000be6f6ecf94fff98c1c71072caa73ccf6c318c7b8fb324ff2903103b","signature":"ea3bb88cee2f2752e48e75c00ba80500d1ec9404160859804ccddccef1002ddb"},{"version":"aa28402c225fece07db6a4decdabe92cd905b329b6cbc2673f4c464fa4fa300b","signature":"37756386a07460ca40caec0a192629c709af57ecee057bcfe7c311f5da0be5b6"},{"version":"622cd7a5b9b304ca18aa1723952f21ec5c939f7c70431c1063bc25e59a912dc7","signature":"90c7406dcc6fe0fd8b0fa3e23b8b1440b2506d841d8e629a6b1df0283c8fd1b6"},{"version":"83837a404834ce7ba3f2498e3faf5dc31ae0a5859cec3101d684f824f8cbe3f1","signature":"3eca308a8adead7d78f165d89c01c30c4dbf141cfc5900a563ef47bd2b652a27"},{"version":"64cd8e7ebad2b8827d66171a80c2b516c5a57a91eddfe3f9b317faf8879dad26","signature":"084cd2150bfe1929b5fdad5847010232f8d7ed1acb1a965409d1009ab02b945e"},{"version":"68ee63044e87286b7a2100c05437babf550d647e748e3ee66ea6ad4cb268d52f","signature":"0d27b4098a7f8d9daca5e7f0304750773f03dd567b4d7d49db9a983be5a2e57e"},{"version":"ff60cb0d4b987911a9db25c4e372a81da6211e9248bf9eb336d2070b77771bfb","signature":"ffae22976581bd977560fb6a27d3aef9508d68c714c28c9be036c1ebe38f26da"},{"version":"187b3e36e643d6484ba0286c361eadcf4d3f4174865c8bb2e92ede5cfc0206c7","signature":"742178df5f8a476681d0544713fe87cee7a790042b11b27da9b102e80c318bb7"},{"version":"b5cc0ba3df9e33e2fa3849d474b4c558b77c854ec3addb719919720786f2462b","signature":"7ffccda9f5233cf7f4dd76c403921a51a2db0fba00c6d1c5156f463d95781b86"},{"version":"996c05dee2488fcd52dea0baa6bb03cbcbbd451bf22ca0982ffc1bb412ee5dc3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e83b7266b4bc5653f60004a5a07e2dd1484a92b256fded2dc1fc65e828b4bb57","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2853d5c65a6ad064deedce24ab8dbf06aaa5ce9542a47f078fe02f03ac7cdd03","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3383d105a4eb14ea4ed618769f30b75f90188e7935364332f7082793d1196b9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d96eb2f5d3802c4a877dde7cf5c19f3e938d792a6c623e806c9cb3d64f134d19","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"df1cb31463ecfb08b80ee1fb021dc44fe79934972679382b951fd13eded5d250","signature":"2af67711c0b92f1ec7bfe590266fb550a2a274b8e60fdf1a37d57af36b0bed07"},{"version":"40b19636fdea5f4ff717e2b8c783e06978d56ddf2e56cadc547203802f3ac0ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc0e638cb5f96071486da3fcc349b7f938455220ad96d4e80a1afb444b7fe0f6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b65293780e6f9a13e12fd16c069c51294f40e1e12a28add6a26d8205c74b17fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"82a1830c87e3d98fdd26f717ed49b8781d7fc773c0de5264e05e62640da8987a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e7b9731ae386cc1518aaa4172cab2116a0b1a791cd8ad34ffe09459f4574a415","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0dda19732a158aac112629965a55ef3a3dbb4696aae000963193b51fb860c59a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ab754ad0ec19423ea27bc7313015d6cf738360f4631148d2d49b9b87c0a46929","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1395fd01fc1397b94c1c12676294a680c57d649db4b3b28e1c260f0ebb541e6b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"80a9cb4a7a8161419dfe4d452a08286676b67d360f2bed4047942d1f76f2bdb5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4156d5f13cb167807cd3b50f1ab673c57ff0051d3c5ba40aa2dcf95f310465f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1cc522ad210d2c7dffe081392e099776aea1a12b7341387bcceae1546565fdc6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0eeae146f6113ee176a29b1625a3e63bf9e84e3d15c25b672653db26e45d8ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"85251bb0af0b000acc3eddaabb09b481db2d5b09be42f25d52b056b966ddc6c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a63ab84a834b223bc3cd8224f1e39c2ff0f906f3c29375f1dbca5a34ea1b4005","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"093fd001ca343779855f7f386b448d526e734c0d8c707eb3b979eddb84d40161","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9598e8f2c6880331c2f57e6fe39fe65d279d5fcee0879cdfcb10f676f2af9ae0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2deaf139a18640875564d069b8df011081214018c145526504bf2e378c716a3f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e7a92c6c568e3073f67648be4f0ab0e8d77e36fcfad8aa97bbb268ffce6cae5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"be1f6a316e168ee956b44f0e9587e97a5989614d65651278328e6de12800fe42","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1c26429a84968da0f9f6818874208d5395d5d681789eb62d1e97874afbe55156","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfaf579c6bd64023a9f079619c3c42aa47cab047556d1299c9789a6bb4a93be6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5032259dba8af052f8650efd4b8290d61a0213e30c43255cbae505fcf6d1581","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a61e00af63164e828f24a3b5abb6c76837562fb18ad83607d245762b92566c22","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec80f7ad05865aa145a03395e48710a388452c5573da23b8680f9fc4c40f7023","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0c2cca1042ac885087aefc61dd07c98d4d763e1b93da979e9cffefba1535103e","signature":"956433971a3be8ead015f2ba25dfa9b9a9dad91a092766686625e24987c820d6"},{"version":"90f918fa4bfc8a1ca28e5ee6c726fb4314e3dd5e4e6e5c138d3722a4406dec1c","signature":"243160e9793898a75bb1706e22e14be7dc4f7503439d0bd4385c9002bb73a9f3"},{"version":"c1acbe64c0dffe2769da1455ffa7a7a54c630553e711ed9ce686509d5a7ca22e","signature":"ed6c54273b0447c505914973fedd613d6bba8426779fbcdf58d1a900bf95d3cc"},{"version":"3979212bf8fa950277a8d6177a6ad6c4839a771a7074d9610bae0e35e6ce9040","signature":"f6aa1162ff9538566b39c5592f558b1dc70974ab34c3d1a592bd7b65711e988f"},{"version":"d275631a49859ae715b00bbf0b5f027c44d809861c2436dfcea1797176f9f4ec","signature":"2f0415ffbf291a21f7b8e32657ea9757e9b49bcd4b1dad5524ee463a1927916d"},{"version":"68a712c8150b2351406c2564d71be4e6bcf2ca9d5d5a241e99421ecd917043d1","signature":"f1447d898e5612d1a748da9566c03045a70199ff3535c97280baf53da785bcbe"},{"version":"75b3fb36bd172a0191b3540170778693e0d098328f7f6b783d0155848717a104","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c360f159bf7cc50cdbf9fd68912ac63bf5889b7220045435cc681b4fbe0b8f99","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a93c39a33bbf74c81cd249032ab84d98f1bb5b86a5d557111792af7fa51fd3b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f62ca695015598c5971af372d459d280dc75e566c024bf83c3c3fed392218927","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f6df81427b32ed9bff8b40394ae1322afd73b4607ccad4b57225a153efd6d6d7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aaa89e91914fae87683fc9b47537b7561fe705e3a19ebe436209a8553a9504df","signature":"78ed4e422bb1101f6a3186fe4b0b70d24d3503382ce07299b406a68f01141809"},{"version":"6d55514cfe052291428316f8b5ddd2620f161abcf9d180a39ad04f2572852a5d","signature":"8d866b691c98d47c7eecc6462e0581e3c9e62a4b13b87c6a7f830f0c2b918015"},{"version":"e34138d79ebd653b7302f054d2f5b086c40075dd387ea3c02b9cff1cafbf66c2","signature":"488ad3e9fa660fbbf03ee600d13285edae90e156fbb5b1c5f4ab396e5ba87226"},{"version":"6c6f6ae7a61d464a58070d9204181c34f88def3da2364ab213b2769fe1da314b","signature":"0183321e9456c163a3b9630a73441c67d709bfbcc7425c09a97ab1ebb83c1216"},{"version":"17af83b68e8692e64f6e475e6d405dd8dbf0756b44f05b22f61535e88f84acb6","signature":"aa43218ec3abf932ac6aa3fc859581b8f2fd8c9469e3c919408062e57d232bda"},{"version":"31c12db46e3320bb3d198856123b8875d4c18a00a9e8e8e6aa4c87153954a24f","signature":"4910911105559d5ec48fa179743ed357001b66fa8df1227bdb5820ec71c3c5bc"},{"version":"442f6f1df0e9859c783e9c1260324833376cf2ef977b0a8e7c5fe85ac45b2b4d","signature":"2e9fd6dc4a8c33cf0b4b359754e567e8c5c4a714fcda3716a4c1ea413102c04c"},{"version":"727b5a06aa2c2d16692b1ff55cac347033ee492fb0132f3843133567175c5926","signature":"edcc0d9e675c37f8b8345ef683965422335c183997a5abae692e03fae3b476d6"},{"version":"104321bbbae499a49b02b529e4e5176eeb094395ccabb51475b94ee7ec3fac31","signature":"cd789dd692d4dd223dfd8938a1dfe00325b137c3852e6a85bfa9ace8ed00a10b"},{"version":"8d1549cef4bfbd34d863903405a6f4146fca4310628edf97cca7f43eb1b5b70f","signature":"b5e0ea031f83d837aab204deb17bb3dc7bb49f3bc3d5f5ae00de7055b0957bdb"},{"version":"90ddbf56e752ea3aac5906c0bf372a35361bfdd05e37674295b930d1a6145676","signature":"f9dfcc6ba837fe9dfbdb57b71db828463f35d1938c29d7405b78935fd6551ab7"},{"version":"0bbccab1ed21d3bfef65a11d71e402b85c240e811a9a0f5d557927e6ab7a1ef0","signature":"60e6043a56300fa24f867fa24168c0ce827d6154625db4aa79ed2d49432f06af"},{"version":"f8c99e101fa518ab3c47320437e1cab29097021f2e4db90088d14cec9ab15db3","signature":"e605c17826925ef50254a6f1fe1b7615d239bf637b30cdb0df4637d362fe265a"},{"version":"1ea8cf150ecfa2e7100ccb91fb039af6b12d8f5f022266a716f6c6d3d0564280","signature":"307b1f6b818d93140e0f0a31acaf65d20eb606098df57cc97103fb0d83e79529"},{"version":"1d75e713898af44896feaec991b57d2e9a23e8790c7715eab5617b37c81b1304","signature":"c44cc8c4930b76d1fbf935484045f099a078a0e56218439da28da5d829065a89"},{"version":"84dc9514ce5ccc030dbec7dbe9731508854c3e7004783452997054dbda666a6e","signature":"0d2eed2f304bc1f3b1d854f9f66a454ab06477ac5e5c4541e10b6111c9b288de"},{"version":"487eed855632a69ade2c123a6190838937543c0ccb9adff04b2fa4acc6acc55f","signature":"1ac9fb8cd09e61aaf85cced63abfbebfe7620efd14a30559e8c4197a629212b7"},{"version":"73179d9bed7d28d279c73fdf5c7ec4dbc78493af676d383b3f3c5c35d839e7c7","signature":"65c789d98597042f1c69b58091dbb80443537c26f1dee66c37a32ee3c625087a"},{"version":"cfdec451e6198722f6f1a470ae1d702e91aba34c5a82ddc8ca2c46eb2841b25d","signature":"51e03b177ae1693a016731d78123a9375e88191258438907cfb8c28289ccb8bd"},{"version":"aa0e1cf4ba7438c59141ab42db4252344f3c2a78fe48e04a4a74dacef405bee6","signature":"f26c96975df3621c30ad0e860d7cb2679f76721cf94e7f8a733b9f3e73f87925"},{"version":"2e818b0de54379a805ff642430dd2ebf684b6cf4d3ae133f12fe826a47eaebd9","signature":"bac0a2c0df7457f1aa977c6199e5579baad02f84ad8594b715b67a312124bcb6"},{"version":"fe83b119da0f5ad1d6de35dd8a8ff11c6d3b4f430d2ded235430d4cb84bf32e0","signature":"53f10c22876bc751399d19641a5d1df99980c8d1b24ea5bd17e074a4034e56db"},{"version":"993f4c89fd25bd6aa86e3329183f0ebcf30123d243bc6505e945c7d23213fbd1","signature":"076becc81584aedfa7349ab56ec3058a2c48e51aecc5ecdabc2fd4aee654cdb3"},{"version":"5c0d0bc099cf3cde30d02b2d11f7fbbb934c2434cbfb69d8d67595bb2ccc1d95","signature":"43b7a0a2b2def00095492d167729428073dedf1d85c28159254d3b64e77eb0b6"},{"version":"70b90a13137fcb5ceaefaec6c636bdf5ee4fec1b03803f5bf1d93d3443231741","signature":"a7ea8dac8d777c73d4af8f9fab282c874cafe6b2e8398ddfc1e4e1a00f2f44fd"},{"version":"d9aba09758928ec2439f08c4736980c6e52ddf6a0cab476c161b3592a34d44e5","signature":"ffaea7fcaed416769800cd74682a38d1335953e1eb903bb59c22e45cced12b52"},{"version":"dc96972b8f5a164f425ad5dbc5a07ce9e4e802fd71ef03f44c19ed9b1a4a1fd1","signature":"c17952b18383043b4bb2c233acf6c88794227a30f545cbaf0d701e44a1562ccc"},{"version":"5b42e9d056ba435957700d0585861287052c08afde82e5349a21a43ae7113457","signature":"c892b55f40a8f35ede8ef7f1e0cdd1dfa70b22bee55d10674222bffbb703ef02"},{"version":"fe2e6c470b89b10fdc90714c6c734713dd0809189913130fd31fde1c152dd96d","signature":"d5dfca986d325fb72b02cb63065520cc0128b46d77c1c68441ca2241ce17113b"},{"version":"e221838e10f6d2c4b1fe86acc4066491685a9c6e878ce39480638cadd1fa2650","signature":"b98c55bf5fe063f227004fe751cd334ce9690aa25afe7be7570d66c48cf86e56"},{"version":"742bbb2ee54b65f16f77094b8444fff1e3f1c4aea3a2bd44cae5de3fcc369411","signature":"8d2883f78b4357f180fa333405e5d6c5d1d08305042060e331f9b4b21c262dae"},{"version":"dac20589f2919a63805df5e02ca738dc974c5363fae35c526eccf6b7f9dacca1","signature":"a6d58c8a4ac0a18d66afe6789e372e54e3663f5198753e6e94481cff20b7452a"},{"version":"be476947bb48a7e4e2a2bf100c43026c646e115085d076247f276f88111d254d","signature":"c34b363d2b6cac61ffe29e155fdac051d7caaa197bcd33c1dbebb9632c10dcf0"},{"version":"a1c585659ad6a50677fc7ac3252133c90ec1d60d2f44a716b6ed4f945c0c337e","signature":"092f1a685f107b5dcb94b5d54e07eaa58894ea17312c10bcbf11921448776f41"},{"version":"0c0f21b2b2173e3f325fb91099b32f3e19843b80a92f4ff2ff5a3d4235afa72a","signature":"e5861628b3c26867cdd721803b8fd63ac2568b1a32ece964adb39bcc895a1ce2"},{"version":"f8bd37d0e25c4048cb2f19e6039b8ebfa0bac6d24ac8ba58aa0fa4efeaa571cf","signature":"e21a61ef0088c9d954930ad728286dd23c0dc3790192a6ee38dfe04a0f074140"},{"version":"09256332eb93d63b5de0c4c87a64562486589143068b03be359bbaf2038601e7","signature":"b33f409b8ccb6bd57b604bf76bed489fcad328a2706408b6c444333ef7bbbb7d"},{"version":"2dbb440d516e8a8107ae311ca6371d7808832cab07de9c432b60d3e3a7e89d5b","signature":"eda6d5dc9807881492ab8f1b3d2e72637da870377f3a4742979f049f492a8e14"},{"version":"e46586b8eff1754102c56c3132d0e4622535a474a8a6b82f001baadaffe33779","signature":"7d73e178ecb304b871ac7db31ef6508abbacac21c1234cbb54dcb173b53a0a6a"},{"version":"f513672a732d70b885487041083cc99b50430dd50ee20a8e8175cb68dd6c8cc5","signature":"638eac046436ecd6f612425af86067e56d2a699a83a5eed192b905a4e9e97eeb"},{"version":"0712f215785e5dd0cad60b45d6812d835d1b65bd2d9634228daee48becd93c6e","signature":"f20151181ce2ce06fb16e3cce85a185ebd37032c37598144071c3ffa634b10d6"},{"version":"268d9444d7e21783addb24299011460c065a57101057d5ce904524742f7fd5a7","signature":"5ad606a8ca9d6d3baf284b4af21e08329b8bb2b9963eb9c8730a4f4a0251026b"},{"version":"b820c9c3de6cb1040413353cacbee04c9b8bc8dfa653a4aab2c25aa6c7c65120","signature":"35170f7ef283cd4dd0a6848be2cbdb95d0d3a1e3472a12bcf21fa59d6f81c778"},{"version":"5ec9b5391ad2f1fc9329b4d7f8684642d34f6c7fd339fbf5074ea3115ce9b5dd","signature":"9eaae9cb3456005143c5fc9a8938ff63f654a96cc6e1b0df490e68d0815a6390"},{"version":"40d627eda1855b36de7021d43907d566cf190cc41bb19c45098750d39f6fd454","signature":"97670088c72dc3f74a553fbd7594d6647c0425baddb95661037559a2d34c6030"},{"version":"e03417e94b5cc0dcf823345bb66d315e527526d7c114a3d8d5334279c7cfda97","signature":"ce511872d83ff623f8ef004de954c7688b48dc397e4d06da86d2b3ee8be28cb7"},{"version":"f33f31e669affb7cc29fda99eb3aaa0a0f3780667e31ed20375abeaaaa30138f","signature":"e4fe18f29c0d82feba07a9633acda1463e3bde4813bcc719fb4d0e5e26b468bb"},{"version":"f7956442417275691905a10a694cf23e778b1d4650fc39f23e4ea91435e92cfc","signature":"7f145dc473fcbbd9152b5f0eec88bcfefe5e415ca70d3edb84aa0038037f61e2"},{"version":"a090199ef0404e5cf68230c4180a409125dc70299541cf6894355022f641fe0f","signature":"e1b2e5515776399fcd972989e7208004f188f075498aa4f77bd888046110ae61"},{"version":"9cf58515da90a71a9353678a765a8b6c94b63b625fc05bf5f9d691992d0c5ef1","signature":"0646461331d1a1e9f1dd6b22fb002a043259f5210cd693c5959bb6d1737415b6"},{"version":"b667b78d8db3056c14aba18e2138fd530838de4e44437153b8c338d97eb7944f","signature":"67cd5d46643ba488aeb104da791a837e1c564361ce47f8bf02f18a49b1ff1eff"},{"version":"6b440e18084a343fc1590e831adced1b079f7a344aade681cc70cb688769c674","signature":"0e651dc17c75471cfc564c0c2fd648826e431307eec33d167fad6f8943b0b9cf"},{"version":"a1f703b3b7e3200dc2d735de4ae5942ced19ce3cfd5423da9467fc280fdc0329","signature":"bb851ecf30c98fe3b901290ae1fc05bdc55da8bf15ba10e1a3d10dd12da09cc2"},{"version":"070a5b980cf70e9e54d6291f31a634d1346707662aa0b906ebb47695316d94f5","signature":"0e92e6224e167b5d58f377e0513c39bb3b513cb2916a2166f87a20d3ac9cc629"},{"version":"cfca522a29f53430f1d0447baa732bf2fbfa5bebfe68d2e475432b228a496110","signature":"f8ac07e911fd9a3bff7d0a2cb3b8589e14a3b52a3d26a2fad493348f494edfac"},{"version":"431b70b424860910a8ba2560f83bd864a2939b87109f11ce22873964f1823b62","signature":"24cf0f162a2bcae8f5ae4b678da2ca97a91699cfc30898c606cda9ebec4d9f69"},{"version":"5d60d719297b5cca5a0e20daa44b23493c8d65f7e64593468e3c97293c236894","signature":"d316a8da36d661ba0c2110e7fa8961db330ee9c39732cdce1b693c8608a06100"},{"version":"703e7b32062955f1941d78af2bf1a972cee1905d9f64c8c945e0307b71c6c8f2","signature":"5c8e01f96eccbc91b7243158ddef84763ac292ad5e914a352f8422f4b374aa6a"},{"version":"5320b2c2ba15fc1d0250cfacd6deec6a1b242c1d03f5d5bcc2d7ea8186fb8787","signature":"7a977a3406b9510b629b97156a3917b9f347835d16ccc19e110f0bda76af6621"},{"version":"7b7a0b908bf6dbaee29816d012f9ef2ff0b0745ebe5977484e12ff0d2a1d4fd3","signature":"7b90b0d17d565ad2bcb84936503634ee9f77cf9f40b2353797af5c1dd26b6161"},{"version":"7ccdddbe6d980d43d92d5bbdacf45f1198b4142db652f6e90ab97375400ac468","signature":"ae5b73a3e026381b16450aa4673ea1b2ca1e7fe4ee196acc0a3f09c299081d51"},{"version":"043caefcacde199496905b469f7251c08948c4921f91e5a0f5c0df4f03cd2d55","signature":"08a95f68e870bfccbd65af83835d7f74d53e33f3a90926375171726c9394185f"},{"version":"d33e44d9c563ba82cceb0c3fd5a20de58d19a4ad63160482de55bd9c50c3ad2a","signature":"c53d5f0a2eac0af33a7fd617b3d035c3f95ed81fc152da2058542e01ced0fdb9"},{"version":"58cca2c47d5dd00d8585348eec7067b9e45f9fc89c9c430cbac18e2846c4bb80","signature":"7eca6e5608816544c2487977bcadb1118578578f54eb343e7ec2ab82302f82d2"},{"version":"002b0f75fc367ff79a5c95065b8d1a38cb78121d2588ba872110920bb9643024","signature":"512558fba7e0f5f8d0cfaad40f05937124ee8bf4c3a11dcab9f618afa626fc0f"},{"version":"920099117da73b53caf5e84b81cc4d2200bec4f82e818bc23b7d079a2a56907c","signature":"396d5e07f113ce101976ef3238521989b0162a3440f8f15e5115f00d67aba169"},{"version":"7444ab226ecde90756e4e31ca68280797132b5c3b38348dfafbb101346ff9c4a","signature":"41a5855424d478222c6ea0546f8f0e7563b8ea830f0b02418285fee2f6010104"},{"version":"4674b23baba8d8d1145d47b4d8db58a1161a0f0327cc5e05aaa3c70dea3aa4f2","signature":"597635cd2982b768c8075e33902d4bcad6b823ad6837b83bdd5df1108a8b5ef1"},{"version":"dbdf5c99dd4d0362a790a664fda2f7d80f0b90ec20d2dcf9f4e71e5d859ee247","signature":"7b9aa1a8a9728abd8faf699093ec32552e44ceb2e3e4eea7fd39fd4a105abc61"},{"version":"7007c577d3881953fee9f301de570abe4ba1f6a54fbe2873968dc002ab5e5629","signature":"e7dc47606500af2c3ea9d69e3d9ed293bdbbbf81bfcb0c48df6568c1ffee4b02"},{"version":"cfad3779365697cae7b23669d57fdb286de38dcd3c9b1fd53689f9ca3a91a2f0","signature":"51838b26378f28235d88da3177a2f581d6325b1c546464f2f5bcec82149eda0e"},{"version":"a6e432450d84b15cefce91791dc06498a08e357e05526537db4bc137807316d5","signature":"92cc6638c98debee2178e4ef0e1cd3859c86121ea2163b0b0685d41a96d14e73"},{"version":"2cdc33138be52678761d11065245a401d3499f110f502a2cb34cc2632e9c5e61","signature":"41f303a470c94ecbfca891884533f4544fb3ccf7cf97aef0ee9b65df992d97e8"},{"version":"236b3b7d3b7a86bfa27e5bbf1998dd7a09b9b6ae3ebcaf1040be305324dcb5bd","signature":"347ca2f28c70154d1081c55c3772b0e5073d3482e261847eb6ed655894401136"},{"version":"e22bf0ed5f47dd3f92ca02adde6e4459657a649c00c57071c4b401d6883009bc","signature":"c21b522e44f78bed8f3053a3824eeba6f32c27dd933d6b45cce037e8be0d0538"},{"version":"6f15ed3572bab8df8b145e2808ae12e8945eb7174add99ccd556f8bc1fa91b5a","signature":"dcc74f798751ba65a1cc7a24a795e46be9ec56f409261005e1d69b732b013560"},{"version":"d592e7b22c830bdf0c8da2ee4c4d5d3587675ef62db03bdc0c78df8e7f7b7c80","signature":"8f3dcdeaa6a4c6257d53aebff62fd88889876d55839a85929f5ae2d3a37d5a73"},{"version":"19edabca93b6826a91c26832d55037e487218d8f29f2172917ef87ff08f8f380","signature":"cb9da35a72a402b315b10d2569a304b12543538dcaf383f4d9f8dd5a8114927c"},{"version":"fae8d4ba3bdfd3f087c40507d8748236b0c08aae3d74c706651a15c0e27ba16f","signature":"2496283dc414126ef574138ede1396f27877de39dafe04d183e30d2c38e2cda8"},{"version":"9041eb411777fc80385d1b639173fbc6675ad3d7cbce257f52605d4b18616543","signature":"748edc1e544cacbc98bbdfd79c2a36b98f9e35ece62316b401da130aa0769631"},{"version":"3aaac2c7f4e18c47e5197948b4f1c4d1d569257499c1dcd2395bcb15849fdae4","signature":"bf80d1b3fd049b9db79c5bac94e6a4b2cc9df97720f65c91a62e095d793499b7"},{"version":"ced161d675dae30f24f7d001a14ad62504d69069b971753e9a8003b2200e7cc4","signature":"1c51e31907648173207e55a21000c17277311c053c658c91e5505c4f1cf4e9c6"},{"version":"9a5d95bb08f3be86b32723cdc4d4165a66d3008f375283cb31100fdabbf913d1","signature":"dd0fdb6f0c71a53e434d39a22ad54d9d196de67178d156fa5b13df073c527f19"},{"version":"22772822785aea051e4454632aa2bb73baab3d08d48cf7366cafd8f19e1e0c4b","signature":"7c89ddbb992896a2006feae6ff5ce82f22e2158dc35be692798b4744a624221b"},{"version":"08069afec7cba0f29e89b4fab6af533440854664607e1a6381781df96676115d","signature":"a78d3790cc5ae1b4930c299095023c07eccff89cca32e2939a4722a716c9cb55"},{"version":"cc5667037f805a2921883f7fa5e091aadf2ee8907c46d61a7d7992911965eb66","signature":"442965f5309d0a2d4d8def49f2aa35996d1b73d37c0b296eb305501cabe8829f"},{"version":"7ca021d1c2dce515eefcf872fd521c489e8ae3509706d5af45e4a53f7ff22549","signature":"094975115f35bbba2f336b32b78b856d111acc84119f62ae45f2261094e048a4"},{"version":"a2bc61e9998909cdcb970a57b46b9bb502242bc9d93a9a09be2c2f446baa729d","signature":"a7ea6ec679bdb15b1fbf1f1a938e8be370ec62bef5744c84361453a59c182a1f"},{"version":"736e4e795c9de961b77adbb54f1e457e47abfece7bb141eb1fcb6ca6fbe2c9ec","signature":"0f071aacf6b65da0adf7ede684da36b0ee0f310cf6b429e2fa37fbeb90ce79f8"},{"version":"82dbb5baa7af6aca1b1392a81acc3bbbc07f50ccd8cff2b3eff2ceb1c5db2182","signature":"e9708da92b0cb69d4b46485f491a6f053fa07145eadaa8101b16fa6738100f9e"},{"version":"fd3444a7b0304c83565c7d69296748987a09c2a55377bc9e6c4d32961f8c99cc","signature":"669ed183124d2bd3cc638ee9422002a758efdd672388d6d6121187f2d073d024"},{"version":"65f0fb9a264a44649bc963291e7d6e810c6c06350748007de4e1575eaba8d319","signature":"814699b1fd707185dac005f3047ce5badb2b34bbe355ec84b4c3cadc82008b8b"},{"version":"08fd220005ac42cce029a0f6ee96478b45715a8a39ebeb71e42aa726597ba44c","signature":"d81983bdc0492ab963061b9fa1fc64926ff2ccf744fc3ce3922061f6016ff571"},{"version":"904f2be841ddcf41a9358e1ffd053618fd6bb1ff1446d429fc2f253af774c5f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2ca66d64ada0abbc61f1e626ec99774d85b81f4090bb27f7f3d29c599970bb3a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e27dfd3b35176a3a2e4307206a9ec3909995b23657330dc835e7b5fd50ae89a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"72ac7a0ae5374dad1652ef8e41ef145bb371e9b8af2394b91a3b6e0220b5f39e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"943d9fea1a3def860d7398ac4c9c72712f969552e4b6e913da7f37b20defaaa2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cd34778c118327f37b7d398e6f0eeb648ded19b5398bbf6aca8af751167a18fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f9527355cd60b21f51b107ab7097d05e058c0d4a38f82738d111c2c9c47c4a87","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e37af3d628ddbcd48c89790cb73dc028f308dba050d3ba3ae64d1cdd491dd7c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5681b305d9e4a3f2a27695871b6685c547771c8df3321ba62aba2569d3537076","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90e42071689c6160951272f117347d7859a2ef54dc83f3879eacbffcbfee1868","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"faee2c92e93b04bcf0bc3cf951a6ab15c80022850773ba6721dba52e84a5ba41","signature":"1342888987d6078504543599fffb3dd6029c2c0f768b489cd232fb10bedb675a"},{"version":"e353b7b008a1c093f02f51d6c46b2c1ef2c28fddbbd38889a6a7e4c224916779","signature":"8c40edee0d9d2d04dee8654ba2f8f239f1662ba5d78ee296068d1e17dece3391"},{"version":"b2ab2a7ba089dbf1480307647bb4fe436039011171626946c12efa7ac7ef23ad","signature":"ecacb7a344532575d0bcd497fdd22d7e66c42117aadfa1fd211a47dbde3b364f"},{"version":"b858241a65512e697bd928eb1675541fa5fcf2b516aa837f90cca9ccf23162f3","signature":"6a9bb53e41455547f9529dccd266b05e7cfd3fd72264f41bbc97581094096369"},{"version":"045b680cd4cc18bf4d40193feb610f9692e31e055ea84a89bac2f417831c7ed2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e6107e7fa315d770a69b9edc7dd077036f115479e102f2380306a7a92629329e","signature":"0717bfe8ecec022eb7f964ce697b1b0d749e217864e7a11b438c462ca1e55412"},{"version":"b26bd1398c6f71549fa23175f3c8b8245fdd2d2092a6380794cf8ae8ee67666e","signature":"99173fef2fa963dad3c3a06cef6bff8e4a9e4fea656ec6414aea2f84126ffc23"},{"version":"002de79e07e5851f180fd44a17d1f855b5a17fb9a00f9a17cd53ca055d27ab8f","signature":"08e14aa9343103c4781efad6b4d287b2d577a8266f51f389b2ed4db8957cb5ed"},{"version":"ca319732ee32ab8064188cb5e284f7d8994c1e05e67bd2f7f55203eaf67b33de","signature":"0de5e5a2fd2db15c16147aff67475c913395e62c14bd7c5313880b001a88e009"},{"version":"04ff795f13235dcc2df104c2363bc370338976c37fd408129eee133fd481b1b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d043ae7012e9b61fc3a47946b043e6feabd01d8a1d43f5613bcfe1fb1d144fd0","signature":"ca0cc7ec2073444d6a6e3edc6a759fd98917651115e6c1b56e805d7226d122f9"},{"version":"e48cbc2646a758124caa3c87b05a722e8da250afbc6fd5f4eefda072772b4616","signature":"2a57bf7b0ebfba810bdcc71f9eed2403e5a2aba006c55788b48195c51aaab8f1"},{"version":"056fc04ab05389b453bdac4ec2e3c1eedd8bb661c20c9fe2e184125c8d69dfb0","signature":"abed2e07ab4d9ae16437a73aaa4382973966df349afdf5eaa654b3f766c30625"},{"version":"cf4d4045a6ef47b776863026fea118f50fefbf94bfaca15b330d5c939ebeae61","signature":"f670d82642bcedf7ae7e34c49a5dec771f607f89b47602cd0b7508aa981ec2ce"},{"version":"b0f38f471c06b85328632e90a5823eec4d4bbdaca72b634b477325cdd68d62bf","signature":"704594b25466b609c3bccd775f15b2118e1ac95cbfbca960e5819c93ebc1f8ee"},{"version":"2d3dd03df960f48735d9ea246405ce7f2f6501675599c7342965217e6873ac28","signature":"95e604b1fe25d3994cb3ff463ec3c46968a7ffcc3615814407b7a78359431ea1"},{"version":"dc2a05a4d3db8795c9c161d8e05a72d0548cf61e5b5a1e992b958d287d148f20","signature":"3844cd66a0ac7b19cd62be77527a4a53499fb22a7a122d735603ae6979064756"},{"version":"2c008ab0e0b102c7d0752086dd258c08086879c4ee036b40eb909f53cd444f76","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"078f20355a4a20354fb1a4ac5e7b0398aa43eb0d374aa77846c74b1496e7fb79","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3e5cd51e11096be95c01de7fee203750f0b365f46dad987f3afe9fb535b99122","signature":"c84bce3c55622bfec01668fa58087cc8073885a20ba030d9e9b519debfecdd96"},{"version":"e50fe642408753de3208274d3a7c83c42bf821821740b79012eb581ccb425bd9","signature":"8674781878cf01b59ae950a13994a74b11e766bdc3d6a87ecfc77d1e4e0fb7a7"},{"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":"36eafdce35542335372c9104a44db2597b5ecbdb11af1177da13d457efc94fb3","signature":"d6ab9d3f4bfde85a62fea7182d4e68ba2104946541f47eab58799009a38ba2db"},{"version":"d153e1cec75d95055701de32dec8d0ba9c9a89ce85bd371b7d51fa15e495137c","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":"916fafd410b9c3a04bb3720774b0cca93d1dee94bc88b4ecb6edf56cd5585abb","signature":"50e5d708858d82cbd8bd30ca7a76597632b0dff659403765266a4891b35a712d"},{"version":"ef82bdd9d674d855785bbfcbec2181e8d602c430bf73b4b65ef581d78ecdc64a","signature":"24644a17b266badb345ca337c9d9c80300473c40b2c87a28e5a3ddc011551909"},{"version":"846affbec83fefdf905e16b3fbdf845edaa248b5895279498aa6ac733ff2a4b8","signature":"71e597ff732221dcbf043d2de4000ccc5326c9ac63b12f2a27f89b5adf18e609"},{"version":"227f3a03b267191752ed1a2381855cd73b0915794ed51151e5ce82ffd786dbde","signature":"22c51e70701555882fd248a93bda5c759c024c0b88a58ce37a54ef186729e795"},{"version":"4a700720ced7ebe4c0c973bfc450c6a7ae31f82fd447e0f464c7171562e8aa53","signature":"212577e3f6db3f7bfb26e82ef9385a9c0c241b3906ccb6d80e4ae6bdd657e00d"},{"version":"b69c8778c50bf0caee3dd1d2da2fc7d5f6157498b51cdf51fac81476850c715f","signature":"0270d8376c084b2e07697ef2de94f943eef66b6de4b77fb20147d306f645e990"},{"version":"39bc5068f51c657236fa9a763dde5bdae05b46bdd49d0390c4a72fa9dcb45dbe","signature":"2f67546822e0445ed6a5fc1d2e96bea837385d7b11803f8214835933d03ede63"},{"version":"2a6012a4f4a4695bc0a97d29f47861bda054359a9a60d295ab26f416f95e8940","signature":"dfb01e57f4a98a678b16d78007abe78b8600ec7545e63331d72a0daa6ce961ad"},{"version":"094e1a72a14a0f38f950e388d9a4e8f6118b493a5918235de9781d5c47f327c6","signature":"d7cd6120b5ccddff937be1aa22a538829f8a93ffc9b4715519f67bd21da26689"},{"version":"60523c590e7ec5b89c49c0728f8b64ec2132482709ea5c5909752d6d68c93401","signature":"2315efae7ec760b18fa4c15f987003721972b75388eb00f80f3a419e91159751"},{"version":"96118b858afcbe20db893025dcd75e19fd530bd5540c65029500e8cc251c34e1","signature":"bad3fb3837da6b89c49e110430e58827c321031273ba09aeb1c83a1e0e9dec70"},{"version":"17ae4d2c0c3487e077a7f6db647df1ebc56194a135262b8af15b19a3a1072452","signature":"7d5919c7ef0b53f308a78754ac25e352473a2de6bf6e25e109cd03dd493aab54"},{"version":"a389c1e6da14dc436285d19455229c3ecb445f0d26b4de5e4df0c223e43545c4","signature":"7e2734061c31bb7fcc162aa37af53a181cb8db1bc2ea1168ecf1c816cf52e045"},{"version":"c40cca5deab8288e95cacc2e5f8d1d2717f9b49e3617cb3ac992847d5a143fc3","signature":"d23b1f070ca79bf4cececb66c23b78eb3f35e10b3ef7d0119549521c9fd2ccbf"},{"version":"be28318b8f96ff27ce32fc1882bf6f18e306dc8ae65ca3361d7769ff98c933b7","signature":"36b9e77029aced4614bb01342291bcc4fc65360f32e9d3d639c8b38edfb86169"},{"version":"f8fcc667b4a4cc586bfbf3d76cc17e91bad6749ee634f736ca957ea7377cab3f","signature":"4f3963b6ccad89bd71ea9c5e491a83c9b448df7d36a01ec887aea29400c52cdc"},{"version":"fee446e0178c52a271d63c9d12598620eeba7a0a0178def71ab7eb70837d7f26","signature":"5eccb4db63e70774c70de6e6e6f67f3f4b26f2801767073541a772077c2b8458"},{"version":"dc1d17e168090dd4df773ba839410a2e106ff0f43163a994f681a0044b4de578","signature":"6113e636ad4fffa72648f2a41eb42ef83262089973d54e080d130cb4219dab4c"},{"version":"01ba304e845f2081cf6ce244153824e727d70d9acbb973de8e2b6340e4355185","signature":"9adbd23317b76a09a9364daa02f7ad358ef30c277dca1b05e8df356f7751702e"},{"version":"e8ac8a0a426c433de3f592188e1fabc47b43cc63be440be87f36f5f90980fc56","signature":"098fb9262c019dd7c7d2bc1efe85f61d7fef30a8c6ea0267398aee95321cf1f5"},{"version":"5e8cfa753701ab1bdd8545e9436da3c53f24c179978efab36a5d919484516735","signature":"03f8247a05f82c8f96aac14f06944d2dab5061d08fe71d98e429251144b7d9d8"},{"version":"e9ff70874950ac2fa288fe64a6fd622a06d3579bfd781be96bea79fee7fd1381","signature":"1b758ade259220a7723152591ae4997ead9ed62664cd36c08289f94fbaaa5511"},{"version":"574fede341569986c736fd5468e28194913dfe16685e804a5d5fb0a24e71384c","signature":"cb0d718f6419c7cf0ae1316734875540e46cc33e10e9aeecdf79e2f29f6eab4f"},{"version":"2a02d329b308ce5a74632fe2062c72c049e672e5941c5a8204bd14408859c3b3","signature":"291d5759f6ff0d7180456b40f53ac4ec52d6fb91b6688aa2748e70feabcc8124"},{"version":"3970978fc2242f1ee735a0b0f0dfa8a42e17bf5bc8d9200e4e7059c63877f4d9","signature":"f8328683d5b3f602c387cf200cb1726c422dc90197a7fdb0d578fcb7c9bc6786"},{"version":"7f4483fd17f3160331ba5ace194e18e3687009b63ff4f66489586a25f497c60e","signature":"4550911de88ba268a6ebe2afc50d958d54100677ee698557cc2d5a6a36e100d9"},{"version":"9607e2d3418c1e50af1dac762f78b031f5f9c24f13ca4990b062f27c4f09a340","signature":"4cb3d1e907efe7537c8b4603e87bba3e9afd8e3294a436401b5b95fd2bdebdfd"},{"version":"3f9ae10a4a447dc5fd8d079cdac3d973bfbfb61149d6d34421ca8ccb9fc25a8c","signature":"6fb16d7f85050f01ba2e8248d33306db324277a87040aed2ac58e20343a0c2ac"},{"version":"d8a627c1f6473ead38c4a7fc6c22a1718e4f4b83855f85eea45cf645aee63cb8","signature":"c404855e249e727a187122c5a1809d1e93cf3bb3af6d64d68dabae61754a2da7"},{"version":"b9de8a489cf0a4692ea10cebeb35b2dfe35539ba92c47f23aae36f1aeedffc3f","signature":"37fa56790fd8a57b9e8e21bd7f2aa4cdd33b7a833ae9626d8bcb9eb41e0288e2"},{"version":"7ebcbdfd5763421e021e5472bdcde0bb7dadd2fc6bb2d81f70309a89362155a2","signature":"d1e471f636d7ec618d53420476543d28b575d5c30f18b86931f648214ced21b0"},{"version":"826e0ef6771e8bdb186b153dfac8f181926f29570c7443560bbb665099eee80b","signature":"39dcbe7a573f3d3df729c6028108cea477260aba94ca082a95de9d02a267ef27"},{"version":"f955a769066260ff6a27a22deed2c93ed071342093caf86e7a6309d35eaaa480","signature":"3b93cb46f96d399b4cd9ed35122df5e43b202839ee7c8282632c5c47c4e697e7"},{"version":"60ba574b03771c2da031380bda16f8ebe86e64be2a04f31c53d572edf987d8d4","signature":"c81efaefef37848e456f15aca0b42ecc599fb9fb73ed61c95fa7f7851c280506"},{"version":"316b866c3bfbe957ec585f572fab4b2f7a35e8d9cb266dffe597e57927a5d66a","signature":"19d7ddc11ff468813dcf97fb05f4e51d6f78e16a0030933a608aa0fb9f2ff9ad"},{"version":"a17a4fdad4f5f7be2b342254233644413c8ef984661db43c951953933083d8e9","signature":"209ff798fc5f35a3705982a320e8dbcb321571e046a96de4092b4465b74fcdb6"},{"version":"db6562108a47f4a746b4bea1694912ec1ac7ec51b48e3a31b274b4c8102ab772","signature":"e1a2f10bdb3e04997994496c5f189b4eaa3bcd92e06761164845c59133af8c4c"},{"version":"85592302683b0f3d636e53a571bee7fe59803339b8b3eeaa9a5f3e43717bf81b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77e9654c2e90c0915a4894800e66a9c269ffd3f0fe06bb17c14bdc23ef7f5d1e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"861c7b678c64d3cdfa0ad2a3f529dc1f57ad0252f6bf7db739be18e14c79c617","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8ab61002066e314d8b266725a4ed3db857e41c7aa11927aa4a38386370204af6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"62845c5b09ae355ab3bc4c4745dc5585b77b447706ebffb09ea3641e5c963da0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca973ee48e138941af5747d17cc31073d1d08f57241a81b8a6370fba035c57c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"734738e3b2da030816fedead484741535108b3bc904a7b90a6f8b2176d05970f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a2cb0d579cfd7ee8015c6adea94ddfeb2d7e79c040ae9ea9b57275096512bf0d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1d95bd896b6216d08fbd7ec10a33b40d09d711e3fa102786292ceb82e4b8193f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6dbc20023316e17c6ae6382458fa64ee65049a6367dd648e89ec443cd59ca18a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5c6eff88e726a6c9ccb73bd9f6b02dd0e248fba87dc47e7b3e211f3e9680b24c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"31cf5e25aaf868c1ba0e195b7b62f5cb516b70a5bd6e2ffd8eebad1bb011c24b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1896da12486c6e51bb02c20eaf22d1826fe48349e584f2c59c8506e925172b44","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"daf5363c1b026284d12cf8d49242827d18004e624ffd100bcb74536ea92645c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5903b40fa3676f924372e37bbdca65ba67e3191a92f52852d5b70a2153f664c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f7e7141383221df9f2f32fa96d8ac65ab8cda4e4f19eab9f69c24e09ca31b172","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ac0bb5930db453fdd87419f223d44c23e8852223300428032ca09c4a497d9ade","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f3acc54979130b5a6786b3cb3a1f47f0330b6acecf6c509a09c071a1760e3f09","signature":"73351372b4295fa8b882bc93e30276d7a911cadee0f013b17f66d50ae3de6a29"},{"version":"c7b6e3a82a16fd54330388cc5023d8686071c102d3a4cb1899a74064910e7704","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7739faa1d7e4719d14729d52aef996a6d8c8b1b1447dd9441728c642f46d4f79","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1e9b0eaec1fc9821665f79d5cb10b16f5aedda997b77f67cfc634c219be45cf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eddd32b79454e4df90e6e3bd8d43a997c8813aa61e2be0c79875c87a5d9f7b2a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c217dd4af49f75ea5671d76d7129d3d3154589fd0193c323ec2c687ed13ca62","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70879fcfe03c15515033be18baa3afa57f4f4a6d6bce8801e87050b02e04df55","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"811af3b90fea77ad0ddc26b1a7f884d9366a44b20efff4c2a15de5bc9b35bb2e","signature":"16a8c433300e1e2ba1998062452df2b0ef51cfd21584e8bdb0553d9b0aa8bd5c"},{"version":"03a54222bd4814fd65957c052e3f6f68b85189a69e50bcca23e1fbee4238ac1a","signature":"ede3e24a18d5288414797441a3b532bcf9dc229cb41a5bcad089a4814f438a3d"},{"version":"e292ff26b159b2acb49baf29c18d233486c78afdd409e727b71ef6cffd21378f","signature":"2fd39dc262c1fc3f21d6e25374b30919d9315210346a585dc31a758918996577"},{"version":"4ed889a15e24a9e0f20d3642768a080cbf47254ba81997f4c88a37d1a7d0a7d0","signature":"f1088a946445f681d8bfd7cac8fc99d0549d70cff4e47179d361377e529118a9"},{"version":"a161552e025ab65f8b854f9a3338f8c69229c0493b13c323252d07407ecbb1c1","signature":"b5984247ba3e47fb79e844881c939f38398dc60958d0b29f9cb87d0e29fe73f5"},{"version":"154af56b732ad2cf00fb80508d1f3158f0497507c9309670b66758fdc0461bd1","signature":"66383839201674f99a40f904e89c5c9454d3d344ed91210206f28c5776fae9f3"},{"version":"70e051b3ac6969f054669d0eec72f57662efbeeebaec77174e96cb91dd3d7b9f","signature":"de7b7c00fc17f6accb9531e5271897cc70db0063fddf8a17d735db6fcf91b395"},{"version":"e8dae3ce6fefe470c1bcef3bd55eed9f3bd5b5211ab5ece54899783b80cd7b1e","signature":"31c27b104652e1136c1f2c56ef27f83380ae8587517ed95205649ad261a45812"},{"version":"1747682b50a243bbda982e8ef09306e5dc2bf9b0a0def44da795c851ef31d6e1","signature":"fb891c7c97af75b1638fcbf3b1cf51815a1f7aa9192f202bc4fbe295827537c6"},{"version":"8acf6816ce4505a5ef68bb1ccd8d4fc30815a83e00da1353b90102ae5160da81","signature":"997cba142aed5347c9d15f4e15f6daef2889c2fd037587841778b8ba476ea168"},{"version":"0c82b7ea29dce9d9c5ad81687769d14ff730a377fb9bc3c03cb16fb8ebdfdcb4","signature":"9ad6faef6958e6870ea4aba7cf6c40cf2399cf55b92f7bfcbad371186edd9636"},{"version":"80c8854964c4a39f42fdcf47a985104612a776c8de5b7e08e929c4389331a06a","signature":"8bccf01cb22376a08d48b284667f4b812ed8d38f2b723d6aeaa8c96d133d2ab8"},{"version":"ff619d9cbb2254ea51e7d71384abbbd5d72f2c93c071fea9c32b64ec3342888d","signature":"542542c33ba947f131b795d3630b41a32a460aa588a03aeaa5eecc38f3592041"},{"version":"5c0d00b05aedbf7b0bc483dcbb388e94b5948cfb1fbff930af60dfd9298dfc50","signature":"064b44fe73f0f7a084ed8e01bfb2ccbf0a6203b191fd521fc2f9c76d21e652c2"},{"version":"e89aeb89eb7cf6060c0af880e07093c91b08938d8b3a82a8a9b8fd5ae1d056f5","signature":"f3a1e1f72b8affbcc2c49613db77c9ff271fb02b9db2615fcfc355b194855b08"},{"version":"e8373b5d06c8923b34324f9df29eb35bea64a6a995b607a0b0fb2fb8c3a3a140","signature":"d86e8a694d25e37475962cecce34728a00aad3f3ae57454fb83e80c2e559cf84"},{"version":"6be1b5921e30052b789c02a63eda3517e0686c0d8e359d9ba5bbbee4e738d1b0","signature":"38d9938720a626eaf80d7c415abdc14d165e67cc0a3a5fcc4f40f0a16d2ce5ff"},{"version":"0b208ce8494b358652ba9030cf0e14619451807547d25b3b5b720aa57bb940cf","signature":"327348398994bb43ed73a2877af0f313518ed43453dfd4c68b77f47b77611738"},{"version":"d985bff3e70be34ddba319f5e9209e8eb799e392218201acb3afbd77b6ad4d5f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5f45030e7b52dbd77b0e101bccf5bbc08537605f8fb10927b0281a51fb2abbd6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6897d0d0498030dd4d7b6190a78010c071e924f62811f51897f63268faca2248","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cbc7b28d500a1738964097922cd6c6db2adb129dadcfdba9c1d56b77697afbfc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a219c1949667d439c27329b94cfdc416e2839e8214497fb621c491eb24cf3bc1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20769f36dc2e033c8fcc237bc2d7a75682dfba17022efbe30ae06f22767869b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"05a2797ae1b679bba91ebd96c9fee9bcfeee3b3dd3e400ebb3ddbedbba606306","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ae2e40a1fdc7fd8cdab6be243e4541f50b54445387834299471885785e3b2489","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"96fa384dc9c129874b902106257491e15eb6cc80bf921cbf2906a779ac96e60d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"db3b9f398ad210cb961c2b5d638e28f99792cefe9c50c81ed383d2942aa226e1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b3277a8f5c5b9d50cdda98e93cc145820c3983f5e8aaffd31f4316eeb0ce465c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bb6288a9c750095a16037444e6026de8bbdee3e77af676ceb41d4ab7a8aa465d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3780496f95996c8b34890fae81d23cf74833dddcdff259a695f7952e18c9607a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ab9cef431c5ba3ad0da377558211af661ac8ef1b0e3bc5c66bb36f4cfc3ad177","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"13d80d19b3c6cf01d42d51623f934ba1ce71c75aadba05d91d0d67860d86e629","signature":"0f8aeb5191b3424d8865e154335c144f70d1d1bd13be767471d43557fe97ed96"},{"version":"f2b455abf7da931e2c6af9e90e22ace14c7f357bd2cffbddd865d15b442030b5","signature":"5776f350899f8d645398751759ebb4fd2af323fb1171a47aacb022f5d707473f"},{"version":"70b399f171d822aca03548b1644217d752bbfde4d4ec2cfed1a214d7fc79840f","signature":"cd78f41f9d6f04e36cc052c74bfefb7c2db0779f89d2659aa2c3179b054b1c8f"},{"version":"d02be3bb5d64b49b5ba9e768fa8448b5c127d09e2b8ba14026773c1fabde1592","signature":"cabe51c63a81cba61c652a509354e7ab4fb262ecc04a5433dafe9edccc3ce1e2"},{"version":"a88dcc474c044ec5c3ac8536ae40771d408085bba71d322d73bf2204ea023dc1","signature":"39000aa5f4d43f9f6cc8762b6cd8029cea4b6f0511e7d1f47e4d6ed7da095a15"},{"version":"b1d512503e816355be4952330e0a427949fafd8cb3ee124017b7a535dbb26209","signature":"89f540ca38000b4ab06d97bef735703c375a50b0b4aacf9f4d28c14cd138e59e"},{"version":"842955471f601e4c1d21afb2cbb3d250ef424ced61416e8d7ffda5addbda9eb2","signature":"25e6d9fa0f3dcbfeef48b9738ad3a3efb1f07f8c32381d838fed05543afc20f3"},{"version":"493e9f05ac502360eeea2d5c72d28984c6bb2e03dd0c1bff35e2e5265cd8a6ce","signature":"a27763fffd538d56a65d2ee0de520e77a21958e31a87f4cd0c57efa7b9cc348f"},{"version":"5dc03bbe2c52976d8b054be1fdfafa1b7e43f328bf48a19d5f62f0563dfee905","signature":"b013ce777eb845733b2d4fb5608890fe38f7a0829738da416cfed813adf39080"},{"version":"3e06d8650c98a672c6d811bc035a7fa2561bc2c87ad01172e5df460a8629d489","signature":"4b4d1c5dcc9a153360f0bea18e847d0123bab4e18678d00780beeaa4e8ab01bd"},{"version":"feb5cba45f6c40b8b4601f40eb48697fa7e2f7e3db51337f15c308cf2800da36","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c0638b8d32100f3827e3535c4307c24b5ea5e7ae6b33476db318a8d706386626","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fb10fe09a6f0c5fb2f5f7bfc0855cbabc27cc4fb9fa3c56e5956f0673746ddb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7b0daa318777bc57de0c9198d4ca71d7f1ee1e3f02c5bd860ec5bf390e08fce2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4c5ca16923d08d3df7ce8003095ee7cf136956b93a3a87d06e046c967a07d379","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6ddec7fe4e03cf6c98a431bd4b7998cc9a11ad1f5aace1c73f6a0784c7c9d503","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"048955e8ea4fff7090afeacc7a8d9a0d843199be15fb1ab402679f63d2a18a54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca2ccf002342ee1f87be1682f2aab080fab7316eb8f37aaa6a76a58859b3de76","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e85d9d5252dfb1dde90672424170e3b89cd14b07086f790d3c45aa3f023a93a1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"789e5210de191f9b2c090a2acc40b4d8a1e86e02626cf05cbc6b60079b132f3c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3fbbbf2353edb06efd1d6d25286c1cc267f3346dd7a73424e35606ef0fc04eb9","signature":"d1caf598b76a5d9cb02c68f802fccbe10bafe10d88cb6c0b78350e1b63f44ba9"},{"version":"d86965dd2a2cbcc99a7fe6d86c26dfc2509fe3b640d6c290e141cf41d0df613e","signature":"226dcb5115901da170f4421a0382fc00bc2ba83c97d9bdb91ecfb29ef4e8f2c4"},{"version":"a65cbbec2ad16ed203df68cc7474b8e45722ec2a6d2a6a6f6c0625754c9a63aa","signature":"1c1fc02bd4a8c8331473b19ae20b92b8473d8337bcab684d5bbc262b7a31f07f"},{"version":"4185e0d631c6b1e03a2ab79d937f8d47f25695cd2065f88744c5d237f0dcc4bf","signature":"dfbb24194637a7659be87169ccb8b885de89d979ef098ef29e16b314938b16ec"},{"version":"5efb20d08f4e3e7a5454d5403a500512464a4d59d30dc442a162e9f9873beece","signature":"19483d92b38fb23f95f7dcf3d6bd88df63a983d3d8c11fa3b4bea4c6d8141059"},{"version":"a50db966163020665ec8a68d0ecd79d8a9fb0d059c0f4d25ba53bdcd7e43cd75","signature":"d406c57bf0c60a88a4cddaf0944ed69a7554658e068fb62559e2d823f9255236"},{"version":"5880d909fcd7aa478c019c0916f68012f10427b2d90d203a9060517bb9ce4de5","signature":"109a47009ff1ea87255fbb9bd75f5f3a918ac7ab44ed123b521028f580aff53b"},{"version":"1bf7d9677be10753f2ede73e0578c4eff97e9b8fe1b7628d0d49cceeced9f051","signature":"8146af3e7bf09f628b043910cfa6b72b86f8109aa7b06167b9eb51a3f0a75da4"},{"version":"d97193507f74ca55d696adf4c7bf4dcaa581cc38da8993320385450a4837b988","signature":"5e25c87cc967b7bcd7949f75916a6757b59aada3685fddd966093696c85163b1"},{"version":"b7426a25a7942fd04027ef39d6e57d3652de5850a59c04b7a3b74ad2f335db99","signature":"ed09ce0bd7cf961caa2bbaa0265743b1a22acb59fe82fc227698550a7f0b1e14"},{"version":"d7e15183b1073666220cad96a18914084528dc05dc1e2af175c863afa3023e07","signature":"7afb481364c9e976ea5c55b9b02006f2496e68cc009eedc57f38264121a77836"},{"version":"b56e4b6d8d241dc9428b20e7be5d13487de4d263c5999f91d547983fffd8bed9","signature":"91ee1b220ead097d3cc5b596db9be622f0dccd9c05e4f4cf069f2e1db077511a"},{"version":"c5391ca708a239529f9f132919def5d73d4cd67786f87536da7e539d247bf149","signature":"44248c8a13f35779d07d3168c64fe9a1040ea2e66bfc4ff92567095c5b243e55"},{"version":"7fd84794a97f879f03f3067cc042ac622063d821e7b60b27100ce300bc65d833","signature":"78361a8f013fc8aea9c04034475febbc49998b63c3fe09f56dacba1e1c73f8fe"},{"version":"eaae5968ffd536215d143ee0c4a295cc4ab730c6306c0ff39da500a259fffe48","signature":"11fa086538a611fe1a99a34d1378e2579a4de6eac405ad7fb9eeaa51836977c0"},{"version":"169d8b256bea5a05efb2049b4bf5b8d916d986a97fe9000ad3af60c1804deb62","signature":"6840721f787baca46b15289facff041cf00967e6d746b6e2fcf657881b6e6c5d"},{"version":"0ad029b491ecca9c3bc7994015f376562f9fe7196e2c7815a7e7914545fcdb65","signature":"87d3a353f4a5033a14c02bebecb39e225f521c82a998c294c33481b9c5198271"},{"version":"505f10cf78d9caaf7df503e3c495055785de4c93e0286843574106d787d9f97a","signature":"4929cd61e267755bf505ff0a66adda55af5d318b84798ab1b46ead808203bc59"},{"version":"cb431697c9e94cf9faf8cb15dc79c36f21d951f2ae68a6cfa106b93edc373044","signature":"d21e563fc29f32dab8756bf5797d4c39b98ff0828fe02f8b766a8cd0f2130729"},{"version":"3c5a7c91728aa49db1d5eadc0e9f0d724dbb50b01ac203b8c577781846962d23","signature":"b524e9c8c9572a85e539e60885e7cd27a4a3734d72040582434a25e486702df4"},{"version":"c5e33bc47d97c9161f3cf286f89238e4097589e4dc86632a8a575135353883d7","signature":"bc3dc9e5cb7a7493571d35b9b2fa5a1f39cc7ad76f998ad62e7a98b56fb8df6c"},{"version":"a422e96804615648c7cfcaf2e23d5353ce5dbd305ef5f5467c7fff7ab39f5bdf","signature":"063c721b1237aa52f454a374210ba793cc38a5267af12e5f937c7f36bb33b6c6"},{"version":"0ef1d6c0063b12f4dea951dc976267bd8e11aca63fddb3aa10213ebd2abedf04","signature":"3e0b6c4d0b2d1c058853b3054d0ca2f00a36d93b462a4cbc97e0e20de4917691"},{"version":"a1d10e7fa181933ae7eeb34361f76d99ad2872cf6da8542528df84e4311da86d","signature":"de0b7ec69d1de3d88340668f43c9b8ef7086f96671d741ffb16d82ef844fc18e"},{"version":"db4c881c4d0036d8676e76f60ff17c6fcf240dbea3e48b5961e17d9a3b73831c","signature":"a2885e55e65c47dde0e39e7dbe3f9d931149d439b3b3c2a4480ae20b609bdc83"},{"version":"c50edce2d99b12b4447e3de82a3cbfb87332a0eed710e31f3a5306ebeedfee9a","signature":"3d577b57ecd8ee26a71f8dcaa01d354301d4155aa8fc228210f7980278d5a40e"},{"version":"c82897568bbe3658edfc608ed84b0615593c444b3dfe66fb884fe1f6f9ea0254","signature":"71ed8f0856c314b1c9270b9feb94da47f13e458a6b7041e75ea43a5a48e6d8e7"},{"version":"83b0bd3a78d0d2fd8794a9983dafa4f7fe6ba8ee95f7cd956dbc1b5ef1ae3f76","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cc1c0d6d5a958523960410c45f1e15874e8d8091120d3d7ef90f6d510b00438f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bcf28bc54cdceca8071e58514b793a72685363a07a190e0a093f90a74a9236e2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3daa25bd4bf7c1253c5bc577e84a9c3e08b4898204163d94e8f06bf93fe4657d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20fdd22451018bcdf123b42bcf8f3607b54ec5bfc1a40ce6f3aa195114fee50d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77aa32ef7822978656a1cf7a8955056e16072d0b6b3c71c8fe81998678532695","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2bd3da8d18b5839c651f5dfffc391a3f583de5e4a3d7f856d908a60f47b04ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c840fa95d2134e19a21130e67e75c7d75715d95f35921d62b1d50262d7e34cf0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a6bbc1073d12694e6b875ef3444737c39391622528871c6663c36bd214779309","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6449a0751d077973e8e32ee57044cc4d03a50623c5a291196dbd19178fc3e649","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c8bb1a6ad7f07b0c3af284d80c5b76724ec9b6c2dbc1720d1af4018b571cabe7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5585e289b764f82b17802e044380f2f72b584c02f0a9e5e5f9994fa14079179","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fc40f72f1c03ad660c6ad52cc2ec092594bd05e49bc5c960a4b0d30620dc55c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"968339f16a5177a5ee35cf9b77108d92938ec1da02bd41e361585030b4f00da4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bdcb2c9e692ee3ee605a7704fdb479fa10ef6d4271ff6b9ff995d355d40e2206","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"044ad589837559611012aad8bd6a946acdc485aef131a351c8a01c1bcfad9db4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"53a1989193c0f9f558c62b7eee59b3ecf57cc7c3bea2fdd469ed4fa2aafeb0fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5af67615072b85cf169a9b15a5bc2f54f874f32ff594fc80135b0229d46ed148","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2b3d3b1134cd416e62ad730ba82293706888320b0ab860aa34a61c02aa48789","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eef952dea22ec228c085f41b939f8824d7a8a9d5d53edf570d0fd162be862e8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"41c362e66d4ade6b4727a2d3dcf1a3249ab336e6812cd51cad747284736a3610","signature":"d257d8bae8cfbd36ea0ea1c5333150f5b290f7fdc60c41083a81153a4ca4cbbb"},{"version":"c83c8f01896aed99315ae67c6fb0a5c948bada628c8f7b19665a228711c2d340","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03c10706dc050b16e0ab8f3c5adde2d44fd9c4510394ded88c1254b29614bcf4","signature":"a257a955f81d30464899ba91ac6e7caa9c165d10f49b0e06bc9cae4cdad3bafd"},{"version":"5c45a3522e7ed545e4c383833e4e9590374dee2c6d2a4d8d50080bfd4a41188d","signature":"a8dd6879adaddc6d84af4fff927c3da912e5c65198c208823a713fb268cfb047"},{"version":"9d2e9036f50ec7b8066dc9536bd50a76f1a2e503c4fa7ee1c92725b694600d94","signature":"4f2f07fd2750e73d86f4763ee55f0ba88d59585ca882aac5cf6b5218af52a735"},{"version":"43aeb84aea08c738c5373e8ea3b4487907677f3e3e619f5f583d60925d221504","signature":"c1f55fce6df97a3f32d64e8e2b485c90ede6b9b6feadd640a3c16bb6329c192e"},{"version":"cf6a54f50ebe9b1fa179e3ae972e17bb5132bc1dddc612dfc2d868ca309999d5","signature":"619b2bf107c7c61e145876687ac47175b223d7cbbe8414b8d1ab5186064bd02f"},{"version":"6b3d4163477739b98bbda0e3722c1df15427f4fcdbcc044d4ae093622fc07691","signature":"0e81f3c44d6d754411d9b3fda7802a8c6c9567fcc7298542fafd23d879519d12"},{"version":"c4af4eaa49b5afdd70def3eb9ee71b509fa90dec11ea33591f7a1b1822400fd1","signature":"ba31eeb48994cf91f0acacad869ecb708d6840d7a8df864ecb86522256949502"},"920205f073d9e7a7cec8b42adeda1b66e3287253232703d75676ae0ee280ce5b",{"version":"8e209754fc98efbd0db41c1da78eb4f46d55d6da176ad9c6988b5947c02ea59c","signature":"34bad0db72824bba1a3419664c94ebe765c196975aca12cc24a7bf309f3fd68c"},{"version":"c4368b3ccf0073b6a7f2526ea0aa22e8184e98b099727db5884414724ee62151","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cc0c38dbb4436cef6d4ad0462c0b9230363a23303589e36042685c1132f33696","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a999f9be568ad3a72ecf729bcd348b4bcee26719790f21290a16b5bc7dfe839","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fb37ccdae5fa6b7325f5aaf5d1b28caaea4c148957839827fc5b1f7ab2b2e2d1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"177cba3134e2dee9afd65d1d508127f10141c81769cef693f3493e5f691892b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b54e6b18ed48a74d2d6129ca2ddda0aff1c30d2c46e7640113d2fe6669a5974f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69a9d780f923c1940e4c1676d0d7d02b961490eb84cadbd80376c98b07d1ae23","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b34a6f3217920c7cab89d81ea67dc64b48ebca1b4fe06e38852af49ea78068b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8a354ab401139f416005ba61675a503152089ecad7ac237da3d508779c29957b","signature":"b30c66f8aeae088710859fc3c16836dedd29bdc025e7304fc50dce105b9c04e6"},{"version":"76b797eb5bc8fe7158378f9ae1a16f98a76f4963b2a6eb40e1afce5f7574dc6f","signature":"f665a621665bf4b9ac13011827bc5cd5cb272d0adc1cf91afe269a599e6be31d"},{"version":"ba4ad41a9d1e344185c41db9d8bcd77361758caf451a76ac4844e690f096192b","signature":"6f3369ea3292063709715ccdc83ccf6bed46b409fbde2ac5c8b23bd5ca192401"},{"version":"fdc0244b111f72144b4b5ffeb4be73d77985a2c9839d87630366739702a7d069","signature":"0d7b280414b0cb316adfab6c3609f4d3b0c34aa5f942a74f5d0b330aee061cbc"},{"version":"2c7d3b8e7fac27fcfcce5e3b5a0043f58baf786e20e951be19087e05956faf52","signature":"6bf95bd5997a54eacb05169d05e4a3ac009a2ee4b1202cf0e609c84e711d28cd"},{"version":"feec6c48848e9e9fd2cc1dce253451511a02574223035461557a4bb97f173c2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"213c42f7d5367619fc1f7a022520c0cfaf8828c3dd910d8abb68b229c44f97ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fe4805a16fec6d9ceaa3834ce1ab4d8d3ec80c3c41ad093c2d09e7d7a00fe81b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebc3418849ce69c3e4935c9c8ae98abd05c1bda372d9ad08cd259635d6bcf475","signature":"4a68e8778087ddeb83520b7ed367b8e7a5413545211f1c12181b036b98b46972"},{"version":"29775ef79bb6d19d569a24c59922476271b093a1afffb1678254d66e938e6980","signature":"1a01f741b2cf1e9d7d9a1bef2e8547b013e1ad3bcc8fbcfe1389c9eece787977"},{"version":"426e0fc6a30225af3662c056bd33bc2af1b58e67ebc1dc7c4d47aa9075a3df94","signature":"edc9cbb7eb4f1ec26911e7cdfb0673eb04ab03be7a74654ca4b68935792dfde8"},{"version":"bbffefcf2d2194e3c9cae686f981935765cee13a5f390c97363fed32cad90d63","signature":"c10afa01e312d1ec1d2e455117340bd869610913a3ddee3e1903060237b2d330"},{"version":"3ad0a06341e90f9e25826449160fccecf9e83b3ae923de99bf6318f2a0adcac1","signature":"8f0537d31f337710a710ca9da779d9fbe37da09f82a2bccb3159ce054a303771"},{"version":"754e504a4f7e802cc110ec7cfab158438903677b6d1e5320e3d06b4215f42d7c","signature":"f50252519f170601d78c919fbbcc6aba2864e344ef66c8dbae519080a9ab6763"},{"version":"a3e35d26f2d2ba764a55bf9af3fb0c22806c238a222679a83b40c838c30c7499","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4d1729c06dd03ddd24e92985fec6aa5863373fdce658884e07eb4827df021f67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b80c68e22c6ef3a8c82b3e48dece693fd7b4e628542ac28b02dff88b31385882","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"22f5bdac2994c065f821a3c19074445873b02b4c89c5c4d26f95fb7319bd7298","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"da3a4a4651c78a13301492b76a30f6b89aff6919a14ce20dc48fab5473b99bd0","signature":"b9e301a99266862c3a04eac2c53d225b50d31203c93c570bb44b51e6df966f6f"},{"version":"084a417795f6930358662f806575b15cbe6975483daa9c40ae3764a1ae955b68","signature":"9e21029095d6b935b82ef9e8dabc88e552da4446f8551bb8e66bb608e221e7ee"},{"version":"9ffd818baa22a5a4a3494bda2daf646849c2635ad622ea25e34f4ee2c9a8f400","signature":"f1223da6f0fc1fca4ad9ae12e3c41227b7da0de5f55b38d9b871f3c651464039"},{"version":"3efe902a8539920b21bd44d2d0bed08ef8a95d3c4601ede6848a192af8563536","signature":"651a00540b7a7805a8de79cc6972dede798970c3b718c8374be90813037437cb"},{"version":"0bbcbcd6dd929d9dad0cf660bb39c2c578888071f5a7d80db51857ebc1c57923","signature":"9205ed03aeab041ae8db74ab3df06c747ba006d4bf2ec67df0fe59daa1a87d56"},{"version":"41f8f38651d123496907c1aa1b37c2361f1dd2cedc3aba6cb53c9ee253c7e4e8","signature":"7199bac5eac9213b52fe3a6d9481a0d20ab76d2bb99cbdafaf6ead4e5914e7a1"},{"version":"4631ce1c40a09fbe889057b38bf9d6daa45137ae0fa25fbf1b066ca41ce6f2b6","signature":"c685b52193d4c3022b8210703605d2b21a467ae9387aa15a8d9940785400fbde"},{"version":"1ff67eb52f40826c7d5512f924be11f6bec373c92896df0df557a13a8658f693","signature":"9d0fecb8068df90d9ab52aad97173c385ff2a17baf50297fc75cc31b3938c945"},{"version":"7025dad7d78fd9ad96f064ff669d353f930ddddbb39aa3c4984144fc6760118a","signature":"7d3b48b39ec46eacc882956538307aeec6db56edc31f31be7d6289ec2c92a385"},{"version":"8a9404494ea982c2bff41003f8de1daf258f83a54e917a6df73e6a6201862cbd","signature":"c93a0c999b510d141f69facbcc4d763280501bfbf78b8f1cdc4270af272d805d"},{"version":"b1f167490ed130cf9c920ee60fb21e9dd2ea9e601e9567e457f609a61f2f062d","signature":"96ac0d54822a7637a651aad1726587e96e5adeb6fd3e92f04e0c957313aaa83d"},{"version":"786aa97ed22b1c1aadb445ee997a12785c863377f4dd4a45365a1a90e1bdfe98","signature":"3d8b96ad1cab0524e81ed5283ba02e42100191203cd7f5e1280500f36a9abfc4"},{"version":"01f31174c59202f69635b2957a7a556a01c2ea194906befae45997b6d3c470b2","signature":"12f9e010df1bc3628cdb97e06e5b41a3bd149a6b61eb4ed5d9eab248bf5e2b67"},{"version":"6abbb171efa9fad3d88c9320ec5eccb199b726f832482379414fd55bdd485a66","signature":"f9fbed20734c2279dbee3f4186691fe847ff186337649bdccf7de42363d02022"},{"version":"08bcfa6546d768789b5134c6344f18ae851abd4513e3431e91b5e955f07d7eb9","signature":"90e7eec60d281be24fac0f9230a9c60c67de9d04a915ad11aadac11ea2715da3"},{"version":"d9bfe44b7126fd3ce4741db90af68d24cf8a56104826770276ee19f133496d37","signature":"3bc6339203e14955ed7d1f9bed916418c12a6a692d269a609b3a33dc4a863951"},{"version":"ae6be9a07e940a6f4b0743220077f33259542ae908744ab349a1a70d22f723f5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70442efc456b60235ae408e157a6f0caa8ceacfac13f5af9d27265a6a3f57dc8","signature":"926011fd4f1072faf06ef6de9b938f68b655974be67f2ea7cd63d5ee58d69338"},{"version":"31409dc7d6946f1566b501934ee84e4d61916cb6893791c2da2731b12ef24b89","signature":"cb9d2bede0762fcbf1f1f6e59445d5671d08ddfd920f8ea01612d81b3a4384e1"},{"version":"36c6b6a3bbee15a10e445a9aad4f1287d7d1039b6b58224a01524fc62446e533","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"11fe25d3baf912c908f4a63d2e05da668fd9a9838f258130cca57cc5be4744d2","signature":"3c7233fe8f8bb2292a9100dc596c64a103235700d7d34096d5db17ec0cb9cb9e"},{"version":"ebaecc11e0bd3f3451f11514cc0ca76bb2c763d10240b59cb187e587b9e01f66","signature":"a406bd45d11ccf4449bed75df91936ec197ad7de0facd342bb0ac55597dd7cb4"},{"version":"56dd3ebfbb1e02d419ccbf55a7a298872f8090d3803610e2faf132a5fb07389a","signature":"823c47cdde5eb643974b725bbfada0576890962d21434906d18ce26b06bd9544"},{"version":"635713a99868407271583323a9aaca2958b2abe2ddd43d7f2ea987160f6ff89f","signature":"866bc33412b93d1a44a41d1a8ee37e688082c1979b4dafc2ef88edc53a7a999d"},{"version":"fb888ca2d1491a87202204b095f2816e2e8041f8a0dc67718d21e3e963afeaf2","signature":"f6eb9961bbb1fc5507f52b8081d6e82eb3ef5eab264a5d926518296dc4244127"},{"version":"4dc46afaf28a8d1726a39f5e7e00b37df65de1ed452b673bd74c90797d4c6201","signature":"a0a545639911994ff57683e710206d749c7d92037f30c37ab2cc070178a7fc3d"},{"version":"63b2318993b6e0dcf67bc21cc8aa94e41c7de4936bc0d33feed3828d589d33ac","signature":"1184cd7ebecbdb9ed966cea0f626822a3247878fe0a83dee49e89b0f89a92973"},{"version":"454b346ab7c6e6fea1963daa8abc997bf20a61e172018cc3fb4f3da99adc3ec1","signature":"29c3c744e646ac31f51cb4ae4b0cf912d8d251972c6a958b100df797025a94ac"},{"version":"fc357aeb6dafb0b0088062750d702118459f2385d31434c8ce94ed1c1e7914be","signature":"995cd4a56687721b9ebcde8c6499921201e7bdae56f437f08f6a2ec2b1e1ca0a"},{"version":"6ba0e711d73e317b739a4b0b083a109fc3fd294985c81e7f3c284ce4bc6427d4","signature":"08470625f34c0ff0200976ad34ce7d65a1fc9286f8b8a884e0553e19a4662610"},{"version":"bdbdf92aecef77ec1ce77d842bad71821d8c11bb84335c99cbab6e6519885583","signature":"d507737f7aa3a9dc2f94c67379888cd7e1e6ee3c96ca265ed0dea283869e2642"},{"version":"f46250a3d3cd3bd34994208caa7d245088a529540bdf459e7225f4752509085b","signature":"4c117079aad8524348f5b782625f9663c24202732204ab321cc56f0913c99318"},{"version":"2fc8086bb1e429d2786b7d38419c2dd195328c42631f4c03800ba8b7d691fc6a","signature":"55853877ee77b90e1a143d58d14c4f5c2003b54d251cc9ef4c809f8ecbd4aa1d"},{"version":"e558a1a4ba6ab77b902576da0a477e0a728245e47785eb24bcb352f5204fd506","signature":"fcf5168806c6395e7acce0b4494632bd61d8dd3b7be90a4b3b86fd645f898b3f"},{"version":"93835e1c93a13d4c2e896a885c90698d28d07b65788cdc1a9b55636eb05e57bd","signature":"3b65f98cd92e0cddcfa1ed665b6b2d2ab06584d87079d3ca16d473c24009b2bb"},{"version":"c15e4b4deaf1fb4877793b7cf7d89f6254a54419ce5357ef98a2f800c97825c4","signature":"0a6956cb83f672f2aaf173e306a81c48ef904b9444d51c28c5d07a7a90321840"},{"version":"df70517f2532151afcebc39b9984bfa3c5ee4677c6e9938df86d17dcbe6a8222","signature":"c55b5dd40b4e4244911fb70bec24eab327488b4b6414513f29d5c0d4669d8399"},{"version":"b6bcd22d528966ac3b3226ce4368fa2548b9d27086496f200acb6778b4be9e37","signature":"67c1fa6b68da9af0b53802564e5571e32e74faebcad03bee79ddba76534c2c28"},{"version":"6b79a17847fdbf4ba0c82d9a7ee2d987197680ca5317c2a6a68ebb5bc9a4a829","signature":"a7d6ad6e9eb8f49ed5a46f2764a8fba42de8ef651c256c04a16abc78d4b787b5"},{"version":"d99e261147a8ca0295f772e82edaae16711db8d30e2511b98c59131f6583c216","signature":"46c0c755480b2e33e77428563ef84a0fa949e17e287c83baed1c53d6e9fa9014"},{"version":"caa900f1d326dfd6bc47d123e685680bcc21d4462bcda44c92ca7cb4318efcbe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4e14e16a2da8fcfa4982c8cfc32231f7ec7651440030365345e64956a86d5c3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"963e29c03860a04f42f2ca7723bf2f6c8aabcce3c2aed54a011716e20c1d65df","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3571219af6edabae2c952146660e1734804bad8169857f4b8ebe6433463ec3a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5f610e2b5a184bf4fa504123d543d6c34a35afa82f6a58cde23d70942c8d77d9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b6a64100f55b037a2788401e6a59d3850ce656c85f3e4a0a8eaf66a750c6ed0d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"85e753e4bd1bde91bdfd4f516cbde1f0eda265356b0898e7e7623e45a1fa446f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"16ed5d4e6d9bf022f732e27adf9081604d593b3ec37e9c7a2094c67d115d6e51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ba2e7bb3085f0acf77f6e173b0318d0db592580a32aed9c1d9a4bee49693996c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc529f36460fcb82d608cbf7dfca17bf60caa2efcfa2ffc62dae265cf1eedc81","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d36609a41f06c7968f01527e56c8a1cb6d5697867d2c64fe0d46e0d8efba55ed","signature":"4eadc8f12f74708d36f7a73dbd6a4dba984b83f96a8c0875a27b54e88331c516"},{"version":"7dafd83200a4776fbc6fd2bbda38b6bf4743cd754535adc2d0ac4a5cae258aca","signature":"bb8f5c8174b21b9b1a9d318205301ddeec2d0cf85ba3a7cd68ca9bfa0517f36a"},{"version":"b3f296dacd56947df11418f474b12eb09c180449cc833fbbb203c13e657b96bb","signature":"35f2abe6c86b8ee3741319a9d7a8c3eb0230e9b42f7bd7d543db7a94dc4e9051"},{"version":"8ca12e1da31b750904a7e9c542da66d01d735bbe9b798bcfbf9753bc9451aa66","signature":"74a54bff2e7775037930699111384bbb5d74f4de57c1359b5880f0f86182f56e"},{"version":"4e64be35164e01cacc75bdc277a3412e76636435de76c0b193b3f3c5d4290d48","signature":"feb2d5fdc50e327f8560baa4feed95edc4e786e9b164d7718d7857a96f27fd15"},{"version":"5be3881384cca3b8cb54b455055859bb3417730c43fb7ea39b57102833b60ab7","signature":"ca02a04122eca135259518c85da5210e6d924d9a19cac98e0f1cd55cd75efdaf"},{"version":"2d4000626b78819a6a26c46ab8fd01ea13296c078a8ac19ca144933e47826a28","signature":"45988a2c99eceb92797c0825e6351b563dc059cde42a94107c00c34530b64500"},{"version":"d50a41d0c949f23759d25178df952afebb72b777ff9aa3dcdebbcd298b6dcdae","signature":"cd09cb9b335e1a378ede556e1a96dfd9fd412e9caa02bf73cc09d256252beb47"},"1cf312f6363cab7b3a2e6c67480fa5a5924eb6488bb43b766244327a6ab75db3",{"version":"dcbd3305da0fa9f9bfa2b6775179a16b18185d820810dd243be17ca852b7bd99","signature":"7837dd9c018c571283ac6e26b11fd830ca92edacdfde40f0dbf8ad4e9643b736"},{"version":"da3e0ab10454bff69d784689a6017755f62f51f9270bc5ca33a780d8f1effed6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3514987210d9b0ff763ffd0a1aecd4fa7a2b4fc1f199c39b86fec4a717bd2ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0c282008ac1468a683e39296fa4bfecf89cb06be55359f6b6f7cd97acf4ac453","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"402e78c9fc8f2d232f0ba377e70c2ebba520dfde76cdf4cf3d71e28515c8f33c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9279672d35d72514a5d65cb870ae38fc12b87f6e814f1c8f60769021d49629be","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77d640a224467919d1eaecefed3e3bddbcdd6ed34ae045f4c6c879b03ea8552c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a5acc53f383e0d586667ea6e6a22dc8ee83f9cbeb851f73a287c833818465e5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"917d97183b85b400b895ef923475bda325f1a593dee6be6cda4163e0c649e1e6","signature":"925c75746c624e162e30df934c8b19a5b81be5a03978e80325c4e9f09bac61db"},{"version":"83d32bb6c68c36dc2c27d16caf470e429254d3de8c5c8f9ef91134d33299aae5","signature":"59ee2a3667021f2c7ed7061717eb0e9c7f8b0a4abccd93a8aade0900766e5e91"},{"version":"2af1638b9ec1f87086b8ff286b71fb279cad7a6256fbb6ec7192f31306f146ca","signature":"8edda68fc04a498391fe3e3d486b469c92b2e4afcbdd0b4a5a8bfe78cff9be0b"},{"version":"b2b32e7018f450b50765d979cfbedb39d6ef0443d6223b79e626db3d80c708f7","signature":"8490537159f5b3a3fd14f628b32e977a351e70a3bd09b890fae5616aaf894cca"},{"version":"b27e07ee2b259916d71aa0453912cae34b32a3492b7509feda6c537b796f957d","signature":"cf65707352be96547e90a932227cfc57bb9a3a71bccc6659328bd161ebffc36a"},{"version":"b26b234c627799e3b90925ea36bd4cec5e57e26e3eb4b95d598b0ee19d1fdedc","signature":"a476746c1a3430e74dc7d6764252eb4efaab3f931bb8ed285d82185b2efb30ba"},{"version":"ae3c82b6b88fe1315f8e92eb498df792923d5da97e77376a8e6434ac660bbc62","signature":"a5b40c328c53179858f4850879d0e77ea5f554c6076eb084c7a077ba81adfbf3"},{"version":"5d1a09f5ab37a76ea0dbcc20cd08dd4dc224e263389aeac1c61ff592cc825690","signature":"8e81241cc6e2de102991340c8878879924b204883de36540bb6d9c3931611147"},{"version":"d89ff4c66bb8ce9ecce1e47d62c2a11000e5cb57d27604af1ee22374cc7d6a32","signature":"eb07f404debd5b6bdaa86469be47c6b2a1e1ebe7c4d263730ba3fb4b32cf85df"},{"version":"7e488d2c1064204830ff271a055a09dc04d15aa208f1c2aa19be88ba19f57bde","signature":"2a346de3340c2b612e54eaf8f283d10e06620b02d5ad511a26faa5178f473b06"},{"version":"6a52477ffa08adc8d4bd84879ac20a5436f46333996cde7ca4a2e53e4e2f1776","signature":"b07b68f5938a55bf423545b75b3a448653410a1b1a09533ed9b00bfdd4c0ed64"},{"version":"b718744f7bdf7e86cd910c7e57698380454e0e94be0df2e1a4e658125ffe5516","signature":"5171645eaf377c98ada2fe1f7b90520dcf9b9356d4f4044b7cff35580836b049"},{"version":"ff3eda4c49da2ab76d049b988fa44b8a7658527d9abb1a22cd93c7917cd8dc27","signature":"a13db128a389010a8c44cc19ca53aee045a3f3309a1cf4468ab110962054254a"},{"version":"c11954f6c73d0bfdcafe0036d47648d71e8ca4f1a70b1ae88c815a703fa9ab80","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fc9adafb376ae31c4ea9501ef266f0faaf29de7d76aefde50ec9c6ceb67655fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"af03f35fd7d1a7a1b59e8fefab3d87aa8fc45501cde4d5f42657a0af2dbd3b85","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"765ccc4e3f7042c4bf9a0288838c93f3841d85e2c3fd10e15a17ef5da7e348a3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc705423f5e581231d5ab8472659fb37060aa3ade1a052f301ccfaa5eb836748","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23f751ecd2c2b9ce4449c843400093a3359bd77b541c50c815b3f3bb234ddbcc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1f283a5f8e29c8def3e16de0233029b469cb0c493d586c737e4d9c373e7cffa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e93b1c62e445dda55a6dfb00d29b489d986c6b2fae90a1aec65f9020cdcd6a59","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"566e62da419b55e2c0504baa8b1e36b7af570e68ff1efecd7db3fdbd67d75984","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd48793e5b03253b7fbdd55f3d0cc8aa7c66077cb7c43b9afafd4fc69aa65c13","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d3a4e8bf48ecbb6823f47d4883928e917080125816b24714745069c8a7f05627","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ff310bdb1d2c5121653e826dd2e72cd137c909bb92fbbcaa12d612e6008eca9d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca5e466b1cd54a780167ecb1b23e6be6ebb99ccd3e500bdb6909343f4eb08e70","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"86bebba5823cd4c0c8c264ab1e5ca89532125029e01b3701365d7c8b57ff7b03","signature":"80e816ce6ab347f332104a3b4295fcd234484a8c0f78af931f6f679bc819854c"},{"version":"15c6a3bcc2ccaba6a79ea23cc968005bd86ae7c98e1851abbddacda91561027f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ba697cec2494efd4491a9b92cf45a2b453b381938429db999e6b0ad8eb91b607","signature":"462de0ebec39f4608d311ce5efe2f7996417ee2ba050330f9589541e27badc9a"},{"version":"3b9f374fb01fb21e7d3dc1ac1bda5a6ca485e8a42d80c5857c0a907fb1d56d9e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e3ff7ae3ddb410e74e0c46707084795c01be15481fcaa355cfeef2da8a92dac1","signature":"27c8473ae6d0631063de4e25ab27c0203e687c741721337d833ee7a8d114d9ec"},{"version":"726922c369c19248989afd3de749124e1acbcbf0b9916cee980e10b8cfc02557","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"178b68ad3da8447deb3fa36b903515c68a878693390dce2c3c51887138a4d358","signature":"d80742a6a41d9f569db06f2a6f1ce341e38a8023e1f172dfc596ebf59b320d89"},{"version":"2fe04834987c803287fedee95429f29ed93194477634301a80acea18732b0584","signature":"c8cea0f80c3f03c528c1ee5bcd4584ac807b414b573259bc90cd3787ca4645b5"},{"version":"64e96839e33ffc472581904d3d5f5101ea95a39fdac17d42bf3b8080ed452416","signature":"4ad6c2671041ef9cb7425418a493fca3c8b38243087e4533aeae67d0a9da5616"},{"version":"c632abd896e5fc858119334bc27fe15d828dd2ecb2efa72b19ade831564e4a56","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad56f09ec02b513928021933ba8ccb5322184a5f145211adbb54bec8ab7c939e","signature":"02d62b21f2b1b3ae90d6f4c2a2177c849c94a135893850b697a16146152533b6"},{"version":"4d9ac1ae59eaa55088de16aa37191db82e512889ea2e3475c923acb0fb5dd1ec","signature":"e6ec51d846f163b420d420782dd42e40aee266aeff314d141b11a0307a86fe09"},{"version":"2b207ad5750863999cb3b248b98e29d8cf15b832e77bee46c23dd5c712094bcc","signature":"8494e8d1afa0d76f70eea09873120b790df6fe7b084458941c2ce07b55155b33"},{"version":"8a5547b3ef575d99d9981c3f2cec446e2b348ed27d728e3758ce04518afe2236","signature":"418948af8b278bd71d186eb1cd3e77e0692f5d950fb4f486a805fcbfe934e4f6"},{"version":"0ec34d2579a19920da577b641066c31e0bf05a7b1f5b2700f0cc325daf817a31","signature":"d3d6c1bd0b87f5ba67cd605f6bec93838c20c6f6086cf1480f783117cdf7056f"},{"version":"a109dbde88de9c839d3a7dc6422ffa7c7d5edb1f2de052d90c4f5a4f227831a3","signature":"e6c6732d88e711c29a07fdcf4cf22361827cdb0527c668da5e91b2c63923674c"},{"version":"9f9cfe8a6db296e29b8d96c60a6b2571f64ccc19bbb365971270c013cae6cd81","signature":"9a87bb6fb82202486cd4a2c71006169c516a1eeaf1a3e2303b374c2ed9f3b7c2"},{"version":"d165ab27ea9971500334bb5c37f5c56741d65f4c70ad1d6788e4582a222d2794","signature":"79ebf04474cb0d7a058c41fb366280437bd079dd47f2138ec94f3918daa05ae3"},{"version":"3b40021cf5c4b492aa5cd8fa0871ab438f0da413ca344de421849513e4332ba7","signature":"ba994537d2ab9e6ef4ac8ffc86dc36ba2b9fdd034a5725d1986d97759876b755"},{"version":"9a6a75a9d4cbcfe725e96855f3af3803559790aa6b7e48a6314be4497e3aeb8c","signature":"b05b871bd13173d03b8a6ccfd9d1d187d6f612bf672f565eff21e0da7055aa3d"},{"version":"579925bdfaa8ffdf328f0aaf7a2b98a43acd6c7e56f4902c31f81cb93597fb98","signature":"4478ca9bdbf267e8ba293c55d26d03b720b9006964a13d4ee05afbed4509335e"},{"version":"f749d4843e5fa4533b0e8bae2784314231b4404e332e8d56abc2692272965212","signature":"46c0c755480b2e33e77428563ef84a0fa949e17e287c83baed1c53d6e9fa9014"},{"version":"71c8ad895db3c65dfbefa63d75e779b2ce821e8badb100fdcdc6bc241f2f4544","signature":"01fedc4512be58611b781ddf06d6575cce9825bb18f1492ddc0b7174273b8f31"},{"version":"25ff64eed6d319715fece8d041173a27719a7616837f57626e812d1ec3c6faa1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"02f5133b4da482864f51bd4c061e1af4d67011eced5db16da712c5ca6d0af7d3","signature":"9ba498bea3aed8b2794b31437cf2cc47c2e1e500cd72521b38dd4e8a772d2459"},{"version":"2160b2891069ac50f73bd737ea2d4a26ed3f8cd43f483066e9715018ad38fd53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2006492de4323a0166b032c02a1f8f5f6433b5e9756bfde1d98f7902aad7643a","signature":"45b373ad2e114de335dd3eaf62f9658266d71c2f34537489f88f3b4815fa72f8"},{"version":"38dfac0e60c6379a3276ffe33739a19e2c81f3359a73f80370b7dbd615239da2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7ecfb0fe515e7462466e1fffddf551ab3cbbb0120b9c60d8b0882da1184d719b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6e05af866b9a2fba0ca03edd8461339787f298085067eac7179ff66826fbf6","signature":"845a8c55efa3e6c366c3d4fe6aba5af60a822523d537e8a3930466b565b738a4"},{"version":"c7a40c6af045ffba5250fd4b2805c5e57e5f7ce518690f180c83b65018840f3a","signature":"cf231aee194a0a458e33d6b2a8017c04c869079c965b00b9d294016e5f331617"},{"version":"fa4fd1a6c106daad4d2048e50518a1707039d574d96f2282addc0157b2143b29","signature":"b59523722261669df66b7a54b3d8686823768c90c5a8a04fd1a7c0bc07064fb0"},{"version":"22c9076e33fb7efff1af1d8c1a9c3e38eb017a1a07e62d78b8b64ab33664f2d3","signature":"53e646710346887942688dfceeb46259c4d04547c3f4909366bf5a9e3ac41392"},{"version":"78baac76996d1d214302749ad18c6424d1952fc441004bc8b1ff78e16ae94f2a","signature":"e0fa0f834bef15145ff38c4f94b555e406815bff1d72c3cc4b911bed38024c17"},{"version":"a41f813b81e3ee6f2fe6051c05f77671ef035853004832795377479c61cbcb81","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e374ce00606b66ae99a8dd321694504f11749fa9f407bcc445dd4eb6c6b3b5f4","signature":"6a2a0e9055a691ef8a292a143dd336005e96f4cfed93373adb6d1fb2f7d67cee"},{"version":"bc8339d6590cff26e515017178e6a430e53c0fe8f4f858355180bc22278a1bcb","signature":"0c25e09a2b6916bfd4fb6138feb16d394bfedda3d5fce6464478918e2f3a32ef"},{"version":"50a97612fd2557f947fff7bc53118552d9eeae0f349814c410151cc6dc305f81","signature":"b3deb4cfcdd96ff391f83c5cbe1f6880f7c11facf2ecf8e8c60983ba70664cbb"},{"version":"93b01da13427e2d00a8d73767869b61102d6ced5b8e6ef297006047e7a36b63a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ed34dcbd916c8746407bbe31966464ba2a40992a7d3eafc7b89fe9487322e0f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"980ff563c04a7ee054838de6d5581a1c74f879aa573e49083b767661eb497b06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"073d7d72dada0f47cf563f302854c2f4a56a0fbdb4ca0bb02878abb996b14c71","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"81f5e70b2efef928bafed1f1ed517b5f3d7dbe1bbae734620b1680b8cdd0bc66","signature":"31055f7d0532f460a1f2ec3229a6c990bfec524fb95332e3108bb50913d60c09"},{"version":"2f9876fe775220881f9a1dc662c4d45a1fc6c69dcbdf3394d4dfa7d38e7abf08","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dd7cc12f27a14a8b4160e7eaefb41b48701477ae419951c0063c8625d0ce9ba3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20e933198c82a21395745ca8398ef702bab9881d5224febcdffdded08766df03","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23a5597b552c4fa9218e4ff75c6fde15c735a495a7eba796b21a28102ab16307","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23fa3382c09d278365b7a211300808076300a0d16e6b7a7aceb22bbd6a5e2850","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32890338fb3db8ba265d19c7192bfa9a11bc5ee4c15154a4db81a4ddf1c8b38a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebefa20d8e7844bf717e29dea823d72e0e3851abec67bd7442f18d5e1c929979","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4cfb9e24d12ee634464b2e685f0e830f3871b28e0173cc89558416f194d49f73","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef7f54e0c441529398e2666a264256395d244f143f2f97ce5737b8ba12f9dfb3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a2f242d01ce2d89d7afdfd1fd83653b8d751731fe8484472e55caff6fca829c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dec2391eb73f6d626e7679f9c1a15a5a3939f799b408ee2ace519ebb16802d9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6c8520dc79618d2ef97bd41bd2d9f9615e8d7c31289ad6ff40202de2520d8a0d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d7c9c2e7dc4c35e0a79a12add067b79cb96493da0593a7e063db435257c7ece0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5afdadab59d5ba59e11b585459cc467cc1f23eafce2e1357e7ae9173112cb540","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ed8ce303eb9c07bea6cfa724060c049d83421b0a03c671040208438adcc1ddd0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc2358aa66dfb3288e71f8568e09cbf493eb412a7ec67ffa33cdc24b0eac922a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"71c4bc806bfef481e0a6ad07ad37d0be53ac5d8b0d19fb843e6a9549080dcefb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3e133c38d7312361e47e684f52022933865ca28b6d5d1bac3fa6e306c64e54e6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bf92a5c54601a670a6c8b9c02336b7a63a05b0cb9a05cf290d1cfaa95f28f284","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e8991aae569d37156933dbe3b3f24083dbb48ce7d8382f671da7c0ad563dbbb7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3557cba180e795e1819088a6d2897084fe66b04f6c0c03d72eb196d960790b1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9da4f2245cc7c86108bcfa87bffa093cfd5b532a90783a7f52c3baf79cf42393","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b55ad93c7c4c1b77f78a46e1d78564d3dae464706a767f3d25ffa5e3dcec0cd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ee249a2e5c93e9110ec235c1e89cfde32b81e509c667abb08fe9c1f2e324a810","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8f33490c1ff132d5483cf4e1eb80e6e6495eeee76803ad9e0bf039c16f6214f0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9530414f935f2d4311ff2b25d6d8fe9b119e40eb052183336306fc8be3c84e88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc772c4125fc9d21fa0bce97821fb5879dd123fdfdc00b666165d69a62c5006b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6ad131fba9f64b1c6efecc01403b93c63b294fca637e29d8d515eef286d78348","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"453246aeba13c55733a15b8aa23939b666b7dad48e20c2b940f6895f2d8c1a85","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a376d7cc82fea71186921ef0f2779295f1ae28d8685f2dcf5aecebd6ed897e7a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0c9b5c4082f748ded869361cbb3f97d405998ff5512bff4ec98ea95213085ae9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b3f09f17c91d57b6a841936dd215929d1ddb25b6cc36e2d5af8c2ad22efaea57","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e878fc887c74d6c43af5acbaa72e7f0d5e598447ee16c0c4eedc223bfee16a0c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ffba69cef9d354ab21efcc26daafa01e3426d6ce70629064bc121269544e2f0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f5fdf3fdfbda3df361d340493fb1df84a700ba2d928cf6d48fb3d47804cca24c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d098b2c0bf6c0333deacbc30836b8b0cd048f48b76cc4f7f32c5c448bbb9a3c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"83780a3b4577d40f2094e631b3929043444b0bb16097fcb8c7eca08dcb3c1427","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c0acf3d7f2a5d62332da4fc79bcf475ec142934b00b1b0c8bfd3893f64bd1c24","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b64de355d67c8f5b9c0bbba60fbfa188e0e16f107bdc4a72632a213c5ab9bd09","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d767afd9e2f82e7e899edc3775e1d86e5acb4c7e6268acfa95c551fc7c02d676","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34a2803e9127b665802f3808b668a5474c0e95e2efa58720312bed19f4461187","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"49c7bcb065270b2137eb524c7f0dc6c2de882ed30fbb903686d9fcfc71b1ae1c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6c4b346b7396de0a88562c85f142a3e6c71f0f0c3a51d8956d9d3d656bece75","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"17428ad5e6272b4958e99bad33e44b2c65c554fc5a4511c5ca18f6ee88277296","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32a475b7d9fb82fdd7df4de62fab6df47c2906058f70f5b68b7dd4796e0407c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"87525aac3b68b128ede1c21fe4f43b896ffb651c5507ec5bf554021789f0ec68","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b52485c59bb8f5f8ecebc36f9eebd5bb9e839006267e67f14f40bf57c21e545","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6230518eb3bb41f00853f984b9208154c9180a11639ac532d115aa34daf08a4c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"63e54be11fb7b740bfdeadd63e8f451830470fb4add677af84ca53813253f593","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"41222d3dac2c14b6a1f71e0b5105f2e3f860186aa3db1aff6ec4d95f833bf6ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"59d51e5e8361f7051ead0c29c8a03483e6929dbb6cefc3b77c2c497f2d895762","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34bd75b6379933f0a0371170d95905d43f72c8a3a2ee431fba5129470947bc84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ac0ebe63335c8cf5fd698cefa7904ccccca2f9e5d27dc9e0e18ae1cbb5ba066","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"44da1db5f81f80f935eb95e20e3c925d71d68ab43379c478ef6aea748a3a0b92","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd6074dbcef6177b94d63a539d60447a71cc249f93982528095f888e24d1fd9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"405ab3515b5d2f07531943438c5ecf082bd61434adbf4860e3f83cea145175dd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7356415ae2693e3f94e126d3fb31d42990d0efd882d063661d8a588124fecb67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d0dd8957db84d11780ab6f4fa208bc3827c49b5986f0b5efd5bb98171bb5a944","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"25028aa767cb234fb49871cb5dd6784ad018d94609a519cdc5334f590085d21a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"28f03986bd300c037d0dfaa877d4e1ec84e84f56f87e6a28354988c4dd313325","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f9e276f89ff3b71df956676c5779ac82c1a96513a360e6fa66de0de81195adf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"13bd658bf78bc035a961497fdb6d06b0329edfbc22985c2ce9bac485ab8e1cf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9cd1dcce9c4466749da349558f09c55628157e19f3755e2043833faca3f3d3a2","signature":"407c70ddc24d5c90bc55d198041d27c6ce2cb0f42fe30a091ef7533f5ac3686f"},{"version":"ad3602cf898d906862e748a847deed9392213387659382a47bda93090c24d2e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47beeb485aff1ad4e1fbd8ac6e8d36a24e150de10a30b6899840faa301655414","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"487344aba34994c6bea6db3099ad923d847a27e7c900106a17d5273f8cccaa06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90f9cb383eb3d9b17a982664056a8b1f86b8fb7b391e2bacca3b58e220a2a38b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0568baac3cdb203dd85282b137b649bbf8171ae2f1a61264cfdfdc1dd5f6c1ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d05701eea88d40fa2962c3e988e6e8c751892445eeceaebb8f76bf10d8fb47e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"73167e4dcf4eda771cdd7eacdefb1f35871c2fe41104840ed700732f69feca46","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"10074d86632d44722c8168202803d73c95697b4d6117ecbd68e5a433031eb52c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5f1a3661ab26f668a8195833f02b4085991113b44a0bc7f790e9c9af257f07a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77a216493a9f360de8197a4996159822af842dbab96da3d7aa502563ec12e64f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8fbf898b003bf3d70416df534552735d946ee7c578766469039551b5b5989a16","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d43aea7abe28c92b8494f0fdd74762bc1d3ec18b972711d2a883ede1ca8ae628","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"598384c7786700c7d6208cac6007b37f123131de52f69441e496d3086f01599d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8237ed9ac77a0e6c957c04ae1939d077b1eb214150e0f2ee2330dcc698ebdb6e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9db5db65827dce6c3005c0ab5feb8dbc60776a2767d1f3779e4e56b6ac0eee26","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"133e42066e3a748c1236a82709776425d0592eaa5a1bc0195b102df8160076a7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a57babc764911fd4f52710a78128abaec189a6b12a0adcc0e06267741434f4a6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"750e7f25638270d4fba9ee9fa59e79d2d97cc88e655bc8bf27573dce9ecf52d1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"745001d456418763f9801cd2f8e00a519d597d29efac153f41db8ca2b4cb5cbe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32552e3b687bed279decc2ff81ba12b6d194998a102b5592537ef0a7bc246ed0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"17000b2a7cbc8febc1c38e79ca4aff5a824bca523973aa7b5c4be0313c10278c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ae3187226d80dbd2906f54a87fe586f0b33961a92b99f74baddf23943ddf197b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6290d7ae201e2cb37a3462e8f0474823749c74478df2c024483ba0f66b9201b7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a1ed513cffd7653b442cb3d8865b226957b6a84390ad18d7702cb73bf169402b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3ffb3ab371ef4a1c49f3d70e6cf58152abbcf97f79b87b81fcecf0e349c9e47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"43c903a3a3e6bd110c6e1e0edf3f119bc3863e25f534de171957fceb9373b791","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6060795a11dbdc1b053619d909275140681f310638413d7f75dae71c0698a0fc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b536f09a14585f7a60c6198eb73475cfda55bdb6eb7982562b14d9745ab3f58","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f21b2e5f827fd2bfa35959f943d5f7c38bd76195247f63a7e00c35c869583c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c93a6690c5ddf530c35ab275c70a4a15ac6ca4a74275d3a0205d1acdc8f99d2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a1d4f7b55a0f4585ce971998ad5602b25f56fa82c105750c8f770fd89f61fdb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a25ecb2df0785bca73dcc65bb1971e06ab8dec22390defd7bc0fb6e56bae645","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"71cf715fe7ac9cbda3398c62715e9e41e205bb9f66c14db522c05d33d9bed871","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c170d6a07e32644b63485cb4fec95a7b4210c95b0106bf604f77f60be4590609","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7e8ad08f464b4d38665019d1a2e7abcf8431a2fafd4af65bcd93e71e9defe276","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"284afc03d292b1476a7abafc7a199b1374eece1304d742dfa2fffe29d1ef0c25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"724ba566f050f9a5c9d59f094d43c5986a190bc913ea545fadd79e99201c1cb7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"63c289c6931d3546d36c0cb59ea38f2d22ce5df282547200bf86dadb4cf442aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e9a831e721e46b46f371bea50434b366775486045b009ed500a273a0c87cbc6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"220c41cb6d922f9df023fc9633b25d3f277be8ca0b6959d35510aa0ce0d7f435","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fcf4e15889b48532a4fab0550d27792e595ac7e53b656745561bbf0499175c37","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9f93119e73d9aae89eb4897d9fcacebfc8131e4fd6add6bd0af2f085efbc1b5d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bed5a24b28678ac3060e6247e7f1028d52c3cd0a5da6f8de620813357bef52ac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e8323f9bf61781a5c665b85254c338ac0bc879cf252c408a9155fcde6d3926d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"58da354bb341bfae058822830e25841c7d4e322f2c01b523533d976788288a79","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5905fe6fd8787042e9f2a85ac2c37182f7a47b7dac70e7929ccea5bfa8fc51ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a45c928a098549927b5e3e27d83b1abaafa8ffe97af5ff78d51dc9e983f1cb41","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"de10d6cc7a07ce5c5d961316be25ee61e38b528aefc5b78bf4890f24c0749f6b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"569beb54f189db6412e1bd14225b3c003cb7ea7a8b8ac9d2bb4a98d443a1202a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2bcf3b15c705b78d2624ca829055672f638ce38a4ec0bb25d7f776265ac833c6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d7a0f90adedfb247320507bc1f490cbff7e5c0236bf52363e4dcfae1219bb9d4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b2a4650779610aa8626f855bdead2a9ee445074ac77f0df56d4c3d74d471ac27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1a138b16a062718039f7b4a0189c173d4612c918f1391c15a13ff9d74d76c0cb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0f2066a8d178c8a82454b1b1ec4593e7dc6b52d802df5d6073180fc4affe3942","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0cb88121abd9fc2f1e7d2aa933930cff72eef65fa010ed23f923307e56193e66","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"570d900e54c02bb666819963695f97ab355d3a10137e4c90d48647fbef5a8bf1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"279259cf3f1278d44774d4e4ca7738ca2a7b23d5df3422c8f04a4ff8e435460d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dacd876d911d48bed49a48b86cf54c0ef8ddbd05ff05a7396ed34f3f1709400b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"535301d34adfb07b738b5511b1aeea6996f5cd1e1ab900f49e43419d8944fc2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1b5f939daee2f045b7d23a12a0d3650dd49e4f9bc351d2f22e696469051d4eb1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6c3b470578a5bd66ef16829c185d96ccefc3d2a3377d9976410f500610ab9628","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f13b437a12555b0ec040c3d4f6d3aed3eda3ac447ef37cdfb0e458b697a97b8f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fc8cb3e5ebcc82df41bf9b42982dcadda2f7715d8b1b2df23b707388de721fed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"972a8cd8b3335703b18119089e6d0ea65460a6b0502350734fdb77941bb0762d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1003da076e1cc06db0b7632ad85dad4c0af857ecd949856c40e87a733c9ad2dc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27a14d107fa2f36104dddfe0d0f3ad259d6a5a8cf3ff91cce99b5e493f9395c6","signature":"c754e6829c741e6b805b1868f57d8dccbecec8f04c2bea49c8fd3906a9b4bb9c"},{"version":"00796a0b209c7a10afa20dd2ea1561eccaaee5ab2798c4b2b7641d5eb16d9205","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"26db1d864473277cb2b8327fe5a4ca010b2894eb1be5087e817095a95f74938b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b03d1c836d3624a6ab8fd8395bcd1df2106a4c7da12ad82bbc7fe448968e7f41","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"20d1bd40bc36713f75dde61ff02bdda74cee057be3c13af6ee23fecdae565d53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f70f7a7defcca6061e9a022f5847772015898ff7f582b3e9f9991181cc23b7f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0f5206a0b32d6320d4207fec04712a52460492e4a27e4af8d7e864bf09acd24","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"26085d6a7b985e91fad21164ed5cba66427dbeded7e0a672532ecff63d2e7c4c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"87dbf0346d5746894eca4b429e98201f34a03e11331cf456d13e71c81212e426","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dbee469d488b262f97f892153e62cd20ee4724dd8b7d253ba771770ac8114c67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"37c5b9f699c69780b09804adbd63f971d028523664a9a769f92d9b407915c2ac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"061d0dbd29081c82e7739053859db508c8bd9038d05d659505671fff58e03a90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a13b36699f297594179427601a6b8cec07112c1638ca4ac3ad451a7164bdb3e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"52d51a3b16158da8046ce3051acfd48586035a6ec20e3186b40f9fb0a622bc7b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95658c91b67a72ec6af1ede02ccb5802d685bf848391710bb006f1ff1de9cc67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b1c0e19c69e765adb21c7b24a911f38d3ec08f8a4c5bbad7cd7c15e416f106e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"baa9f93cd885deed2211a1f17e2b64074d45217f6f95784d9d7db3b9adf39f7b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cdd21ddbcdf8e5073e31fe7f730fe3c4023c66309625b87b5b9785fe140190db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cac72a71dd33cd4dcf93a4c06a34590d661d2ce406b9734cca33f567ddcc7208","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"508859ba82d5f926349e4a9d51add2f33fec2eb154fed40a6a80f12df4d99bec","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9fa26168f88bfa67f9b9f82b7cdc70c643822adc48535a76c320bc7d262ad78c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9f11d83ac8f4908d460984e703c13f43b69aca1572d2949292bc9b95ecb7a2b0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47c08e2137ad5ac7254ddef770e68221d041e397acf64e2059b92018c4d38b4d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5458b79d513a3c28249bd399e109764da57de09097034437d65d13753035ec7b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abd6d932a4f5ffeb10ea89ddc43d53467aabd67f1424f03634d2bdb9e91bb39f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e7484d772180a4fe32c9d12b3701087ec6479a1fb4027d02443b362d6748f265","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d22df3d0d4a171faea1356d2ed06746654b7b54a6f134ad5ea64f2bbffbe282c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e3ae440b75e800d2a80437da11c4e3a68270925cd4382fbadfc5e2a426b144ac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6256390bc79dff5190177864fca522b99f1ff8c690ab411abb268d2660660479","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c871c193395edb4f0bc64f8dedd55c8d15a51a9519046dec95c4904242d7b2c6","signature":"3b5031a79ad3b873f4979dd714732927534e3a6d3ae7a9ec689c5725ca791ea6"},{"version":"3cd49322854ce1d737e709347cfd3aea195ff6e1b262d5958bb256c8beecfa0b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0f7d44afa8a022241eb790ad981f9ca901650e125283ee25ecef8dfba63b1b1f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c144fb7b835575d4eb400187da6e88cb37e0e58c7f2d430bfaa511f7f471fda","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec563dab247f022b8527fe82436349f3792b975c4e939886ce128d095583abf0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f815436168a53475078bbd0aa903c756c66bca0ec8c468ff534ca4312eca4bb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fa2aa9a0b964ca9bd71c8f1b2554010f338e89979fc0581e1f273a56897086f8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"01655680390019da612e557fa6c87313dd411791e200ec4a960546fa1c73860b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79bdfee706e5a2f5afc91eff7c3a186da1c451fc3827038d6bcead0160ead42e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f719002191cbec9176949717a9a57b621e3a1d307a74ede4cc94dcb78c249c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb67219ebb0d8339188f55657be0482d7eaa73cfa5fbbabfc76bf0757fe217a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9cd6b43d528855022541993f9db3ff28d73c7183ab1043df00fc36073d6555d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dc96489dddaa0e3e3c918fea22584a4823f0f5b0ea27df04311ca25224969acc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9e68ae5f6432e9c50c43e9d2835536cfa152255e680aceddeb3ba2c13b5a24b1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7fb4e3677e240e9cbc6268542e651d8d7142cafc9b716002ecb94db2923231f8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4fab0835f3f0569611b4185f74019264c8ac4338acdae9786c36fc5e73165f72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"095cc9d0709e52c5869e31a60f719773a43b60940f726480b689e301eb661d9c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6e4a5152f75e9d77ddeec6158887b08565816164545f301243fb653d7c57c47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b850436d0a9d744cd3fb92eb3be65206791e3b4c21cd66fa1af395074ccf9520","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5ae00f9eb7202bad96cf17277b37f8eb7ea8dea3e1d29766ca904c2b81f54043","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"33195f2e0363a39a049cb3839f69891f3e92cdef82661f683823b7d4f2f3d3cc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1a91b7d838f633711b64538b4a4fdfa77eb8ffc9e1a5cad23d66a43cc9d1bbf5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e30d7ffd5f108b4f12429dca91377297ac7b070fa87b5680201b4c3da07ff6db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6eaea317fe5b6bbceeddd6440306eb6dfe56c86796b5e90b0c86f03d98fff955","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"539ea0ca2bc54254c4751432472c80d8a6336e592b9701695ac473aa6c9b4001","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8b10a43468849bb4be5c7ebf6e582671999c7378ba459e6307db755757aef954","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce30fa93f285427c6251e073491cecdaf1e80751e13ebb7da419092fce4393ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1eed9281109c026b9f052241336e80589c39df225980919ec591a01ae388f11b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b2a018598e7116c04eb2caaff9bc3de6378bf8646bd96429ac82c6ff2668b9e0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a0f194912ffe562a67d5570ee74538fc74e5b9ac3eda0c8188b314e72bc0b1a4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9c63668af53291cbd777acdc086a76266b1f9c51e354ea2787619ffc3c10cd24","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eafd0838df9188d3b117c9fe53e0c77b707f5a985d3b8af99f664de7a4bbed33","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"83e6f2be1beac83f30dd5f1e56d42e907c7ce21c05ac72970b6ebd370e5432d7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f59b8b9f9609eced7551d65f5a9d36c47c3e8e8f946304c4a9202d8748c87e4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ccc6e094800a0ec7e18a71109ed4efc28f2070b608838fb625afe4ccf0dc9b87","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"313a574dfe32f592b23877fc0677f33c8656ea9970e4af30ef78b96e17e0a032","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3fcdf7901f8d9f9e77895e5b0743e77242c2710c17d8ac73beba8a79e433b57c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a991ecc505e086074ced217226118fbee9bd97d37d94e9a73cbe73cefc82b23","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3c91bf1628b9af6723816e7f06ec22cbf5627ea3c793e802eee02aea37406231","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cea188c7223d92eaeaf903844518a77ba6daa9160538bcf25d4f7e2990a8be01","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0daabc5b3a23e77755acff32e410a9365d2266246af76bfc96de9738a6915bfa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca5a2320c781052b195b38bd95a7424e01468dc5e78ef946d8eae4d218437eb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0dbc717d091c928ea25aac5b7118713c489b0b07f74b6ae3a57803d4d704c841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7fa3cf6e959f107cb2e099dbdd80e4d78f6aab3a0c012a77a7b0d1288917c2b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"29a34d2e9a40f421e4983fb1849a26f666b67513417ae53112b81f859d0ed18f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2f0f520b1cab36fdc9f80c54b74f17e7189921be14e5e6384c9e76dd694c5df1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1f20c6cfef7afb6001fe5361b024b2f4863f2c37eb8bd6e3d3c2f7891ee91317","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9210170f2fa566053f02e2c5c3a77faed4e7e51d8366ec02adcce7953297fa56","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b01dbe7929b0a92420ded501af329eacee87e3465038b6b1a0950bc7c8f90421","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"92e3daa4f9c571d990375be84c4f55ac2d68e6eebb26ba066b18e6712b4ad2f6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d3e64b1b1acb43e370665f3816223c516a760ff13f6d29f08043d1cb18afc95a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"803ce94cdd49ca8ed653e63004ed3fcb16ef302b983ede0d5291257babef6bcd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebd54d09bcb969873eff5f50835348d41ed084785a1a53110b155cfe0875b7f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1d57277c40e6512296d6635e280c6228aeb08d789aff2eb4ef865eecf86cc78","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d71d12f9748990e5a21ef6fae3483650f1da187533e520785ba561f8e8f177af","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3c1c6e2d0c647263a8aee2d16655f525c930d6b9784eb6080c93ccac28a7c9b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a93fe9b1bfbc124b8f4777276537084f37469f91fb5ea6ba8637f62222f9d378","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89e0c0b9430b0635a17c439eb81fe536ac9ad69c9229a832c1a661dab780a362","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3c21d7eca64d0c7d8abaf6e2a69f445aa6e514be6f0e4feb54c76781a507b3e9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b57719fe1738f95cf28675ad0e55eb81a991bf372a7a5dda6c45b162bd094d96","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fee02d6d186cd9b1dc4824242b05768bb2edc61614f01ad6207145744366a731","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a83e4dd75c54300e6314ea2c0c5813b418d1a2244391acb001f263c9b1b37521","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27c25a73ab8e8e6ea25f0679d1ef24c446a929a7b9da8fc842af72349beb9ef1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f0eef830593e6ca3e34d7c4af265fcbcd5d7ec2a6c980a442a8a395c98b7d872","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"752bc0bd543fc478323820a8595d137ea1fb8fd0d8ceb0ea05c3ded4bf1d3729","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4410ddaa3d6e3c1441fc5f669ea5c3e3390fd75f6127f06b1240625558160a9e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36919106e6e5c86f0628d2542222b4f6a09cf7955bd96c53a9f17a09b62f3903","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f6be390877d224b02106db41d582eca38b6d52215c0843d3e6d78d210c956f95","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"477bc6781d39427cdfb58b00ac6744fd72a76ddb9add5ee2b6fd7c0123e8c133","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d77a4e85ac4e7465ee559c7aa33e9b67794fb42eb006094e41de859e0f574567","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"efaba8801c46c71114040269fdbc963f3496d01a5b185ef05612d3d71f6c1fbe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b4f65e7dc37a2e488233381af3c9fdd7ca6e0171adc8a9cb2d49d2fa17cd7d67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90866b821f4e9d5b76370bf9af71b3bf92fc1fa9e4498452ce90b1035a4e4737","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d534cfa39fd1fa8f70dbd3349d67920554475cefd0087e7c3e418d544eeb3adf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6cda8181158bb3331be538ad07a3a6627c80322ea32904bc0807eae370c510ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4733417da9fe7eeed82209b53ddbf53bb76c0a7706f747945278a2c037ba2bcc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b78fa0476aae9c90f1ba345f48912c46ff37b4c95cd6242b75cb57efd8f2bc4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c9959681b8cffae14e821fbfdf3daac7759ccd92bd04413f45100301d8d08d20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"57ebd17c1bf0e09d222252bb67deb0cef31476b2476c680ea5ceeca29cfa3efe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2813a4305e2d3d23e4997d9a2f482fde783962eec4c66dcd112a3348a1b1f6a8","signature":"b84cea73e43cd5d152e01d2870e7736075b6c5ffd9355dfe2660b98078c17e9d"},{"version":"c6a12b1489747a7b0203fd3e8ce5905c18b426aa79fdf9c47578dbf0f91058e9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"48392b4f5473115f4cbd2da11efb0fda7bb0610c15185a5838260c9c2b2e5745","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"931e404359129b88ae22b707a39298f2d8351f150a5ad6feabb975603272beeb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b522f77d275268e582dd53f3dc4f93082eb2f79a0022d066bcadb94a59b6c88b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"002c7bb5c3156eee0fe39dff54c4741c297b56aa6d4378a8acd34507505ea63e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c77cad2e3e80373964256a967f064b23ff95f5fc46788636eac8b765b2fea524","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"75424e9c1527a998d62a8b265f710ad1302623e4de736fdccb156f78d2909ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35c528989066fabdde1d1aafeb745a6d41d5909a454992c1107c318f59f80f8d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69b3922251aa575049849afbf72d429c17965f5827fcfc0b6636263d0a261779","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a23b58c3087d419c5d21fba70096b8a9eb42977ad61f22f6f7fba5e09e0e6ae3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"055e5ac1ac33ae174595f55c76aa1e371ca8819456cc5b97a69872037139ac72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"45f9d9bf9998e2ec7147ad27c7edc2e5ed387302c4018c9f4f7ff088eb22af8a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"90e9e2b93e5bae19a5e66972efb5e6ec11dc1b50b9e8259f882055ccdb3d4aac","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f397adc5718aa5b8ea60ec16afed311eafe510111cdc0de0378994c629ff4eff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7a83e8d50eecbdb731f8da23ee08494be2d247cb9e8e5c3857da7cd9e07fdc50","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"671265f64fc5c31cd317267ead0afc5c6c4634fb51204bfb54e3bac5d19d4db7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f1f4a3bf8d46ac603eaefa297ebfafb18a111a4854577d169bc3c0358bb373aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89a4895e643533b7b76f782b52aa9b695d0961f2695ca7720dc50b48e9e55215","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dd12c9b4822755161ebb4ba65818948561a5982f5f493eca9f6f0db242a468b3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"114445bc0794c2c9a3f03a42134748f545ea788a004e4667d7b9eff39211a61f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a47055c95d43ba5fec9e3d06bdf5e2458db8a93246b28c9efd0525fffb77f74","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"09b4d72988c4682aa5713bc6a6df7892a7b8e2f10e1af3dc02985c6d4aef84ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"baefb08b615c9fc53a31c27981bf8899af8e01a0c9e2ab60c23cf0d324d77274","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"465069c75ef1e4b084bce885c0a2ee70520c5ebb8f201fe6f85090a28fb34703","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e1d5c76c59c49b2b6f32b7065d0a95bfd229908da2db72526ebdb9312ed2cdd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"99eedc96ba7fa339e3ac82727c73628382af56287cc1219589004ea36e1b0c64","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"40510866633ba6c635e0495e40994f4e3f30d9378f23cc26887b3ff5e56391a3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c9cc3eb942b0cb85daf312f463a21c77b019a156a10240188e26976bc5f274b9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"288e930c1a2661f6345d07635585f1fe13c2deda86e2ccfc349413716c420555","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7118997a67de7cc91539da74dd524459d5cf3f9e2a8b90efcf140600ec9a4d82","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"beabd1db71dc8e0911944d9400ced2cd02de425ffeb61c6ca0d2124cbe64d785","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"945b34137fa3efb0ae22928275c1c42a722dbb81a26c57fb02f64f71e5daa3ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ccbb103828aaddf117b3d694cb122ae8657298732bd550ae10bd1284366080b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"294041d51d1910e6986cfe979cd3732a5f7eae7f329589ca4f2799248e5a7265","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4516f87a2270fb1f66171535148068380734007309d9568c4b81e42124e08cd1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ab4a93c3142d6f517f1d764f00e83b838fe91e8533dd3b006c2b3c2c901b4a6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"de385ab9ee98320c98662be263959c7d9c55bb2cba5e8d7f9107def8bb82e4f6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c86fd4b8708bcfe085c01b61dc75aff81070eb58ac0ee7bde066589d1876b7a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2309f180e82b613a4f9276b83512a7a85241e7651e734437bc3e89f2cfb342e1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"78228dfdc1a7024c9e4eabee22c057409886b9014ebd22319ee8a0a9ed31e799","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"18beabc03110b8f4e1d1eb5a556e6de09834d365995b2f10b17d26a574dba141","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70e8e8b44c2fca9791db4f3c06c3cb310556b19335d656bfe3cfe6aee8d65622","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"995394b9345e8eae0c2413b22ea07faa239769d45a58bb219b9222d86bf2d9b7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"04b5ec07def664c916b3a73a5b4b31f3930a626739ddb528569bdd33f0300456","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa82aeaf9be0588a652ec636bca8e6d7be86a81f85bb22e857b02a469e8ab2b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"028ca5c0547be6d2d8d1d20306614c16c3dee3d6501f2c38031e03aa6c9ccbed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4b54f4c0273db6878a1823ac888998ad7a0dd816f1c45a2fa24e0417702fc7c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32b1d470365f006b5fb2ad91d9097eefce0fad34bba93764e4501f2611104482","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"41b4c577be164a41be457fa1eff74c8923c8f08e8ba7e5e57d894424f48de2a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15c805dfe9b0eedb507e5a9d32ae6e321327d77673ba6181de4710f2c2634cc2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5cc1c46b57a52eba565ad27fa54cf2e09d763de1f3412354357e6085e0d89ec4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dd7ca639d42b7f9566dc6f58188e1919ddc8f3349dc7163c0efa1cb4205cbfdf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5597c2bdc52f017f791a1962e3595e58f628d60de80407f11e0c3020565b52f8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47c0e01bceee2d7e95b691b2417954d55251167544413855e8440495dd67a5a8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c2b1d972a2b83eb6f85a7d894c57331aa5be4e9b93d1b9b16d697112b52069bc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4599379d9c9ef5f827a03600471298c7f5ce06580b0ef6364a0f1e82423ad9c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"931f024c731f1ec9fe2618f279ec2ad7c4a74e5378728dd2a60f427afc066227","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a1ff1783f054a0cc5e2a938d35db521e61071d522c9b9ba9036bf33844dedd99","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35bd7fa89a59ca3b136c221ef604ba3a8e30cc88bb03cbc4d26fee0659d02ce7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d596725c15eee936539fe4bcb0ec9f08b2d8392f0e9bce03effb76ed734910ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b08b6177c9234876e6836895b0bbf4465e14c9b64bbb7467da5b89b9b5b11d89","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ed0d11407fc46bc08b4b3b36c4d168f207ebdcf78496adef7dc126513efb6877","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a85610ba8978234a59d37629fafe05c27fb2154cb62b6b775eb8119802e0a38e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"adfa78b8af8a8be5116202f634a2f113d7801ed20c47767339f1505f952ebcc1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f328b3203d26b5f709e5a082bc956c2e95fbd9fbdca6abee0802b59b633e0dc0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4435ab8057ea5a31a324cc45bf3845138b7589c4ba8399f4f554b17b8fb79489","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d54083855fcc2ed66dedb389bf2efc33b892dcf572829e43758309fe7bcaabb3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f44df634432426f2b1249398b735f171a84c3902b4e0452ea2f7cc3d02568bd1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7952ed742b48930403868cdd2e09a9b5aa543c9adbed9f012618d6b58c289dff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32466921bab35fe4558bf82916cc03ac02d5d6d87d592fe1c4a141ae801ba5ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ba0d14c18aebe4a5cba52b4a7b902247dd5a91106737e06d6e2112b1b4cbcacf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3b8983ef3b44f8779b91c7604f70379f8c40f88da3d6863e4bb7a5d7f95b2c98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"046287f8ba5cdeb90b848a6eae7311c5a34b343e63b3fe23d3eea19eb59ab284","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6889f8eea64d18bcd198ca1ad22b306cf48f4e8e213e15d3c789e98d67e4c87b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ecaaff298281e8bd8bc234e03d4bc1ba565a804edb846005ea6566cbcc47fc73","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ceded34bed1c475b90671a320a8fd84a6a4a4d7c56c3f3f88d9a6804e933eba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6679ae1bd78dea53dc058ae235a3708f27ac7f87da929ddd38f7d4c222c18f9b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a40eaa7da4b2085746448671fad7ca6da6a84c58cb1d0e2ebfba17888d040a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b2dd58af1e54e5d04b9130dbef33c83234955d0faa6c32018be9934e12ac2015","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"16e6cada57ba26e2ea6c272e08dc7017b9df78f114061b8144ef1c0537bc3274","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9815b675507e394e469b6bc395afbe8c63d6736cc7290a73f56cfaaca549b027","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0248daeeaac242de0ea72ac0f093a31b55e70b43020d40380dbf609803a45e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a31a79a9691f6153276381e906dd27e985f53c6920adab35199527cbfaeace8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3414d416e51f2846b21f4aa1492fd44bf9ffeaece26743ae1527154e3da6f7fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6309b32da582c7b3e5afdf30678bd7d456cd9a1118ea1c660dd73ee32770d683","signature":"4c372df16f354b44e6e653a4442eb9f26b95f2d43efcbaa75b59506276b92df7"},{"version":"26f213bee14ac8092e7a36473db58d1955fa4867bf5b091950ad8dfd31956809","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b332cfe58c80b9e5abef88dfe157a88f9170f64035fd2a83dc395b334c440fc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"262dc2495f719b674acd7919e678de874580311f4a0cb71f04c69995bf61650e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1161b186fb7ef72c0dbd14af1652937e6cb3453231dd6f56d396f43d46d638f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89ac6a7385062683575fc5ad85a18f77e6c9617a3786f49aba644d55ae277f4e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3e33a62342fe8bc07fd5ffb6e870ed8f0d906f8021115bea5b4ef5cbd3632d04","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8bad29d0d9cfb3316a408a53c38ce6c56dc4d90b1155f5adc9a72c5e6ef6476e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b247732a1ae37a5e0307d4333ef15e3d5393951e3236be0287adeaa48d553b35","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"1e11adf04ff8cb1c16d42cd6efe0e1039cc1335c26671292bc2c96ecbf9a0b30","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1538a92b9bae8d230267210c5db38c2eb6bdb352128a3ce3aa8c6acf9fc9622","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"89e326922cadcc2331d7e851011cf9f0456a681aaf3c95b48b81f8d80e8cdfba","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"7fa8d75d229eeaee235a801758d9c694e94405013fe77d5d1dd8e3201fc414f1","impliedFormat":1}],"root":[531,532,613,614,[630,634],[636,640],[1034,1038],[1040,1048],[1083,1138],[1175,1245],[1312,1336],[1340,1368],[1371,1380],[1400,1431],[1453,1576],[1654,1656],[1662,1701],[1933,1956],[1958,1989],[1991,2056],[2060,2068],[2084,2130],[2275,2302],[2304,2316],[2319,2379],[2638,2652],[2654,2667],2672,2673,2675,2677,2678,2682,2684,2686,2688,2690,2692,2694,2695,[2700,2722],[2810,2960],[3038,3404],[3422,3424],[3492,4156]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[4155,1],[531,2],[4156,3],[532,4],[3490,5],[3438,6],[3436,7],[3439,8],[3443,9],[3432,10],[3442,11],[3455,12],[3491,13],[3425,2],[3454,14],[3453,2],[3430,2],[3437,15],[3433,16],[3431,17],[3441,18],[3429,19],[3440,20],[3434,21],[3463,22],[3464,23],[3460,24],[3459,25],[3480,26],[3483,27],[3482,28],[3484,26],[3481,29],[3479,30],[3449,31],[3465,32],[3448,33],[3486,34],[3444,35],[3445,36],[3478,37],[3466,38],[3450,35],[3452,39],[3451,40],[3462,41],[3467,42],[3485,43],[3446,35],[3468,44],[3471,45],[3470,46],[3469,47],[3474,48],[3473,49],[3472,36],[3447,35],[3475,35],[3477,50],[3476,51],[3487,52],[3489,53],[3458,54],[3456,55],[3457,56],[3461,57],[3488,35],[3435,2],[651,58],[655,59],[654,60],[650,61],[653,62],[646,63],[652,58],[707,64],[719,65],[718,66],[708,67],[716,68],[752,69],[751,70],[731,71],[743,72],[722,73],[729,71],[723,74],[755,75],[754,76],[757,77],[756,78],[753,72],[758,72],[759,79],[764,80],[765,81],[763,82],[762,83],[761,84],[760,80],[769,85],[768,86],[767,87],[648,88],[649,89],[766,90],[740,91],[737,92],[779,72],[778,72],[777,72],[733,92],[745,74],[746,72],[742,72],[741,72],[732,72],[782,93],[781,94],[773,71],[730,71],[776,92],[775,72],[771,95],[734,72],[739,96],[736,97],[738,91],[721,98],[770,73],[749,99],[750,2],[744,72],[735,72],[774,71],[772,74],[813,100],[812,101],[810,102],[788,103],[811,72],[814,104],[816,105],[815,106],[709,92],[710,72],[711,72],[818,107],[817,108],[712,109],[713,97],[706,110],[705,111],[704,112],[714,72],[715,113],[717,92],[820,114],[822,115],[821,116],[823,92],[824,72],[825,72],[826,72],[828,72],[827,72],[841,117],[840,118],[832,119],[833,97],[834,104],[830,120],[831,121],[835,122],[836,72],[837,113],[838,92],[839,104],[845,80],[844,95],[843,123],[849,124],[848,125],[847,95],[842,95],[727,126],[846,127],[853,128],[852,129],[851,72],[850,72],[695,130],[674,131],[677,132],[673,133],[693,134],[672,135],[688,136],[696,137],[678,135],[679,138],[697,135],[691,139],[680,135],[684,140],[685,135],[686,141],[683,142],[689,143],[698,144],[690,145],[699,146],[692,147],[694,148],[687,135],[682,149],[725,150],[726,151],[1029,152],[855,153],[854,154],[643,155],[819,74],[748,2],[724,156],[675,2],[972,72],[641,2],[642,157],[720,74],[681,2],[645,158],[676,159],[647,74],[783,91],[784,92],[792,92],[791,160],[794,72],[793,72],[809,161],[808,162],[795,72],[796,72],[797,96],[798,97],[799,91],[800,160],[802,92],[801,72],[790,163],[786,164],[789,165],[785,166],[804,167],[803,168],[807,72],[805,169],[806,72],[857,170],[856,160],[787,171],[859,172],[858,72],[866,173],[865,174],[862,175],[864,175],[860,72],[861,175],[863,175],[877,91],[875,92],[870,92],[879,72],[881,176],[880,177],[869,72],[878,72],[868,72],[876,178],[872,97],[873,91],[867,63],[871,72],[874,72],[886,179],[884,179],[885,179],[891,180],[890,181],[887,179],[883,182],[889,179],[888,179],[882,2],[896,183],[895,184],[894,185],[893,186],[892,2],[905,91],[906,92],[909,72],[908,72],[912,187],[911,188],[904,96],[902,97],[903,91],[900,189],[899,190],[898,191],[907,72],[901,192],[910,72],[921,91],[922,92],[925,193],[924,194],[920,178],[917,195],[919,91],[915,196],[914,197],[913,198],[918,199],[923,72],[932,200],[931,201],[928,202],[930,202],[926,72],[927,202],[929,202],[938,203],[937,80],[936,204],[935,205],[934,206],[933,95],[942,207],[944,72],[946,208],[945,209],[939,72],[941,207],[943,72],[940,207],[960,91],[953,92],[964,72],[963,72],[951,72],[966,210],[965,211],[958,92],[959,72],[957,72],[948,95],[956,72],[955,96],[952,97],[954,91],[947,98],[961,72],[962,72],[949,71],[950,72],[780,212],[747,72],[970,213],[976,214],[975,215],[974,213],[968,213],[967,80],[973,216],[971,213],[969,213],[980,217],[979,218],[977,219],[978,220],[987,221],[986,222],[983,223],[985,224],[984,225],[982,226],[981,224],[998,72],[1000,91],[997,72],[994,72],[990,227],[995,72],[1002,228],[1001,229],[999,195],[988,230],[991,231],[993,232],[996,72],[989,233],[992,72],[1006,234],[1005,63],[1004,235],[1003,63],[1010,236],[1009,236],[1014,237],[1013,238],[1012,236],[1011,236],[1008,72],[1007,239],[1022,91],[1026,240],[1025,241],[1021,178],[1019,195],[1020,91],[1023,74],[1017,242],[1016,243],[1015,244],[1018,245],[1024,72],[644,246],[1028,247],[1027,159],[916,97],[703,248],[671,249],[702,250],[700,2],[701,251],[728,252],[829,74],[659,74],[657,253],[658,254],[664,255],[662,256],[660,2],[663,257],[661,258],[665,74],[897,2],[2669,259],[667,260],[669,261],[670,262],[666,2],[668,2],[1702,74],[1703,74],[1704,74],[1705,74],[1706,74],[1707,74],[1708,74],[1709,74],[1710,74],[1711,74],[1712,74],[1713,74],[1714,74],[1715,74],[1716,74],[1722,74],[1717,74],[1718,74],[1719,74],[1720,74],[1721,74],[1723,74],[1724,74],[1725,74],[1726,74],[1727,74],[1728,74],[1730,74],[1731,74],[1729,74],[1732,74],[1733,74],[1734,74],[1735,74],[1736,74],[1737,74],[1738,74],[1739,74],[1740,74],[1741,74],[1742,74],[1743,74],[1744,74],[1745,74],[1746,74],[1747,74],[1748,74],[1749,74],[1750,74],[1751,74],[1752,74],[1753,74],[1754,74],[1755,74],[1756,74],[1758,74],[1757,74],[1759,74],[1760,74],[1762,74],[1761,74],[1763,74],[1764,74],[1765,74],[1766,74],[1767,74],[1769,74],[1768,74],[1770,74],[1771,74],[1772,74],[1773,74],[1774,74],[1775,74],[1776,74],[1777,74],[1778,74],[1779,74],[1780,74],[1781,74],[1782,74],[1783,74],[1788,74],[1784,74],[1785,74],[1786,74],[1787,74],[1789,74],[1790,74],[1791,74],[1792,74],[1793,74],[1794,74],[1795,74],[1796,74],[1797,74],[1798,74],[1800,74],[1799,74],[1801,74],[1802,74],[1803,74],[1804,74],[1805,74],[1806,74],[1807,74],[1808,74],[1811,74],[1809,74],[1810,74],[1812,74],[1813,74],[1814,74],[1815,74],[1816,74],[1817,74],[1818,74],[1819,74],[1821,74],[1820,74],[1932,263],[1822,74],[1823,74],[1824,74],[1825,74],[1826,74],[1827,74],[1828,74],[1829,74],[1830,74],[1831,74],[1832,74],[1834,74],[1833,74],[1835,74],[1836,74],[1837,74],[1838,74],[1839,74],[1840,74],[1841,74],[1842,74],[1844,74],[1843,74],[1845,74],[1846,74],[1847,74],[1848,74],[1849,74],[1850,74],[1851,74],[1852,74],[1853,74],[1857,74],[1854,74],[1855,74],[1856,74],[1858,74],[1859,74],[1860,74],[1862,74],[1861,74],[1863,74],[1864,74],[1865,74],[1866,74],[1867,74],[1868,74],[1869,74],[1870,74],[1871,74],[1872,74],[1873,74],[1874,74],[1875,74],[1876,74],[1877,74],[1878,74],[1879,74],[1880,74],[1881,74],[1882,74],[1883,74],[1884,74],[1885,74],[1886,74],[1887,74],[1888,74],[1889,74],[1890,74],[1891,74],[1892,74],[1893,74],[1894,74],[1895,74],[1896,74],[1897,74],[1898,74],[1899,74],[1900,74],[1901,74],[1902,74],[1903,74],[1904,74],[1905,74],[1906,74],[1907,74],[1908,74],[1909,74],[1910,74],[1911,74],[1912,74],[1913,74],[1914,74],[1915,74],[1917,74],[1916,74],[1918,74],[1919,74],[1920,74],[1921,74],[1922,74],[1923,74],[1924,74],[1925,74],[1926,74],[1927,74],[1928,74],[1929,74],[1930,74],[1931,74],[2083,264],[2082,265],[405,2],[374,2],[2145,266],[2144,267],[1660,2],[1447,268],[1446,2],[625,2],[626,269],[1452,270],[1449,271],[1450,272],[1451,272],[1448,273],[627,274],[628,275],[1443,276],[1432,74],[1445,277],[1442,276],[1439,278],[1440,278],[1441,2],[1444,2],[1174,279],[1433,2],[1435,280],[1438,281],[1437,2],[1436,280],[1434,282],[1153,283],[1163,284],[1160,284],[1161,285],[1145,285],[1159,285],[1140,284],[1146,286],[1149,287],[1154,288],[1142,286],[1143,285],[1156,289],[1141,286],[1147,286],[1150,286],[1155,286],[1157,285],[1144,285],[1158,285],[1152,290],[1148,291],[1173,292],[1151,293],[1162,294],[1139,285],[1164,285],[1165,285],[1166,285],[1167,285],[1168,285],[1169,285],[1170,285],[1171,285],[1172,285],[1395,2],[1392,2],[1391,2],[1386,295],[1397,296],[1382,297],[1393,298],[1385,299],[1384,300],[1394,2],[1389,301],[1396,2],[1390,302],[1383,2],[2681,303],[2680,304],[2679,297],[1399,305],[1639,306],[1640,306],[1642,307],[1641,306],[1634,306],[1635,306],[1637,308],[1636,306],[1614,2],[1613,2],[1616,309],[1615,2],[1612,2],[1579,310],[1577,311],[1580,2],[1627,312],[1581,306],[1617,313],[1626,314],[1618,2],[1621,315],[1619,2],[1622,2],[1624,2],[1620,315],[1623,2],[1625,2],[1578,316],[1653,317],[1638,306],[1633,318],[1643,319],[1649,320],[1650,321],[1652,322],[1651,323],[1631,318],[1632,324],[1628,325],[1630,326],[1629,327],[1644,306],[1648,328],[1645,306],[1646,329],[1647,306],[1582,2],[1583,2],[1586,2],[1584,2],[1585,2],[1588,2],[1589,330],[1590,2],[1591,2],[1587,2],[1592,2],[1593,2],[1594,2],[1595,2],[1596,331],[1597,2],[1611,332],[1598,2],[1599,2],[1600,2],[1601,2],[1602,2],[1603,2],[1604,2],[1607,2],[1605,2],[1606,2],[1608,306],[1609,306],[1610,333],[1381,2],[602,334],[4157,2],[4158,2],[4159,2],[4160,335],[2154,2],[2132,336],[2155,337],[2131,2],[4161,2],[4163,338],[600,2],[4164,339],[546,2],[2724,340],[2668,2],[4165,2],[2734,340],[4162,2],[3427,2],[3428,341],[140,342],[141,342],[142,343],[97,344],[143,345],[144,346],[145,347],[92,2],[95,348],[93,2],[94,2],[146,349],[147,350],[148,351],[149,352],[150,353],[151,354],[152,354],[153,355],[154,356],[155,357],[156,358],[98,2],[96,2],[157,359],[158,360],[159,361],[191,362],[160,363],[161,364],[162,365],[163,366],[164,367],[165,368],[166,369],[167,370],[168,371],[169,372],[170,372],[171,373],[172,2],[173,374],[175,375],[174,376],[176,17],[177,377],[178,378],[179,379],[180,380],[181,381],[182,382],[183,383],[184,384],[185,385],[186,386],[187,387],[188,388],[99,2],[100,2],[101,2],[139,389],[189,390],[190,391],[1990,392],[1957,74],[195,393],[460,74],[196,394],[194,395],[462,396],[461,397],[1398,74],[1369,398],[192,399],[458,2],[193,400],[83,2],[85,401],[457,74],[226,74],[2723,2],[4166,2],[542,402],[589,403],[587,2],[588,2],[534,2],[584,404],[581,405],[582,406],[603,407],[594,2],[597,408],[596,409],[608,409],[595,410],[533,2],[541,411],[583,411],[536,412],[539,413],[590,412],[540,414],[535,2],[601,2],[1033,415],[1032,416],[1030,2],[84,2],[2468,417],[2447,418],[2544,2],[2448,419],[2384,417],[2385,417],[2386,417],[2387,417],[2388,417],[2389,417],[2390,417],[2391,417],[2392,417],[2393,417],[2394,417],[2395,417],[2396,417],[2397,417],[2398,417],[2399,417],[2400,417],[2401,417],[2380,2],[2402,417],[2403,417],[2404,2],[2405,417],[2406,417],[2408,417],[2407,417],[2409,417],[2410,417],[2411,417],[2412,417],[2413,417],[2414,417],[2415,417],[2416,417],[2417,417],[2418,417],[2419,417],[2420,417],[2421,417],[2422,417],[2423,417],[2424,417],[2425,417],[2426,417],[2427,417],[2429,417],[2430,417],[2431,417],[2428,417],[2432,417],[2433,417],[2434,417],[2435,417],[2436,417],[2437,417],[2438,417],[2439,417],[2440,417],[2441,417],[2442,417],[2443,417],[2444,417],[2445,417],[2446,417],[2449,420],[2450,417],[2451,417],[2452,421],[2453,422],[2454,417],[2455,417],[2456,417],[2457,417],[2460,417],[2458,417],[2459,417],[2382,2],[2461,417],[2462,417],[2463,417],[2464,417],[2465,417],[2466,417],[2467,417],[2469,423],[2470,417],[2471,417],[2472,417],[2474,417],[2473,417],[2475,417],[2476,417],[2477,417],[2478,417],[2479,417],[2480,417],[2481,417],[2482,417],[2483,417],[2484,417],[2486,417],[2485,417],[2487,417],[2488,2],[2489,2],[2490,2],[2637,424],[2491,417],[2492,417],[2493,417],[2494,417],[2495,417],[2496,417],[2497,2],[2498,417],[2499,2],[2500,417],[2501,417],[2502,417],[2503,417],[2504,417],[2505,417],[2506,417],[2507,417],[2508,417],[2509,417],[2510,417],[2511,417],[2512,417],[2513,417],[2514,417],[2515,417],[2516,417],[2517,417],[2518,417],[2519,417],[2520,417],[2521,417],[2522,417],[2523,417],[2524,417],[2525,417],[2526,417],[2527,417],[2528,417],[2529,417],[2530,417],[2531,417],[2532,2],[2533,417],[2534,417],[2535,417],[2536,417],[2537,417],[2538,417],[2539,417],[2540,417],[2541,417],[2542,417],[2543,417],[2545,425],[2381,417],[2546,417],[2547,417],[2548,2],[2549,2],[2550,2],[2551,417],[2552,2],[2553,2],[2554,2],[2555,2],[2556,2],[2557,417],[2558,417],[2559,417],[2560,417],[2561,417],[2562,417],[2563,417],[2564,417],[2569,426],[2567,427],[2568,428],[2566,429],[2565,417],[2570,417],[2571,417],[2572,417],[2573,417],[2574,417],[2575,417],[2576,417],[2577,417],[2578,417],[2579,417],[2580,2],[2581,2],[2582,417],[2583,417],[2584,2],[2585,2],[2586,2],[2587,417],[2588,417],[2589,417],[2590,417],[2591,423],[2592,417],[2593,417],[2594,417],[2595,417],[2596,417],[2597,417],[2598,417],[2599,417],[2600,417],[2601,417],[2602,417],[2603,417],[2604,417],[2605,417],[2606,417],[2607,417],[2608,417],[2609,417],[2610,417],[2611,417],[2612,417],[2613,417],[2614,417],[2615,417],[2616,417],[2617,417],[2618,417],[2619,417],[2620,417],[2621,417],[2622,417],[2623,417],[2624,417],[2625,417],[2626,417],[2627,417],[2628,417],[2629,417],[2630,417],[2631,417],[2632,417],[2383,430],[2633,2],[2634,2],[2635,2],[2636,2],[2059,431],[2058,432],[2057,2],[2653,433],[2267,2],[551,2],[2671,434],[2670,435],[621,436],[623,437],[622,438],[620,439],[619,2],[3426,440],[2142,2],[635,2],[574,2],[576,441],[575,2],[1039,74],[2803,2],[2777,442],[2776,443],[2775,444],[2802,445],[2801,446],[2805,447],[2804,448],[2807,449],[2806,450],[2762,451],[2736,452],[2737,453],[2738,453],[2739,453],[2740,453],[2741,453],[2742,453],[2743,453],[2744,453],[2745,453],[2746,453],[2760,454],[2747,453],[2748,453],[2749,453],[2750,453],[2751,453],[2752,453],[2753,453],[2754,453],[2756,453],[2757,453],[2755,453],[2758,453],[2759,453],[2761,453],[2735,455],[2800,456],[2780,457],[2781,457],[2782,457],[2783,457],[2784,457],[2785,457],[2786,458],[2788,457],[2787,457],[2799,459],[2789,457],[2791,457],[2790,457],[2793,457],[2792,457],[2794,457],[2795,457],[2796,457],[2797,457],[2798,457],[2779,457],[2778,460],[2770,461],[2768,462],[2769,462],[2773,463],[2771,462],[2772,462],[2774,462],[2767,2],[2303,2],[1370,74],[483,464],[488,1],[495,465],[478,466],[230,2],[238,467],[378,468],[381,469],[353,2],[366,470],[373,471],[255,2],[355,2],[236,2],[352,472],[398,473],[237,2],[228,474],[380,475],[382,476],[383,477],[455,478],[347,479],[300,480],[360,481],[361,482],[359,483],[358,2],[354,484],[379,485],[239,486],[425,2],[426,487],[266,488],[240,489],[267,488],[303,488],[206,488],[376,490],[375,2],[365,491],[473,2],[215,2],[494,492],[433,493],[434,494],[430,495],[512,2],[330,2],[435,104],[431,496],[517,497],[516,498],[511,2],[281,2],[333,499],[332,2],[510,500],[432,74],[286,501],[293,502],[295,503],[285,2],[290,504],[292,505],[294,506],[289,507],[287,2],[291,508],[513,2],[509,2],[515,509],[514,2],[284,510],[504,511],[507,512],[274,513],[273,514],[272,515],[520,74],[271,516],[260,2],[522,2],[2697,517],[2696,2],[523,74],[524,518],[198,2],[362,519],[363,520],[364,521],[202,2],[367,2],[222,522],[197,2],[447,74],[204,523],[446,524],[445,525],[436,2],[437,2],[444,2],[439,2],[442,526],[438,2],[440,527],[443,528],[441,527],[235,2],[232,2],[233,488],[387,2],[392,529],[393,530],[391,531],[389,532],[390,533],[385,2],[453,104],[227,104],[482,534],[489,535],[493,536],[321,537],[320,2],[315,2],[469,538],[477,539],[348,540],[349,541],[428,542],[337,2],[451,543],[325,74],[342,544],[454,545],[338,2],[341,546],[339,2],[452,547],[449,548],[448,2],[450,2],[345,2],[424,549],[210,550],[323,551],[327,552],[343,553],[346,554],[335,555],[328,556],[476,557],[401,558],[319,559],[207,560],[475,561],[203,562],[394,563],[386,2],[395,564],[413,565],[384,2],[412,566],[91,2],[407,567],[231,2],[427,568],[402,2],[216,2],[218,2],[357,2],[411,569],[234,2],[258,570],[344,571],[264,572],[324,2],[410,2],[388,2],[415,573],[416,574],[356,2],[418,575],[420,576],[419,577],[368,2],[409,560],[422,578],[318,579],[408,580],[414,581],[243,2],[247,2],[246,2],[245,2],[250,2],[244,2],[253,2],[252,2],[249,2],[248,2],[251,2],[254,582],[242,2],[310,583],[309,2],[314,584],[311,585],[313,586],[316,584],[312,585],[223,587],[302,588],[472,589],[470,2],[499,590],[501,591],[465,592],[500,593],[211,594],[208,594],[241,2],[225,595],[224,596],[220,597],[221,598],[229,599],[257,599],[268,599],[304,600],[269,600],[213,601],[212,2],[308,602],[307,603],[306,604],[305,605],[214,606],[456,607],[256,608],[464,609],[429,610],[459,611],[463,612],[351,613],[350,614],[331,615],[317,616],[299,617],[301,618],[298,619],[421,620],[322,2],[487,2],[219,621],[423,622],[471,623],[329,2],[259,624],[336,625],[334,626],[261,627],[396,628],[466,2],[262,629],[397,629],[485,2],[484,2],[486,2],[468,2],[467,2],[399,630],[326,2],[296,631],[217,632],[275,2],[201,633],[263,2],[491,74],[200,2],[503,634],[283,74],[497,104],[282,635],[480,636],[280,634],[205,2],[505,637],[278,74],[279,74],[270,2],[199,2],[277,638],[276,639],[265,640],[340,371],[400,371],[417,2],[404,641],[403,2],[288,510],[209,2],[297,74],[474,522],[481,642],[86,74],[89,643],[90,644],[87,74],[88,2],[377,645],[372,646],[371,2],[370,647],[369,2],[479,648],[490,649],[492,650],[496,651],[2698,652],[498,653],[502,654],[530,655],[506,655],[529,656],[508,657],[518,658],[519,659],[521,660],[525,661],[528,522],[527,2],[526,662],[2699,663],[1659,663],[1658,664],[1657,74],[1661,665],[2962,2],[2968,666],[2961,2],[2965,2],[2967,667],[2964,668],[3037,669],[3031,669],[2992,670],[2988,671],[3003,672],[2993,673],[3000,674],[2987,675],[3001,2],[2999,676],[2996,677],[2997,678],[2994,679],[3002,680],[2969,668],[3032,681],[2983,682],[2980,683],[2981,684],[2982,685],[2971,686],[2990,687],[3009,688],[3005,689],[3004,690],[3008,691],[3006,692],[3007,692],[2984,693],[2986,694],[2985,695],[2989,696],[3033,697],[2991,698],[2973,699],[3034,700],[2972,701],[3035,702],[2974,703],[3012,704],[3010,683],[3011,705],[2975,692],[3016,706],[3014,707],[3015,708],[2976,709],[3019,710],[3018,711],[3021,712],[3020,713],[3024,714],[3022,713],[3023,715],[3017,716],[3013,717],[3025,716],[2977,692],[3036,718],[2978,713],[2979,692],[2995,719],[2998,720],[2970,2],[3026,692],[3027,721],[3029,722],[3028,723],[3030,724],[2963,725],[2966,726],[1338,727],[1339,728],[1337,2],[569,729],[567,730],[568,731],[556,732],[557,730],[564,733],[555,734],[560,735],[570,2],[561,736],[566,737],[572,738],[571,739],[554,740],[562,741],[563,742],[558,743],[565,729],[559,744],[1388,745],[1387,2],[1049,2],[1065,746],[1066,746],[1067,746],[1068,746],[1082,747],[1069,748],[1070,748],[1071,749],[1062,750],[1060,751],[1051,2],[1055,752],[1059,753],[1057,754],[1064,755],[1052,756],[1053,757],[1054,758],[1056,759],[1058,760],[1061,761],[1063,762],[1072,748],[1073,748],[1074,748],[1075,746],[1076,748],[1077,748],[1050,748],[1078,2],[1080,763],[1079,748],[1081,746],[2317,764],[2318,765],[2766,766],[2765,767],[2171,768],[2264,769],[2262,770],[2169,2],[2170,771],[2263,2],[2265,772],[2173,773],[2172,774],[2176,775],[2243,776],[2238,777],[2139,778],[2209,779],[2202,780],[2259,781],[2137,782],[2208,783],[2197,784],[2196,774],[2242,785],[2239,786],[2190,787],[2201,788],[2244,789],[2245,789],[2246,790],[2254,791],[2248,791],[2256,791],[2260,791],[2247,791],[2249,792],[2252,792],[2255,792],[2251,793],[2253,791],[2257,794],[2250,795],[2148,796],[2223,74],[2220,797],[2224,74],[2159,791],[2149,791],[2215,798],[2138,799],[2158,800],[2162,801],[2222,791],[2135,74],[2221,802],[2219,74],[2218,791],[2150,74],[2269,803],[2233,795],[2213,804],[2274,805],[2231,2],[2229,2],[2234,806],[2232,807],[2228,808],[2230,809],[2235,810],[2237,811],[2227,74],[2157,812],[2134,791],[2226,791],[2175,813],[2225,74],[2198,812],[2258,791],[2192,814],[2146,815],[2151,816],[2203,817],[2205,814],[2184,818],[2187,814],[2163,819],[2186,820],[2194,821],[2195,822],[2191,823],[2206,824],[2193,825],[2168,826],[2214,827],[2210,828],[2211,829],[2207,830],[2185,831],[2174,832],[2178,833],[2152,834],[2182,835],[2183,836],[2179,837],[2153,838],[2164,839],[2204,822],[2147,840],[2212,2],[2177,841],[2167,842],[2199,2],[2271,843],[2272,844],[2273,771],[2240,2],[2270,771],[2261,2],[2188,2],[2160,2],[2236,845],[2189,2],[2140,771],[2268,846],[2166,847],[2200,848],[2165,849],[2241,850],[2180,2],[2216,2],[2217,851],[2161,2],[2181,2],[2266,2],[2136,74],[2143,852],[2141,2],[2809,853],[2808,854],[2764,855],[2763,856],[656,2],[548,857],[547,339],[406,858],[629,74],[553,2],[1031,2],[604,2],[537,2],[538,859],[2731,860],[2730,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],[117,861],[127,862],[116,861],[137,863],[108,864],[107,865],[136,662],[130,866],[135,867],[110,868],[124,869],[109,870],[133,871],[105,872],[104,662],[134,873],[106,874],[111,875],[112,2],[115,875],[102,2],[138,876],[128,877],[119,878],[120,879],[122,880],[118,881],[121,882],[131,662],[113,883],[114,884],[123,885],[103,886],[126,877],[125,875],[129,2],[132,887],[2733,888],[2729,2],[2732,889],[3421,890],[3405,2],[3406,2],[3408,891],[3409,2],[3407,2],[3410,891],[3411,891],[3413,892],[3412,891],[3414,891],[3415,892],[3416,891],[3417,2],[3418,891],[3419,2],[3420,2],[2726,893],[2725,340],[2728,894],[2727,895],[2133,896],[2156,897],[606,898],[592,899],[593,898],[591,2],[544,900],[580,901],[550,902],[545,900],[543,2],[549,903],[578,2],[573,2],[577,904],[552,2],[579,905],[612,906],[605,907],[598,908],[607,909],[586,910],[616,911],[617,912],[609,913],[618,914],[610,915],[599,916],[615,917],[611,918],[2674,919],[624,920],[585,2],[2073,921],[2080,922],[2075,2],[2076,2],[2074,923],[2077,924],[2069,2],[2070,2],[2081,925],[2072,926],[2078,2],[2079,927],[2071,928],[1305,929],[1308,930],[1306,930],[1302,929],[1309,931],[1310,932],[1307,930],[1303,933],[1304,934],[1298,935],[1250,936],[1252,937],[1296,2],[1251,938],[1297,939],[1301,940],[1299,2],[1253,936],[1254,2],[1295,941],[1249,942],[1246,2],[1300,943],[1247,944],[1248,2],[1311,945],[1255,946],[1256,946],[1257,946],[1258,946],[1259,946],[1260,946],[1261,946],[1262,946],[1263,946],[1264,946],[1265,946],[1267,946],[1266,946],[1268,946],[1269,946],[1270,946],[1294,947],[1271,946],[1272,946],[1273,946],[1274,946],[1275,946],[1276,946],[1277,946],[1278,946],[1279,946],[1281,946],[1280,946],[1282,946],[1283,946],[1284,946],[1285,946],[1286,946],[1287,946],[1288,946],[1289,946],[1290,946],[1291,946],[1292,946],[1293,946],[2683,948],[2685,267],[2687,267],[2689,267],[2691,267],[2693,267],[2676,267],[2867,949],[2858,950],[1314,951],[1313,952],[1312,953],[2864,954],[2857,955],[2855,956],[2866,957],[2856,958],[2865,959],[2861,960],[2860,961],[2859,962],[1245,267],[2862,963],[2895,964],[2893,965],[2894,966],[2812,967],[2911,968],[2912,969],[2901,970],[2913,971],[2899,972],[1315,267],[2914,973],[2903,974],[1317,975],[1316,976],[2898,977],[2915,978],[2916,979],[2904,980],[1320,981],[1319,982],[2917,983],[2902,984],[2896,985],[2909,986],[2907,987],[2910,988],[2906,989],[2905,990],[2897,991],[2900,992],[2908,993],[2918,994],[2851,995],[2919,996],[2924,997],[2921,998],[2920,999],[2923,1000],[2932,1001],[2925,1002],[2933,1003],[2929,1004],[1322,1005],[1321,267],[2931,1006],[2927,1007],[2926,1008],[1323,267],[2934,1009],[2928,1010],[2930,1011],[2949,1012],[2946,1013],[2950,1014],[2936,1015],[2939,1016],[2938,1017],[1324,267],[1326,1018],[1325,1019],[2952,1020],[2953,1020],[2940,1021],[2951,1022],[2937,1023],[1327,267],[2942,1024],[2941,1025],[2954,1026],[2943,1027],[1329,1028],[1328,1029],[2955,1030],[2956,1031],[2944,1032],[1087,267],[2948,1033],[2945,1034],[2935,104],[2947,1035],[2719,1036],[1331,1037],[1330,1038],[3051,1039],[2831,1040],[3052,1041],[3047,1042],[1334,1043],[1333,1044],[3053,1045],[3054,1045],[3049,1046],[1336,1047],[1335,267],[3055,1048],[3048,1049],[3056,1050],[2959,1051],[3057,1052],[2829,1053],[2828,1054],[3058,1055],[2830,1056],[3059,1057],[2958,1058],[1344,1059],[1343,1060],[3060,1061],[3061,1062],[1342,1063],[1346,1064],[1345,1065],[3050,1066],[3063,1067],[1358,1068],[3064,1069],[3065,1070],[1356,1071],[3066,1072],[3067,1072],[1378,1073],[3068,1074],[1373,1075],[1379,1076],[3071,1077],[1367,1078],[3072,1079],[1365,1080],[3073,1081],[1364,1082],[1403,1083],[1363,1084],[1359,1085],[1404,1086],[1366,1087],[3069,1088],[1355,1089],[1380,1090],[1374,1091],[3070,1092],[1357,1089],[1349,267],[1400,1093],[1377,1094],[1401,1095],[1375,1096],[1402,1097],[1376,1096],[3062,1098],[3149,1099],[3136,1100],[3151,1101],[3150,1102],[3152,1103],[3142,1104],[3154,1105],[3145,1106],[3155,1107],[3144,1108],[3153,1109],[3140,1110],[3156,1111],[3143,1112],[3148,1113],[3147,1114],[3112,1115],[3113,1116],[3090,1117],[1409,267],[3093,1118],[3124,1119],[3081,1120],[3079,1121],[3125,1122],[3082,1123],[3126,1124],[3094,1125],[3127,1126],[3095,1127],[3128,1128],[3129,1129],[3075,1130],[3130,1131],[3076,1132],[3078,1133],[3131,1134],[3074,1135],[3077,1118],[3132,1136],[1697,1137],[3133,1138],[3080,1139],[3134,1140],[1410,1141],[1411,1142],[3114,1143],[3102,1144],[3115,1145],[3100,1146],[1405,267],[1408,1147],[1407,1148],[3116,1149],[3101,1150],[3117,1151],[3118,1152],[3096,1153],[3119,1154],[1406,1155],[3084,1156],[3120,1157],[3085,1158],[3121,1159],[3092,1160],[3083,1161],[3109,1162],[3104,1163],[3091,1164],[3106,1165],[3098,1166],[3107,1167],[3099,1168],[3108,1169],[3097,1170],[3086,1171],[3122,1172],[3087,1173],[3123,1174],[3088,1175],[3110,1176],[3111,1177],[3103,1178],[3135,1179],[3089,1180],[3105,1181],[1422,1182],[1423,1183],[1421,1184],[1424,1185],[1425,1185],[1427,1186],[1426,1187],[1428,1188],[1114,1189],[1429,1190],[1431,1191],[1430,1192],[1455,1193],[1457,1194],[1456,1195],[1459,1196],[1458,1190],[1461,1197],[1460,1190],[1463,1198],[1462,1190],[1466,1199],[1465,1200],[1467,1201],[1107,267],[3158,1202],[1454,1203],[1468,1204],[1469,976],[1470,1205],[1471,1206],[1473,1207],[1472,1208],[1474,1206],[1475,1207],[1476,1209],[1478,1210],[1477,1211],[1480,1212],[1479,1213],[1481,1214],[1129,1211],[1483,1215],[1482,1216],[1484,1211],[1220,1189],[1486,1217],[1485,1211],[1488,1218],[1489,1219],[1487,1220],[1490,1221],[1492,1222],[1491,1221],[1239,1189],[1493,1223],[1494,1190],[1495,1211],[1496,1189],[1498,1224],[1497,1211],[1500,1225],[1499,1226],[1502,1227],[1501,1228],[1503,1228],[1504,1229],[1506,1230],[1505,1231],[1507,1230],[1509,1232],[1508,1189],[1510,1233],[1189,1211],[1512,1234],[1511,1235],[1513,1236],[1218,1211],[1516,1237],[1515,1238],[1518,1239],[1517,1238],[1520,1240],[1519,1241],[1521,1242],[1514,1243],[1523,1244],[1522,1238],[1525,1245],[1524,1189],[1527,1246],[1526,1211],[1222,1247],[1529,1248],[1528,1211],[1530,1190],[1532,1249],[1534,1250],[1533,1195],[1536,1251],[1535,1252],[1538,1253],[1537,1223],[1540,1254],[1539,1211],[1542,1255],[1541,1223],[1543,1256],[1545,1257],[1544,1258],[1547,1259],[1546,1260],[1548,1261],[1217,1262],[1549,1263],[1108,1189],[1552,1264],[1551,1265],[1553,1266],[1550,1189],[1555,1267],[1554,1189],[1412,1268],[1413,1269],[1109,1270],[1414,1271],[1231,1272],[1232,1272],[1415,1273],[1229,1272],[1416,1274],[1233,1272],[1417,1272],[1418,1275],[1219,1276],[1223,1277],[1557,1278],[1556,1189],[1559,1279],[1558,1211],[1561,1280],[1560,1243],[3157,1281],[1420,1282],[2815,1283],[2813,1284],[2811,1285],[1244,1286],[1243,1287],[3172,1288],[3192,1289],[3197,1290],[3240,1291],[3241,1292],[3218,1293],[1563,1294],[1562,1295],[1566,1296],[1565,1297],[3203,1298],[1568,1299],[1569,1300],[1567,1301],[3242,1302],[3215,1303],[3206,1304],[1571,1305],[1570,267],[3219,1306],[3238,1307],[3258,1308],[3220,1309],[3259,1310],[3208,1311],[3260,1312],[3229,1313],[3261,1314],[3207,1315],[3262,1316],[3223,1317],[3263,1318],[3264,1319],[3222,1320],[3265,1321],[3224,1322],[3266,1323],[3232,1324],[3267,1325],[3209,1326],[3268,1327],[3237,1328],[1573,1329],[1572,1330],[3257,1331],[1574,1332],[3245,1333],[3243,1334],[3214,1335],[3244,1336],[3228,1337],[3246,1338],[3211,1339],[3247,1340],[3221,1341],[3248,1342],[3193,1343],[3194,1344],[3250,1345],[3196,1346],[3249,1347],[3195,1348],[1576,1349],[1575,1350],[3251,1351],[3201,1352],[3198,1353],[3213,1354],[3252,1355],[3212,1356],[3253,1357],[3204,1358],[3210,1359],[1654,1360],[3199,1361],[3205,1362],[3233,1363],[1656,1364],[1655,1365],[3254,1366],[3234,1367],[3255,1368],[3202,1369],[3200,1370],[3256,1371],[3231,1372],[3269,1373],[1564,1343],[3239,1374],[3277,1375],[3270,1376],[3278,1377],[3271,1378],[3279,1379],[3273,1380],[3272,1381],[3280,1382],[3274,1383],[3276,1384],[3275,1385],[3298,1386],[3368,1387],[3367,1388],[1666,1389],[1665,1390],[3375,1391],[3325,1392],[3376,1393],[3324,1394],[1670,1395],[1669,1396],[3379,1397],[3332,1398],[3331,1399],[3330,1400],[1672,1401],[1671,267],[3377,1402],[3363,1403],[3323,1404],[3378,1405],[3371,1406],[1663,1407],[1662,1408],[3374,1409],[3373,1410],[3380,1411],[3369,1412],[3381,1413],[3342,1414],[3326,1415],[3382,1416],[3333,1417],[3383,1418],[3362,1419],[3347,1420],[3366,1421],[3364,1422],[3358,1423],[3372,1424],[1664,1425],[1674,1426],[1673,267],[1216,976],[3388,1427],[3386,1428],[3387,1429],[3401,1430],[3399,1431],[3402,1432],[3398,1433],[3397,1434],[3393,1435],[3392,1436],[3400,1437],[2853,1438],[2852,1439],[3508,1440],[3530,1441],[3500,1442],[3531,1443],[3522,1444],[3532,1445],[3509,1446],[3533,1447],[3501,1448],[1676,1449],[3510,1450],[3502,1451],[3534,1452],[3503,1453],[3535,1454],[3517,1455],[3536,1456],[3521,1457],[3537,1458],[3511,1459],[3504,1460],[3538,1461],[3505,1462],[3539,1463],[3506,1464],[3540,1465],[3507,1466],[3541,1467],[3520,1468],[3515,1469],[3518,1451],[3514,1453],[3516,1470],[3519,1471],[1678,1472],[1677,267],[3542,1473],[3527,1474],[3543,1475],[3525,1476],[3544,1477],[3523,1478],[3545,1479],[3526,1480],[3547,1481],[3546,1482],[3548,1483],[3524,1484],[1681,1485],[1680,1486],[3404,1487],[1686,1488],[1685,1489],[1688,1490],[3424,1491],[3549,1492],[3492,1493],[3550,1494],[3493,1495],[3551,1496],[3494,1497],[3552,1498],[3495,1499],[1679,976],[3496,1497],[3497,1497],[3499,1499],[3529,1500],[3528,1501],[3573,1502],[3563,1503],[3574,1504],[3557,1505],[3575,1506],[3568,1507],[3571,1508],[3560,1509],[3559,1510],[1691,1511],[1690,1512],[3576,1513],[3566,1514],[3577,1515],[3558,1516],[3578,1517],[3561,1518],[3579,1519],[3569,1520],[3580,1521],[3555,1522],[3581,1523],[3556,1524],[3582,1525],[3565,1526],[3583,1527],[3564,1528],[3572,1529],[3554,1530],[3553,1531],[1693,1532],[1692,267],[3584,1533],[3567,1534],[3562,1137],[3570,1535],[3595,1536],[3590,1537],[3596,1538],[3589,1539],[3597,1540],[3588,1541],[3587,1542],[3600,1543],[3601,1544],[3585,1545],[3602,1546],[3603,1547],[3586,1548],[3604,1549],[1973,1550],[1694,953],[1975,1551],[1974,1552],[3598,1553],[3593,1554],[3599,1555],[3592,1556],[3591,1557],[3594,1558],[3633,1559],[3610,1560],[3634,1561],[3630,1562],[3629,1563],[3646,1564],[3619,1565],[3651,1566],[3624,1567],[3647,1568],[3620,1569],[3648,1570],[3623,1480],[3649,1571],[3621,1572],[1979,1573],[1980,1574],[3650,1575],[3618,1139],[3622,104],[3638,1576],[3616,1577],[3626,1578],[3628,1579],[3639,1580],[3613,1581],[3640,1582],[3608,1583],[3641,1584],[3612,1585],[3642,1586],[3617,1587],[3643,1588],[3625,1589],[3644,1590],[3614,1591],[1976,267],[1978,1592],[1977,1593],[3645,1594],[3627,1595],[3635,1596],[3609,1597],[3605,1598],[3632,1599],[3607,1600],[3606,1601],[3636,1602],[3611,1603],[3637,1604],[3615,1605],[3631,1606],[3653,1607],[3046,1608],[3652,1609],[3663,1610],[3664,1611],[3655,1612],[3661,1613],[3665,1614],[3654,1615],[1983,1616],[1982,1617],[3669,1618],[3670,1618],[3660,1619],[3666,1620],[3657,1621],[3656,1622],[3667,1623],[3658,1624],[3668,1625],[3659,1626],[1981,1038],[3662,1627],[3678,1628],[3671,1629],[3676,1630],[3674,1631],[3677,1632],[3673,1633],[3672,1634],[3675,1635],[3688,1636],[3682,1637],[3686,1638],[3683,1639],[3687,1640],[3679,1641],[3685,1642],[3681,1643],[3680,1644],[3684,1645],[3696,1646],[3703,1647],[3706,1648],[3705,1649],[3704,1650],[3709,1651],[3708,1652],[3707,1653],[3732,1654],[3716,1655],[3733,1656],[3717,1655],[3734,1657],[3718,1658],[3731,1659],[3719,1660],[3735,1661],[3723,1662],[1987,1663],[1989,1664],[1988,1665],[3736,1666],[3724,1667],[1992,1668],[1991,1669],[3722,1670],[3737,1671],[3721,1672],[1985,1673],[1984,267],[3720,267],[3729,1674],[3725,1675],[3730,1676],[3727,1677],[3738,1678],[3726,1679],[1993,1680],[1341,1681],[3728,1682],[3749,1683],[3740,1684],[3752,1685],[3742,1686],[1996,1687],[1995,1688],[1997,1689],[1994,953],[3747,1690],[3750,1691],[3739,1692],[3751,1693],[3746,1694],[3754,1695],[3755,1696],[3745,1697],[3753,1698],[3744,1699],[3743,1700],[3748,1701],[3769,1702],[3770,1703],[3765,1704],[3771,1705],[3763,1706],[3762,1707],[3779,1708],[3767,1709],[1210,1710],[3772,1711],[1209,1712],[1208,1713],[3773,1714],[3764,1715],[3774,1716],[3766,1717],[3780,1718],[3781,1719],[3761,1720],[3775,1721],[3776,1722],[3759,1723],[3777,1724],[3758,1725],[3757,1726],[3778,1727],[3760,1728],[3768,1729],[3785,1730],[3784,1731],[3783,1732],[3782,1733],[3793,1734],[3795,1735],[3799,1736],[3787,1737],[3786,1738],[3801,1739],[3791,1740],[3790,1741],[3803,1742],[3805,1743],[3804,1744],[3807,1745],[3806,1746],[2703,1747],[3809,1748],[3810,1749],[3808,1750],[3811,1751],[3812,1752],[3813,1753],[3814,1754],[3816,1755],[3815,1756],[3820,1757],[3819,1758],[3821,1759],[3822,1760],[3818,1761],[3823,1762],[3817,1763],[3824,1764],[3844,1765],[3711,1766],[2085,1137],[1104,1767],[3952,1768],[3329,1769],[3340,267],[3942,1770],[3341,1771],[3954,1772],[3334,1773],[1095,1774],[3955,1775],[3300,1776],[1667,267],[2041,1777],[2040,1778],[3943,1779],[3328,1780],[2044,1781],[2043,1782],[2046,1783],[2045,267],[2047,1784],[1136,1785],[2042,1786],[1130,267],[3956,1787],[3304,1788],[1121,1789],[1119,1790],[3944,1791],[1112,1792],[2048,1793],[1111,267],[1118,1794],[1120,1795],[2049,1796],[1093,1797],[2050,1798],[1134,1799],[3945,1800],[1132,1801],[1131,1802],[3957,1803],[3335,1804],[1122,1774],[1127,1805],[3327,1806],[3958,1807],[3337,1808],[2051,1809],[1116,267],[3946,1810],[1117,1811],[1135,1812],[3959,1813],[3336,1814],[1100,1815],[3960,1816],[3338,1817],[2086,1137],[1096,1815],[3947,1818],[1113,1819],[3961,1820],[3339,1821],[1123,1815],[3948,1822],[2087,1823],[3949,1824],[1128,1825],[3950,1826],[1124,1774],[2052,1827],[1133,1828],[2053,1829],[1125,1830],[1098,1831],[3951,1832],[1126,1833],[1097,1834],[1099,1023],[3845,1835],[3353,1836],[3962,1837],[1698,1838],[1318,1038],[3867,1839],[3281,1840],[3873,1841],[3282,1842],[3874,1843],[3284,1844],[3875,1845],[3286,1846],[3868,1847],[3283,1840],[3869,1848],[3297,1849],[3870,1850],[3287,1840],[3293,1851],[3871,1852],[3291,1853],[3872,1854],[3290,1855],[3162,1856],[3963,1857],[3161,1858],[3825,1859],[1230,1860],[3846,1861],[3741,1862],[1936,1155],[3788,1863],[2062,1864],[3964,1865],[2061,1866],[3965,1867],[3797,1868],[3966,1869],[3798,1870],[2060,1871],[3792,1872],[3967,1873],[3800,1874],[3968,1875],[3796,1876],[3969,1877],[3789,1878],[3794,1879],[2054,1880],[3802,1881],[2063,1882],[2055,1883],[3970,1884],[3295,1885],[3512,1886],[1675,267],[3971,1887],[3513,1888],[3972,1889],[1683,1890],[1684,1573],[2065,1891],[2064,1892],[3294,1893],[3292,1894],[640,1038],[3847,1895],[3712,1896],[3876,1897],[3168,1898],[3877,1899],[3878,1900],[3165,1901],[3879,1902],[3163,1466],[3164,1903],[3880,1904],[3167,1905],[2004,1906],[2003,267],[3881,1907],[3882,1908],[3166,1909],[1464,267],[1372,1910],[1699,1911],[2833,1912],[1700,1023],[1085,1913],[3973,1914],[2818,1915],[3974,1916],[2834,1917],[3995,1918],[3389,1919],[3996,1920],[3390,1921],[3997,1922],[3391,1923],[2066,1332],[3998,1924],[3288,1925],[3999,1926],[3289,1927],[3975,1928],[1701,1929],[3976,1930],[2819,1931],[3977,1932],[2718,1933],[3318,1934],[3978,1935],[3311,1936],[3979,1937],[1934,1938],[3980,1939],[1933,1940],[3981,1941],[1084,1942],[3983,1943],[3982,1860],[3984,1944],[1952,1945],[3985,1946],[3352,1947],[1935,1838],[3351,1948],[3986,1949],[1939,1950],[1953,1951],[3987,1952],[1940,1953],[3988,1954],[1950,1955],[2068,1956],[2067,1957],[3989,1958],[2835,1959],[3991,1960],[1350,1961],[3992,1962],[1951,1963],[3993,1964],[2826,1965],[3994,1966],[3308,1967],[3990,1968],[2827,1969],[2868,104],[3826,1970],[1959,1971],[3827,1972],[2715,1973],[3828,1974],[2720,1975],[3883,1976],[3175,1977],[3884,1978],[3174,1979],[3173,1980],[3885,1981],[3178,1982],[3886,1983],[3177,1984],[3176,1985],[3829,1986],[2922,1987],[2089,1988],[2090,1989],[2088,1990],[4000,1991],[2091,1992],[2092,1993],[638,1994],[3848,1995],[3159,1996],[3887,1997],[2012,1998],[3888,1999],[2008,2000],[3889,2001],[2009,2002],[3890,2003],[2010,2004],[2014,2005],[2007,2006],[3891,2007],[2013,2008],[2015,2009],[2011,2010],[4001,2011],[2843,2012],[2093,267],[3830,2013],[3312,2014],[3137,2015],[3141,2016],[3892,2017],[3138,104],[2016,267],[3139,2018],[2018,2019],[2017,1665],[3831,2020],[1368,1565],[3849,2021],[2292,267],[1998,2022],[1197,267],[4002,2023],[1960,2024],[1961,2025],[4005,2026],[1089,976],[2096,2027],[2095,2028],[1215,2029],[4003,2030],[2094,2031],[1046,2032],[2098,2033],[2097,2034],[4004,2035],[1962,2036],[2100,2037],[2099,2038],[2102,2039],[2101,104],[3850,2040],[3348,2041],[3851,2042],[1242,2043],[3832,2044],[2722,2045],[4006,2046],[3403,2047],[1687,267],[4007,2048],[1090,2049],[2104,2050],[2103,1343],[4008,2051],[3498,2052],[3852,2053],[2836,2054],[2105,2055],[1966,2056],[2106,2057],[4009,2058],[1963,2059],[4010,2060],[1967,2061],[4011,2062],[3230,2063],[1088,267],[1965,2064],[4012,2065],[3422,2066],[4013,2067],[1086,267],[2108,2068],[2107,1091],[4014,2069],[3343,2070],[4015,2071],[3346,2072],[4016,2073],[3345,2074],[3344,2075],[4017,2076],[3301,2077],[4018,2078],[3361,2079],[4019,2080],[3360,2081],[3359,2082],[4020,2083],[3322,2084],[2109,267],[3285,2085],[3365,2086],[3853,2087],[3307,2088],[3305,2089],[3893,2090],[2854,2091],[2020,2092],[2019,267],[4021,2093],[3299,1923],[4022,2094],[1354,2095],[4023,2096],[3038,2097],[3160,2098],[3854,2099],[2717,2100],[3895,2101],[2706,2102],[3896,2103],[2709,2104],[3897,2105],[2707,2106],[2021,2107],[1234,267],[2022,267],[3898,2108],[2710,2109],[3899,2110],[2716,2111],[3894,2112],[2712,2113],[3900,2114],[2714,2115],[1999,2116],[1214,2117],[3833,2118],[2721,2119],[1047,1038],[2840,2120],[3855,2121],[1958,2122],[4026,2123],[4027,2124],[1972,2125],[2110,2126],[1970,2127],[4024,2128],[4025,2129],[2841,2130],[2112,2131],[2111,267],[2113,2132],[1971,267],[2116,2133],[2115,2134],[4029,2135],[3395,2136],[2118,2137],[2117,2138],[4030,2139],[3394,2140],[2114,953],[4028,2141],[3396,2142],[2000,267],[2002,2143],[2001,2144],[3856,2145],[3354,2146],[3901,2147],[3356,2148],[3355,2149],[3902,2150],[3357,2151],[3857,2152],[3714,2153],[4031,2154],[2839,2155],[2121,2156],[2120,2157],[4032,2158],[2838,2159],[2837,2160],[4033,2161],[2844,2162],[1689,267],[3858,2163],[3370,2164],[3859,2165],[1352,2166],[3860,2167],[3296,2168],[2123,2169],[2122,1192],[2126,2170],[2125,2171],[2124,2172],[3861,2173],[3349,2174],[3862,2175],[3350,2176],[4039,2177],[2960,2178],[4034,2179],[1941,1139],[4035,2180],[1942,1139],[4036,2181],[1945,2182],[4037,2183],[1943,1023],[4038,2184],[1944,2185],[4042,2186],[3045,2187],[4040,2188],[3044,2189],[2128,2190],[2127,2191],[4041,2192],[3043,2193],[3042,2194],[3041,2195],[2129,267],[1531,267],[3834,2196],[2869,2197],[4043,2198],[3313,2199],[3863,2200],[3171,2201],[2023,267],[3903,2202],[2886,2203],[2884,1466],[3904,2204],[2885,2205],[2024,267],[3905,2206],[2887,2207],[3906,2208],[2889,2209],[3907,2210],[2888,1466],[3908,2211],[2870,2212],[3909,2213],[3226,2214],[3910,2215],[3225,2216],[2026,2217],[2025,1499],[3911,2218],[3227,2219],[2028,2220],[2027,2221],[3912,2222],[2890,2223],[2029,953],[2030,1155],[3918,2224],[2873,2225],[3919,2226],[2872,2227],[3920,2228],[2874,2229],[3921,2230],[3922,2231],[2875,2232],[3913,2233],[2876,1923],[3914,2234],[2877,2235],[3915,2236],[2880,2237],[3916,2238],[2878,1466],[3917,2239],[2879,2240],[2032,2241],[2031,2242],[3923,2243],[2881,2244],[3924,2245],[2882,2246],[3925,2247],[2883,2248],[3926,2249],[3170,2250],[3169,2251],[2033,267],[3927,2252],[1949,2253],[3928,2254],[1946,2255],[3929,2256],[3039,2257],[1947,2258],[3931,2259],[3040,2260],[3930,2261],[1948,2262],[3146,104],[4059,2263],[2821,2264],[4044,2265],[1956,2266],[4045,2267],[2816,2268],[4060,2269],[3713,1763],[4068,2270],[2277,2271],[4069,2272],[2278,2271],[4070,2273],[2279,2274],[4071,2275],[2276,2276],[2130,267],[4072,2277],[2280,2271],[2282,2278],[4073,2279],[2281,2271],[4046,2280],[1235,1921],[4047,2281],[1968,2282],[1176,2283],[4061,2284],[4062,2285],[1180,2286],[4063,2287],[1182,2288],[4064,2289],[1179,2290],[4065,2291],[1184,2292],[4066,2293],[1187,2294],[4067,2295],[1186,2296],[1185,2297],[1188,2298],[1175,2299],[2006,267],[4048,2300],[1195,2301],[2842,267],[4074,2302],[1954,1942],[3309,2303],[3303,2304],[4049,2305],[1200,2306],[4050,2307],[1201,2308],[4051,2309],[1091,1137],[1937,1139],[4052,2310],[2814,2311],[4053,2312],[2825,2313],[4054,2314],[1696,2313],[4055,2315],[2957,2316],[2871,2317],[2823,2318],[4056,2319],[1041,1137],[4057,2320],[1226,2321],[2822,2322],[4075,2323],[1190,2324],[1191,2325],[4076,2326],[1192,2327],[4077,2328],[1194,2329],[4078,2330],[1196,2331],[1204,2332],[4079,2333],[1198,2334],[4080,2335],[1199,1665],[4081,2336],[1202,2337],[4082,2338],[1203,2339],[4058,2340],[2705,2341],[1695,2342],[3932,2343],[1238,2344],[3836,2345],[1241,2346],[3835,2347],[2891,2348],[4083,2349],[3423,2350],[637,267],[4084,2351],[3691,2352],[3690,2353],[3689,2354],[2846,2355],[4085,2356],[4086,2356],[3314,2357],[3310,2358],[4087,2359],[1938,2360],[4092,2361],[3316,2362],[2284,2363],[2283,267],[4088,2364],[3317,2365],[4093,2366],[3315,267],[2286,2367],[2285,267],[4089,2368],[3321,2369],[4090,2370],[3319,2371],[2287,2372],[2119,267],[4091,2373],[3320,2374],[2288,1223],[3838,2375],[3695,2376],[2035,2377],[2034,2378],[3933,2379],[3694,2380],[3693,2381],[3837,2382],[3692,2383],[2290,2384],[2289,267],[4098,2385],[2847,2386],[4099,2387],[4100,2388],[2848,2389],[4094,2390],[2832,2391],[2291,267],[2294,2392],[2293,2393],[2845,2394],[4095,2395],[2820,2396],[4096,2397],[4097,2398],[2824,2399],[3934,2400],[2713,2401],[3839,2402],[3698,2403],[3935,2404],[3697,2405],[3936,2406],[3701,2407],[2036,1190],[3937,2408],[3700,2409],[3938,2410],[3699,2411],[3840,2412],[3702,2413],[4101,2414],[1347,2415],[1955,2416],[4102,2417],[1236,2418],[4103,2419],[1115,2420],[4104,2421],[2704,999],[2708,2422],[4105,2423],[1035,2424],[1092,1925],[4106,2425],[2275,2426],[1183,2427],[1101,2428],[1040,2429],[1110,2430],[1362,2431],[4107,2432],[1044,2433],[2817,2434],[1038,2435],[1036,1925],[1042,1925],[1237,2436],[1102,2437],[4108,2438],[1225,2439],[4109,2440],[1045,2441],[1043,2442],[1181,2430],[1177,2443],[1103,2444],[2702,2445],[1094,2446],[1178,1925],[1348,2447],[1037,1925],[4110,2448],[1048,2449],[4111,2450],[1361,2451],[3841,2452],[2892,2453],[3842,2454],[3864,2455],[3302,2456],[3940,2457],[3385,2458],[3939,2459],[3710,2460],[2037,2461],[1986,2462],[1332,267],[2039,2463],[2038,267],[3865,2464],[3715,2465],[3843,2466],[2810,2467],[1138,267],[4112,2468],[1969,2469],[3866,2470],[3756,2471],[4125,2472],[3181,2473],[4113,2474],[3182,2475],[4114,2476],[3180,2477],[3179,2478],[2301,267],[4115,2479],[2313,104],[2295,267],[4116,2480],[2312,2481],[2311,2482],[2299,2483],[4126,2484],[2298,104],[2309,2485],[2308,104],[4127,2486],[2310,2487],[4128,2488],[2307,104],[4121,2489],[4122,2489],[3191,2490],[4123,2491],[3183,2492],[2296,1408],[4129,2493],[2302,2494],[4130,2495],[2332,2496],[2300,267],[4131,2497],[2305,2498],[4132,2499],[2335,2500],[2342,2501],[4133,2502],[2336,2503],[4134,2504],[2319,2505],[4135,2506],[2340,2507],[4136,2508],[2341,2509],[4137,2510],[2337,2511],[2329,267],[2330,2512],[4138,2513],[2339,2514],[4139,2515],[2338,2516],[4140,2517],[1211,2518],[4141,2519],[2331,2520],[2304,2521],[4142,2522],[2334,2523],[4143,2524],[2333,2525],[4144,2526],[2316,267],[4145,2527],[2315,2528],[2306,2529],[2343,2530],[2320,267],[4124,2531],[3184,2532],[3185,2533],[4117,2534],[3186,2535],[4118,2536],[3190,2537],[3189,2538],[4119,2539],[3188,2540],[2323,2541],[2328,2542],[2324,2543],[2325,2544],[2326,2545],[4146,2546],[2327,2547],[2321,267],[2344,2548],[2322,2549],[4120,2550],[3187,267],[2297,2551],[2314,2552],[3306,267],[3384,2553],[2849,2554],[3941,2555],[2850,2556],[2700,2557],[2056,2558],[4147,2559],[2711,2560],[2701,2561],[1224,2562],[2350,2563],[2348,2563],[2347,2563],[2349,2564],[2346,2563],[2345,2563],[2351,976],[4151,2565],[2354,2566],[1360,104],[4148,2567],[3216,2568],[4149,2569],[1371,2570],[3217,2571],[4150,2572],[3235,2573],[3236,2574],[2352,104],[2353,2575],[2355,2576],[1351,2577],[2357,2578],[1137,2579],[2358,2580],[1034,2581],[2362,2582],[2361,2583],[2364,2584],[2363,267],[4152,2585],[2084,2586],[2365,2587],[2366,2587],[1340,2588],[2367,2589],[630,267],[2368,2590],[1212,267],[2369,2591],[1213,2592],[639,2],[2370,2593],[2359,2594],[1353,2595],[1205,267],[2360,2596],[631,2597],[614,267],[2371,2598],[2372,2599],[1227,2600],[2373,2601],[634,2602],[2374,2603],[1193,2604],[1453,267],[2375,2605],[1207,1287],[2376,267],[2378,2606],[2377,267],[2379,2607],[636,2608],[2639,2609],[2638,2610],[2641,2611],[2640,267],[2642,2612],[1240,267],[2643,2613],[1228,267],[2644,267],[2646,2614],[2645,267],[2647,2615],[633,2616],[2648,2617],[1964,267],[2649,2618],[1668,2619],[2650,267],[2651,2620],[1682,267],[2652,2621],[1221,976],[2655,2622],[2654,2623],[2658,2624],[2657,2625],[2659,2626],[2656,267],[2660,2627],[1105,267],[2661,2628],[1106,976],[2663,2629],[2662,267],[632,267],[2664,2630],[1419,1287],[2665,2631],[2005,2038],[2666,2632],[1083,267],[2667,2633],[1206,2634],[4153,2635],[2684,2636],[2686,2637],[2688,2638],[2690,2639],[2692,2640],[2694,2641],[2672,2642],[2673,2643],[2675,2644],[2677,2645],[2356,2646],[2695,2454],[3953,1330],[2678,2643],[2682,2647],[2863,2648],[4154,2649],[613,2650]],"semanticDiagnosticsPerFile":[[1344,[{"start":2507,"length":35,"messageText":"Type instantiation is excessively deep and possibly infinite.","category":1,"code":2589},{"start":2507,"length":38,"messageText":"Type instantiation is excessively deep and possibly infinite.","category":1,"code":2589}]],[1486,[{"start":5247,"length":6,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'undefined'."}]],[1489,[{"start":1996,"length":15,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."},{"start":3425,"length":10,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."}]],[1540,[{"start":643,"length":6,"code":2739,"category":1,"messageText":"Type '{ google_client_id: string; google_client_secret: string; microsoft_client_id: string; microsoft_client_secret: string; microsoft_tenant: string; generic_client_id: string; generic_client_secret: string; ... 7 more ...; team_mappings: { ...; }; }' is missing the following properties from type 'SSOSettingsValues': saml_idp_metadata_url, saml_idp_metadata_xml, saml_sp_entity_id, saml_allow_unsolicited, generic_scope","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/sso/usessosettings.ts","start":1521,"length":6,"messageText":"The expected type comes from property 'values' which is declared here on type 'SSOSettingsResponse'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ google_client_id: string; google_client_secret: string; microsoft_client_id: string; microsoft_client_secret: string; microsoft_tenant: string; generic_client_id: string; generic_client_secret: string; ... 7 more ...; team_mappings: { ...; }; }' is not assignable to type 'SSOSettingsValues'."}},{"start":7416,"length":6,"code":2739,"category":1,"messageText":"Type '{ google_client_id: null; google_client_secret: null; microsoft_client_id: null; microsoft_client_secret: null; microsoft_tenant: null; generic_client_id: null; generic_client_secret: null; ... 7 more ...; team_mappings: { ...; }; }' is missing the following properties from type 'SSOSettingsValues': saml_idp_metadata_url, saml_idp_metadata_xml, saml_sp_entity_id, saml_allow_unsolicited, generic_scope","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/sso/usessosettings.ts","start":1521,"length":6,"messageText":"The expected type comes from property 'values' which is declared here on type 'SSOSettingsResponse'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ google_client_id: null; google_client_secret: null; microsoft_client_id: null; microsoft_client_secret: null; microsoft_tenant: null; generic_client_id: null; generic_client_secret: null; ... 7 more ...; team_mappings: { ...; }; }' is not assignable to type 'SSOSettingsValues'."}}]],[1561,[{"start":1402,"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":32568,"length":6,"messageText":"'models' is declared here.","category":3,"code":2728},{"file":"./src/components/networking.tsx","start":32875,"length":5,"messageText":"The expected type comes from property 'users' which is declared here on type 'UserListResponse'","category":3,"code":6500}]}]],[1978,[{"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'."}}]],[1993,[{"start":4983,"length":43,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type 'SpendMetrics' to type 'Record' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Index signature for type 'string' is missing in type 'SpendMetrics'.","category":1,"code":2329}]}}]],[2037,[{"start":56,"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":95,"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":131,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":251,"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":316,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":431,"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":505,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2041,[{"start":1826,"length":24,"code":2739,"category":1,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic_v2\"; }' is missing the following properties from type 'ComplexityRouterConfigPayload': classification_mode, session_affinity, deployment_affinity, modality_routing, modality_pin_override","relatedInformation":[{"file":"./src/lib/autorouter_presets.ts","start":1068,"length":24,"messageText":"The expected type comes from property 'complexity_router_config' which is declared here on type 'AutoRouterPreset'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic_v2\"; }' is not assignable to type 'ComplexityRouterConfigPayload'."}}]],[2042,[{"start":170,"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":225,"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":293,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":389,"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":607,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":753,"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":973,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1119,"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":1359,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1507,"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},{"start":1890,"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":1941,"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":2009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2101,"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":2336,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2434,"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":2669,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2771,"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":2964,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3060,"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":3613,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3677,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3756,"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":4194,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4290,"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":4528,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4598,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4669,"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":4945,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2044,[{"start":497,"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":553,"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":681,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":729,"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":835,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":886,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":935,"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":1009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1135,"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":1374,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1431,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1486,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2046,[{"start":425,"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":474,"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":690,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":955,"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":1264,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1463,"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":1694,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1795,"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":1851,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2039,"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":2311,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2503,"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":2770,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2871,"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":3139,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3482,"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":3755,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4094,"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":4354,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4619,"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":4691,"length":12,"messageText":"Parameter 'defaultModel' implicitly has an 'any' type.","category":1,"code":7006},{"start":4905,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5006,"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":5320,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5590,"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":5957,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6026,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2047,[{"start":613,"length":10,"code":2739,"category":1,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: string[]; COMPLEX: string[]; REASONING: string[]; }; tierLabels: undefined; classifierType: \"heuristic\"; classifierLlmConfig: undefined; classifierContextWindowSize: undefined; ... 16 more ...; returnRawModelName: false; }' is missing the following properties from type 'BuildComplexityRouterConfigParams': defaultModel, planModeMinTier, classificationExamples, heuristicFirstMaxTier, classificationMode","canonicalHead":{"code":2322,"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: string[]; COMPLEX: string[]; REASONING: string[]; }; tierLabels: undefined; classifierType: \"heuristic\"; classifierLlmConfig: undefined; classifierContextWindowSize: undefined; ... 16 more ...; returnRawModelName: false; }' is not assignable to type 'BuildComplexityRouterConfigParams'."}},{"start":1362,"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":1412,"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":1879,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1922,"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":2185,"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":2318,"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":2428,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2537,"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":2701,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2765,"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":2989,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3082,"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":3273,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3330,"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":3576,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3669,"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":3929,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3977,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4076,"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":4478,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4554,"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":4850,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4913,"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":5321,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5378,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5436,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5503,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5564,"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":6011,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6070,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6138,"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":6549,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6616,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6689,"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":7035,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7102,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7175,"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":7566,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7630,"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":8085,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8342,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8405,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8472,"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":8892,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8949,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9023,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9075,"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":9521,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9583,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9635,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9692,"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":9797,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9859,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9919,"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":10639,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10839,"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":11199,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11244,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11297,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11355,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11414,"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":11568,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11631,"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":11752,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11863,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11945,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12063,"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":12189,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12309,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12396,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12536,"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":12737,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12790,"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":13001,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13055,"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":13210,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13268,"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":13614,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13654,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13728,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13781,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13836,"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":14217,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14257,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14319,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14384,"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":14427,"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":14491,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14575,"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":14648,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14816,"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":14904,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15092,"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":15228,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15401,"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":15468,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15596,"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":15718,"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":15801,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15951,"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":16016,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16186,"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":16274,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16445,"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":16528,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16686,"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":16733,"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":16798,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17065,"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":17158,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17408,"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":17482,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17667,"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":17856,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":17864,"length":8,"messageText":"Parameter 'keywords' implicitly has an 'any' type.","category":1,"code":7006},{"start":17883,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18136,"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":18211,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18225,"length":24,"messageText":"Expected 2 arguments, but got 1.","category":1,"code":2554,"relatedInformation":[{"file":"./src/components/add_model/build_complexity_router_config.ts","start":11634,"length":24,"messageText":"An argument for 'rows' was not provided.","category":3,"code":6210}]},{"start":18570,"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":18659,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18814,"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":19058,"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":19240,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19319,"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":19545,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19625,"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":19884,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19968,"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":20094,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20180,"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":20415,"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":20824,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20922,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21005,"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":21335,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21416,"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":21488,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21639,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21796,"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":21882,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21981,"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":22222,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22299,"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":22448,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22534,"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":22777,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22934,"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":23301,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23392,"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":23787,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23873,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23986,"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":24430,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24528,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24650,"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":24747,"length":5,"messageText":"Parameter 'extra' implicitly has an 'any' type.","category":1,"code":7006},{"start":24874,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24973,"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":25007,"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":25086,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25172,"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":25468,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25521,"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":25753,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25833,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25931,"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":26139,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26194,"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":26235,"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":26281,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26340,"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":26492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26552,"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":26641,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26735,"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":26834,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26928,"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":27007,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27098,"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":27170,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27262,"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":27353,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27425,"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":27465,"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":27536,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27599,"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":27641,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27765,"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":27935,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28014,"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":28064,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28136,"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":28206,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28262,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28327,"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":28808,"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":28951,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29074,"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":29111,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29200,"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":29303,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29411,"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":29516,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29625,"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":29790,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29960,"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":30120,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30182,"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":30258,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30431,"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":30476,"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":30668,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30734,"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":30872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30932,"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":31123,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31191,"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":31331,"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":31381,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31492,"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":31548,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31658,"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":31759,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31882,"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":31950,"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":32032,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32131,"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":32382,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32423,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32584,"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":32630,"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":32716,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32803,"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":32879,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32969,"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":33065,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33218,"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":33548,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33728,"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":33781,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33934,"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":34151,"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":34340,"length":25,"messageText":"Parameter 'supportedReasoningEfforts' implicitly has an 'any' type.","category":1,"code":7006},{"start":34367,"length":13,"messageText":"Parameter 'expectedError' implicitly has an 'any' type.","category":1,"code":7006},{"start":34577,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34626,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34663,"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":34768,"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":34830,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34940,"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":35018,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35196,"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":35301,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35481,"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":35586,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35733,"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":36126,"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":36245,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36305,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36370,"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":36552,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36646,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36705,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36768,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36835,"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":37127,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37201,"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":37487,"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":37595,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37684,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37807,"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":37972,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38023,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38117,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38184,"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":38582,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38654,"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":38696,"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":38830,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38893,"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":38999,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39151,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39253,"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":39983,"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":40130,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40227,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40418,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40473,"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":40585,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40670,"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":40767,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40847,"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":40943,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41167,"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":41272,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41377,"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":41649,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41726,"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":41871,"length":14,"messageText":"Parameter 'classifierType' implicitly has an 'any' type.","category":1,"code":7006},{"start":42061,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42137,"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":42604,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42714,"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":43028,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43104,"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":43470,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43618,"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":43834,"length":5,"messageText":"Parameter '_name' implicitly has an 'any' type.","category":1,"code":7006},{"start":43841,"length":3,"messageText":"Parameter 'key' implicitly has an 'any' type.","category":1,"code":7006},{"start":44691,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44866,"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":45088,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45156,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45210,"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":45411,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45471,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45571,"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":45666,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45765,"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":45860,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45955,"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":46061,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46162,"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":46369,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46532,"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":46722,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46791,"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":46834,"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":46950,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47035,"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":47721,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47957,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48073,"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":48393,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48480,"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":48847,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48902,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48977,"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":49044,"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":49260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49327,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49393,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49474,"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":49787,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49843,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49895,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49962,"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":50166,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50222,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50288,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50371,"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":50409,"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":50526,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50631,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50749,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50873,"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":50943,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51142,"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":51246,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51303,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51380,"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":51566,"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":51780,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51854,"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":52297,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":52371,"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":52620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2048,[{"start":196,"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":238,"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":501,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":595,"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":679,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":786,"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":890,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":976,"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":1134,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1208,"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":1349,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1425,"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":1463,"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":1587,"length":12,"messageText":"Parameter 'systemPrompt' implicitly has an 'any' type.","category":1,"code":7006},{"start":1601,"length":8,"messageText":"Parameter 'expected' implicitly has an 'any' type.","category":1,"code":7006},{"start":1620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1707,"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":1746,"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":1823,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1920,"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":2040,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2089,[{"start":11595,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ session_affinity_ttl_seconds: number; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: string; keyword_tier_rules: { keywords: string[]; tier: string; }[]; ... 4 more ...; some_future_backend_key: { ...; }; }' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'classifier_type' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string' is not assignable to type 'ClassifierType | undefined'.","category":1,"code":2322}]}]}},{"start":13641,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ modality_routing: boolean; modality_pin_override: boolean; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: string; keyword_tier_rules: { keywords: string[]; tier: string; }[]; ... 4 more ...; some_future_backend_key: { ...; }; }' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'classifier_type' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string' is not assignable to type 'ClassifierType | undefined'.","category":1,"code":2322}]}]}},{"start":14094,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ classification_mode: string; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: string; keyword_tier_rules: { keywords: string[]; tier: string; }[]; escalation_keywords: string[]; semantic_keyword_matching: boolean; embedding_model: string; match_threshold: number...' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'classifier_type' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string' is not assignable to type 'ClassifierType | undefined'.","category":1,"code":2322}]}]}},{"start":14515,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ classification_mode: string; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: string; keyword_tier_rules: { keywords: string[]; tier: string; }[]; escalation_keywords: string[]; semantic_keyword_matching: boolean; embedding_model: string; match_threshold: number...' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'classifier_type' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string' is not assignable to type 'ClassifierType | undefined'.","category":1,"code":2322}]}]}},{"start":31386,"length":12,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'tiers' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ CASUAL: string[]; AUDIT: string[]; }' has no properties in common with type 'Partial>'.","category":1,"code":2559}]}]}},{"start":31954,"length":12,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'tiers' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ CASUAL: string[]; AUDIT: string[]; }' has no properties in common with type 'Partial>'.","category":1,"code":2559}]}]}},{"start":32495,"length":12,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }' is not assignable to parameter of type 'StoredComplexityRouterConfig'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'tiers' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ CASUAL: string[]; AUDIT: string[]; }' has no properties in common with type 'Partial>'.","category":1,"code":2559}]}]}},{"start":32661,"length":21,"code":2339,"category":1,"messageText":"Property 'classification_prompt' does not exist on type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }'."},{"start":32748,"length":23,"code":2339,"category":1,"messageText":"Property 'classification_examples' does not exist on type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }'."},{"start":32832,"length":21,"code":2339,"category":1,"messageText":"Property 'classification_prompt' does not exist on type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }'."},{"start":32916,"length":23,"code":2339,"category":1,"messageText":"Property 'classification_examples' does not exist on type '{ tiers: { CASUAL: string[]; AUDIT: string[]; }; tier_definitions: { name: string; description: string; }[]; fallback_tier: string; classifier_type: string; classifier_llm_config: { model: string; timeout_ms: number; reasoning_effort: string; }; }'."}]],[2090,[{"start":2335,"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":2392,"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":2586,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2656,"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":2844,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2916,"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":3012,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3051,"length":15,"messageText":"Spread types may only be created from object types.","category":1,"code":2698},{"start":3147,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3184,"length":12,"code":2559,"category":1,"messageText":"Type 'string' has no properties in common with type 'StoredComplexityRouterConfig'."},{"start":3244,"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":3460,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3570,"length":15,"messageText":"Spread types may only be created from object types.","category":1,"code":2698},{"start":3683,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3739,"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":3840,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3886,"length":15,"messageText":"Spread types may only be created from object types.","category":1,"code":2698},{"start":3985,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4022,"length":12,"code":2559,"category":1,"messageText":"Type 'string' has no properties in common with type 'StoredComplexityRouterConfig'."},{"start":4087,"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":4313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4428,"length":15,"messageText":"Spread types may only be created from object types.","category":1,"code":2698},{"start":4551,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4612,"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":4830,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4895,"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":5080,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5160,"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":5336,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5410,"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":5724,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2094,[{"start":1600,"length":17,"code":2322,"category":1,"messageText":{"messageText":"Type '{ budget_limit: number; time_period: string; } | { max_budget: number; budget_duration: string; }' is not assignable to type 'ModelBudgetConfig'.","category":1,"code":2322,"next":[{"messageText":"Type '{ max_budget: number; budget_duration: string; }' is missing the following properties from type 'ModelBudgetConfig': budget_limit, time_period","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ max_budget: number; budget_duration: string; }' is not assignable to type 'ModelBudgetConfig'."}}]}},{"start":2144,"length":12,"code":2322,"category":1,"messageText":{"messageText":"Type 'string | number' is not assignable to type 'number'.","category":1,"code":2322,"next":[{"messageText":"Type 'string' is not assignable to type 'number'.","category":1,"code":2322}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","start":506,"length":12,"messageText":"The expected type comes from property 'budget_limit' which is declared here on type 'ModelBudgetConfig'","category":3,"code":6500}]},{"start":2388,"length":12,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'number'.","relatedInformation":[{"file":"./src/components/key_team_helpers/modelmaxbudgeteditor.tsx","start":506,"length":12,"messageText":"The expected type comes from property 'budget_limit' which is declared here on type 'ModelBudgetConfig'","category":3,"code":6500}]},{"start":3742,"length":8,"code":2739,"category":1,"messageText":"Type '{ max_budget: number; budget_duration: string; tpm_limit: number; }' is missing the following properties from type 'ModelBudgetConfig': budget_limit, time_period","canonicalHead":{"code":2322,"messageText":"Type '{ max_budget: number; budget_duration: string; tpm_limit: number; }' is not assignable to type 'ModelBudgetConfig'."}}]],[2357,[{"start":16399,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":18287,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":18967,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":20970,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":26215,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":32436,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; match_threshold: number; escalation_keywords: never[]; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; match_threshold: number; escalation_keywords: never[]; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":32734,"length":356,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: false; deployment_affinity: true; enable_context_window_escalation: false; context_window_escalation_buffer: number; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: false; deployment_affinity: true; enable_context_window_escalation: false; context_window_escalation_buffer: number; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":33824,"length":45,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ classification_mode: \"user_turn\"; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ classification_mode: \"user_turn\"; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":33999,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":34250,"length":265,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: false; deployment_affinity: true; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: false; deployment_affinity: true; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":35426,"length":64,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tier_labels: { SIMPLE: string; REASONING: string; }; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tier_labels: { SIMPLE: string; REASONING: string; }; tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":35675,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":36212,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":36912,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: { model_name: string; litellm_params: { reasoning_effort: string; }; }[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boo...' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: { model_name: string; litellm_params: { reasoning_effort: string; }; }[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boo...' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":37948,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: { model_name: string; litellm_params: { reasoning_effort: string; }; }[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: bool...' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: { model_name: string; litellm_params: { reasoning_effort: string; }; }[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: bool...' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":39150,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: ({ model_name: string; litellm_params: { reasoning_effort: string; temperature: number; }; } | { ...; })[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: ...' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: never[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: string[]; }; tier_model_configs: { REASONING: ({ model_name: string; litellm_params: { reasoning_effort: string; temperature: number; }; } | { ...; })[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: ...' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}},{"start":39954,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is not assignable to parameter of type 'ComplexityRouterConfigPayload'.","category":1,"code":2345,"next":[{"messageText":"Type '{ tiers: { SIMPLE: string[]; MEDIUM: never[]; COMPLEX: never[]; REASONING: never[]; }; classifier_type: \"heuristic\"; classification_mode: \"every_request\"; session_affinity: boolean; deployment_affinity: boolean; }' is missing the following properties from type 'ComplexityRouterConfigPayload': modality_routing, modality_pin_override","category":1,"code":2739}]}}]],[2366,[{"start":3271,"length":4,"code":2322,"category":1,"messageText":{"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; } & {}'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key_alias: string; }' is missing the following properties from type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }': aliases, allowed_cache_controls, allowed_routes, auto_rotate, and 7 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }'."}}]},"relatedInformation":[{"file":"./node_modules/openapi-fetch/dist/index.d.mts","start":3474,"length":4,"messageText":"The expected type comes from property 'body' which is declared here on type '{ params?: { query?: undefined; header?: { \"litellm-changed-by\"?: string | null | undefined; } | undefined; path?: undefined; cookie?: undefined; } | undefined; } & { body: { access_group_ids?: string[] | ... 1 more ... | undefined; ... 50 more ...; user_id?: string | ... 1 more ... | undefined; } & {}; } & { ...; }...'","category":3,"code":6500}]},{"start":3928,"length":4,"code":2322,"category":1,"messageText":{"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; } & {}'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key_alias: string; }' is missing the following properties from type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }': aliases, allowed_cache_controls, allowed_routes, auto_rotate, and 7 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ key_alias: string; }' is not assignable to type '{ access_group_ids?: string[] | null | undefined; agent_id?: string | null | undefined; aliases: ({ [x: string]: unknown; } & {}) | null; allowed_cache_controls: unknown[] | null; allowed_passthrough_routes?: unknown[] | ... 1 more ... | undefined; ... 46 more ...; user_id?: string | ... 1 more ... | undefined; }'."}}]},"relatedInformation":[{"file":"./node_modules/openapi-fetch/dist/index.d.mts","start":3474,"length":4,"messageText":"The expected type comes from property 'body' which is declared here on type '{ params?: { query?: undefined; header?: { \"litellm-changed-by\"?: string | null | undefined; } | undefined; path?: undefined; cookie?: undefined; } | undefined; } & { body: { access_group_ids?: string[] | ... 1 more ... | undefined; ... 50 more ...; user_id?: string | ... 1 more ... | undefined; } & {}; } & { ...; }...'","category":3,"code":6500}]}]],[2367,[{"start":1322,"length":3,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":1327,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1491,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1616,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1945,"length":26,"messageText":"Object is possibly 'undefined'.","category":1,"code":2532},{"start":1969,"length":1,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":2301,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":2345,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":2383,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":4907,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":4951,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048}]],[2660,[{"start":272,"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":354,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":907,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":1076,"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":1114,"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":1199,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1276,"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":1338,"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":1481,"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":1665,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1712,"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":1757,"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":1836,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1918,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1976,"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":2023,"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":2370,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2447,"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":2755,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2802,"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":2838,"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":2914,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2969,"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":3051,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3148,"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":3523,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3642,"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":3690,"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":4031,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4159,"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":4484,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4533,"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":4878,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4940,"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":4977,"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":5400,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5476,"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":6141,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6218,"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":6485,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6532,"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":6573,"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":6639,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6703,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6766,"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":6823,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6888,"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":7012,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7166,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7255,"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":7313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7379,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7452,"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":7497,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7552,"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":7599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7663,"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":7736,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7807,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7880,"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":7925,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8020,"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":8403,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8481,"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":8933,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9013,"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":9490,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9631,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9757,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9835,"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":9876,"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":10661,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10731,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10785,"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":11070,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11113,"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":11970,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12047,"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":12318,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2661,[{"start":3595,"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":785,"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":4010,"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":785,"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":4616,"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":785,"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":5031,"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":785,"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'."}}]],[2812,[{"start":3077,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":3083,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":3175,"length":4,"messageText":"'opts' is possibly 'undefined'.","category":1,"code":18048}]],[3051,[{"start":7973,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.","category":1,"code":2345,"next":[{"messageText":"Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, autocorrect, and 129 more.","category":1,"code":2740}]}},{"start":8039,"length":4,"code":2345,"category":1,"messageText":{"messageText":"Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.","category":1,"code":2345,"next":[{"messageText":"Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, autocorrect, and 129 more.","category":1,"code":2740}]}}]],[3057,[{"start":3761,"length":7,"code":2741,"category":1,"messageText":"Property 'by_router' is missing in type '{ by_tier: { group: string; turn_count: number; real_win_rate_pct: number; shadow_win_rate_pct: number; tie_rate_pct: number; avg_judge_confidence: number; real_spend: number; shadow_spend: number; cache_hit_turns: number; }[]; ... 7 more ...; shed_count: number; }' but required in type '{ by_current_model: { avg_judge_confidence: number; cache_hit_turns: number; group: string; real_spend: number; real_win_rate_pct: number; shadow_spend: number; shadow_win_rate_pct: number; tie_rate_pct: number; turn_count: number; }[]; ... 9 more ...; withheld_count?: number | ... 1 more ... | undefined; }'.","relatedInformation":[{"file":"./src/lib/http/schema.d.ts","start":1378747,"length":9,"messageText":"'by_router' is declared here.","category":3,"code":2728},{"file":"./src/lib/http/schema.d.ts","start":1372928,"length":7,"messageText":"The expected type comes from property 'results' which is declared here on type '{ baseline_model?: string | null | undefined; created_at: string; direction: \"reverse\" | \"forward\"; ends_at: string; error_count?: number | null | undefined; job_id: string; judge_model: string; ... 10 more ...; targets: { ...; }[]; }'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ by_tier: { group: string; turn_count: number; real_win_rate_pct: number; shadow_win_rate_pct: number; tie_rate_pct: number; avg_judge_confidence: number; real_spend: number; shadow_spend: number; cache_hit_turns: number; }[]; ... 7 more ...; shed_count: number; }' is not assignable to type '{ by_current_model: { avg_judge_confidence: number; cache_hit_turns: number; group: string; real_spend: number; real_win_rate_pct: number; shadow_spend: number; shadow_win_rate_pct: number; tie_rate_pct: number; turn_count: number; }[]; ... 9 more ...; withheld_count?: number | ... 1 more ... | undefined; }'."}},{"start":28794,"length":7,"code":2741,"category":1,"messageText":"Property 'by_router' is missing in type '{ by_tier: never[]; by_current_model: never[]; overall_shadow_win_rate_pct: number; overall_tie_rate_pct: number; sampled_real_spend: number; sampled_shadow_spend: number; }' but required in type '{ by_current_model: { avg_judge_confidence: number; cache_hit_turns: number; group: string; real_spend: number; real_win_rate_pct: number; shadow_spend: number; shadow_win_rate_pct: number; tie_rate_pct: number; turn_count: number; }[]; ... 9 more ...; withheld_count?: number | ... 1 more ... | undefined; }'.","relatedInformation":[{"file":"./src/lib/http/schema.d.ts","start":1378747,"length":9,"messageText":"'by_router' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ by_tier: never[]; by_current_model: never[]; overall_shadow_win_rate_pct: number; overall_tie_rate_pct: number; sampled_real_spend: number; sampled_shadow_spend: number; }' is not assignable to type '{ by_current_model: { avg_judge_confidence: number; cache_hit_turns: number; group: string; real_spend: number; real_win_rate_pct: number; shadow_spend: number; shadow_win_rate_pct: number; tie_rate_pct: number; turn_count: number; }[]; ... 9 more ...; withheld_count?: number | ... 1 more ... | undefined; }'."}}]],[3058,[{"start":6233,"length":5,"code":2322,"category":1,"messageText":{"messageText":"Type '{ sessions: number; turns: number; avg_turns_per_session: number; avg_session_seconds: number; avg_tokens_per_session: number; spend: number; saved_spend: number; baseline_spend: number; saved_pct: number; saved_per_session: number; cache: { ...; }; }' is not assignable to type '{ avg_session_seconds: number; avg_tokens_per_session: number; avg_turns_per_session: number; baseline_spend: number; cache: { coverage_pct: number; first_visit: { hit_rate_pct: number; hits: number; turns: number; }; ... 8 more ...; unordered_turns: number; }; ... 9 more ...; turns: number; } | { ...; }'.","category":1,"code":2322,"next":[{"messageText":"Property 'classifier_cost' is missing in type '{ sessions: number; turns: number; avg_turns_per_session: number; avg_session_seconds: number; avg_tokens_per_session: number; spend: number; saved_spend: number; baseline_spend: number; saved_pct: number; saved_per_session: number; cache: { ...; }; }' but required in type '{ avg_session_seconds: number; avg_tokens_per_session: number; avg_turns_per_session: number; baseline_spend: number; cache: { coverage_pct: number; first_visit: { hit_rate_pct: number; hits: number; turns: number; }; ... 8 more ...; unordered_turns: number; }; ... 6 more ...; turns: number; }'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type '{ sessions: number; turns: number; avg_turns_per_session: number; avg_session_seconds: number; avg_tokens_per_session: number; spend: number; saved_spend: number; baseline_spend: number; saved_pct: number; saved_per_session: number; cache: { ...; }; }' is not assignable to type '{ avg_session_seconds: number; avg_tokens_per_session: number; avg_turns_per_session: number; baseline_spend: number; cache: { coverage_pct: number; first_visit: { hit_rate_pct: number; hits: number; turns: number; }; ... 8 more ...; unordered_turns: number; }; ... 6 more ...; turns: number; }'."}}]},"relatedInformation":[{"file":"./src/lib/http/schema.d.ts","start":883611,"length":15,"messageText":"'classifier_cost' is declared here.","category":3,"code":2728},{"file":"./src/app/(dashboard)/cost-optimization/_components/autorouterbenchmarks.ts","start":508,"length":5,"messageText":"The expected type comes from property 'stats' which is declared here on type 'BenchmarkView'","category":3,"code":6500}]}]],[3111,[{"start":2067,"length":42,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":2572,"length":41,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":3058,"length":34,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":3554,"length":8,"code":2322,"category":1,"messageText":"Type 'undefined' is not assignable to type 'string'.","relatedInformation":[{"file":"./src/app/(dashboard)/hooks/useauthorized.ts","start":1740,"length":50,"messageText":"The expected type comes from property 'userRole' which is declared here on type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'","category":3,"code":6500}]},{"start":4033,"length":42,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":5026,"length":34,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; token: string; accessToken: string; userId: string; userEmail: string; premiumUser: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3119,[{"start":3309,"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}]}}]],[3120,[{"start":1123,"length":14,"code":2322,"category":1,"messageText":{"messageText":"Type '{ \"hide-secrets\": { ui_friendly_name: string; detect_secrets_config: { param: string; description: string; required: boolean; type: string; }; }; }' is not assignable to type 'ProviderParamsResponse'.","category":1,"code":2322,"next":[{"messageText":"Property '\"hide-secrets\"' is incompatible with index signature.","category":1,"code":2530,"next":[{"messageText":"Type '{ ui_friendly_name: string; detect_secrets_config: { param: string; description: string; required: boolean; type: string; }; }' is not assignable to type '{ [key: string]: ProviderParam; }'.","category":1,"code":2322,"next":[{"messageText":"Property 'ui_friendly_name' is incompatible with index signature.","category":1,"code":2530,"next":[{"messageText":"Type 'string' is not assignable to type 'ProviderParam'.","category":1,"code":2322}]}]}]}]},"relatedInformation":[{"file":"./src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.tsx","start":1281,"length":14,"messageText":"The expected type comes from property 'providerParams' which is declared here on type 'IntrinsicAttributes & GuardrailProviderFieldsProps'","category":3,"code":6500}]}]],[3151,[{"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":1981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2069,"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":2260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2354,"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":2467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2569,"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":2908,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2988,"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":3401,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3480,"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":3592,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3750,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3254,[{"start":2766,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":2896,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":3912,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."}]],[3263,[{"start":10763,"length":423,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type '{ status: \"healthy\"; last_health_check: string; health_check_error: null; teams: { team_id: string; }[]; allowed_tools: string[]; has_user_credential: true; approval_status: \"approved\"; submitted_by: string; ... 47 more ...; env_vars?: MCPEnvVar[] | null; }' to type 'MCPServer' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Types of property 'approval_status' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '\"approved\"' is not comparable to type '\"active\" | \"pending_review\" | \"rejected\" | null | undefined'.","category":1,"code":2678}]}]}}]],[3375,[{"start":4578,"length":15,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: boolean; isAuthorized: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: boolean; isAuthorized: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3379,[{"start":4867,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":11772,"length":24,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }[]' is not assignable to parameter of type '({ model_name: string; litellm_params: { model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; auto_router_config?: undefined; auto_router_default_model?: undefined; }; model_info: { ...; }; } | { ...; } | { ...; } | { ...; } | { ...; })[]'.","category":1,"code":2345,"next":[{"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; auto_router_config?: undefined; auto_router_default_model?: undefined; }; model_info: { ...; }; } | { ...; } | { ...; } | { ...; } | { ...; }'.","category":1,"code":2322,"next":[{"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }; model_info: { ...; }; }'.","category":1,"code":2322,"next":[{"messageText":"Types of property 'litellm_params' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }' is missing the following properties from type '{ model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }': auto_router_config, auto_router_default_model","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ model_name: string; litellm_params: { model: string; complexity_router_config: { tiers: {}; classifier_type: string; }; }; model_info: { id: string; db_model: boolean; created_at: string; }; }' is not assignable to type '{ model_name: string; litellm_params: { model: string; auto_router_config: string; auto_router_default_model: string; complexity_router_config?: undefined; complexity_router_default_model?: undefined; }; model_info: { ...; }; }'."}}]}]}]}]}}]],[3653,[{"start":2185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2365,"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":2415,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":2652,"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":3085,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3188,"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":3521,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3674,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3768,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3861,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3998,"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":4041,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":4130,"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":4412,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3686,[{"start":2516,"length":2,"code":2345,"category":1,"messageText":"Argument of type '{}' is not assignable to parameter of type 'void'."}]],[3730,[{"start":11320,"length":300,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":20262,"length":14,"code":2322,"category":1,"messageText":{"messageText":"Type '(value: TagListResponse | PromiseLike) => void' is not assignable to type '(tags: Record) => void'.","category":1,"code":2322,"next":[{"messageText":"Types of parameters 'value' and 'tags' are incompatible.","category":1,"code":2328,"next":[{"messageText":"Type 'Record' is not assignable to type 'TagListResponse | PromiseLike'.","category":1,"code":2322,"next":[{"messageText":"Property 'then' is missing in type 'Record' but required in type 'PromiseLike'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type 'Record' is not assignable to type 'PromiseLike'."}}]}]}]},"relatedInformation":[{"file":"./node_modules/typescript/lib/lib.es5.d.ts","start":71612,"length":239,"messageText":"'then' is declared here.","category":3,"code":2728}]},{"start":21563,"length":15,"code":2322,"category":1,"messageText":{"messageText":"Type '(value: TagListResponse | PromiseLike) => void' is not assignable to type '(tags: Record) => void'.","category":1,"code":2322,"next":[{"messageText":"Types of parameters 'value' and 'tags' are incompatible.","category":1,"code":2328,"next":[{"messageText":"Type 'Record' is not assignable to type 'TagListResponse | PromiseLike'.","category":1,"code":2322,"next":[{"messageText":"Property 'then' is missing in type 'Record' but required in type 'PromiseLike'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type 'Record' is not assignable to type 'PromiseLike'."}}]}]}]},"relatedInformation":[{"file":"./node_modules/typescript/lib/lib.es5.d.ts","start":71612,"length":239,"messageText":"'then' is declared here.","category":3,"code":2728}]},{"start":22118,"length":308,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":33445,"length":331,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":34328,"length":331,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3783,[{"start":3053,"length":46,"code":2352,"category":1,"messageText":{"messageText":"Conversion of type '[url: string][]' to type '[string, RequestInit][]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.","category":1,"code":2352,"next":[{"messageText":"Type '[url: string]' is not comparable to type '[string, RequestInit]'.","category":1,"code":2678,"next":[{"messageText":"Source has 1 element(s) but target requires 2.","category":1,"code":2618}]}]}}]],[3810,[{"start":9097,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304}]],[3828,[{"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}]],[3833,[{"start":792,"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":835,"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":1122,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1226,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1306,"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":1527,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"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":1913,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2009,"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":2261,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2311,"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":2510,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2560,"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":2731,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3843,[{"start":780,"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":813,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":891,"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":1067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1117,"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":1321,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1371,"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":1570,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1620,"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":1789,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1848,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2053,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2107,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2176,"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":2342,"length":8,"messageText":"Parameter 'severity' implicitly has an 'any' type.","category":1,"code":7006},{"start":2352,"length":9,"messageText":"Parameter 'iconClass' implicitly has an 'any' type.","category":1,"code":7006},{"start":2505,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2584,"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":2857,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3006,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3063,"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":3512,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3571,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3661,"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":4075,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3880,[{"start":2005,"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":2084,"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":[]}]],[3888,[{"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}]],[3889,[{"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}]],[3890,[{"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}]],[3891,[{"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},{"start":2584,"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":2872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2940,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3082,"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":3483,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3633,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3667,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3766,"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":4264,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4439,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4498,"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":4989,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5022,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3892,[{"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}]],[3898,[{"start":236,"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":276,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":330,"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":583,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":735,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":802,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":859,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":931,"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":1173,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1267,"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":1590,"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":2168,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2268,"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":2981,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3925,[{"start":1201,"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":1242,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1294,"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":1434,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1517,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1627,"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":1810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1875,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1963,"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":2273,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2391,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2426,"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":2681,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2876,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2924,"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":3198,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3285,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3314,"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":3448,"length":8,"messageText":"Parameter 'severity' implicitly has an 'any' type.","category":1,"code":7006},{"start":3458,"length":5,"messageText":"Parameter 'label' implicitly has an 'any' type.","category":1,"code":7006},{"start":3609,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3933,[{"start":5200,"length":36,"messageText":"Object is possibly 'null'.","category":1,"code":2531}]],[3935,[{"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":1485,"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":1663,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1726,"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":1772,"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":1825,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1880,"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":1947,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1998,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3940,[{"start":1780,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":15138,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3941,[{"start":3402,"length":15,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."},{"start":3423,"length":7,"code":2322,"category":1,"messageText":"Type 'number' is not assignable to type 'ModelBudgetConfig'."}]],[3942,[{"start":3610,"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":785,"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":5344,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":5861,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":6795,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":7743,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":8690,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":9485,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10249,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10894,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":11581,"length":56,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":12876,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[3943,[{"start":1457,"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":1501,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1553,"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":1638,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1723,"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":2186,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2268,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2372,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2435,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2539,"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":3054,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3159,"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":3589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3689,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3944,[{"start":837,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1657,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1766,"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":1811,"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":2092,"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":2275,"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":2634,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2723,"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":3113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3206,"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":3312,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3386,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3463,"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":3808,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3886,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4002,"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":4375,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4449,"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":4642,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4701,"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":5015,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5192,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5251,"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":5400,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3945,[{"start":1408,"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":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":1553,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1641,"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":1763,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1828,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1893,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1959,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2032,"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":2160,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2220,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2300,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2389,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2466,"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":2678,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2761,"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":2980,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3046,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3117,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3188,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3259,"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":3580,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3618,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3673,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3718,"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":3924,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4090,"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":4467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4582,"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":5320,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5383,"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":5802,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5847,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6204,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6280,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6356,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6458,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6535,"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":7105,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7175,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7245,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7339,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7430,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7505,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7602,"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":8188,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8233,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8290,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8377,"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":8839,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8914,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9002,"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":9526,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9609,"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":10227,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10339,"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":10883,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10960,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11056,"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":11516,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11616,"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":11900,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11988,"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":12565,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12610,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12705,"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":12988,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13067,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13164,"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":13729,"length":5,"messageText":"Parameter 'label' implicitly has an 'any' type.","category":1,"code":7006},{"start":13736,"length":11,"messageText":"Parameter 'replacement' implicitly has an 'any' type.","category":1,"code":7006},{"start":13749,"length":8,"messageText":"Parameter 'expected' implicitly has an 'any' type.","category":1,"code":7006},{"start":14281,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14316,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14428,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14510,"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":15129,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15170,"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":15380,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15464,"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":15810,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15867,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15931,"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":16652,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16732,"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":17485,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17587,"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":17813,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17889,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17984,"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":18393,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18475,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18687,"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":19160,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19287,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19325,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19404,"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":20092,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20216,"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":20742,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20803,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20950,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21018,"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":21303,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21382,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21457,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21541,"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":21821,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21890,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21968,"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":22546,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22613,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22642,"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":23087,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23172,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23254,"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":23388,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23474,"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":23943,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24032,"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":24496,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24591,"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":24910,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24994,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25068,"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":25439,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25512,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25587,"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":25739,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25835,"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":26078,"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":26360,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26456,"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":26829,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26867,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26944,"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":27541,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27666,"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":27969,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28057,"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":28745,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28825,"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":29313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29407,"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":29886,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29924,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29997,"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":30245,"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":30516,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30596,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30689,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30781,"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":31178,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31325,"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":31738,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31888,"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":32296,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32379,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32477,"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":32838,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33002,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33040,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33117,"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":33567,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33669,"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":34220,"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":34616,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34698,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34793,"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":35371,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35416,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35465,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35547,"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":35784,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35857,"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":36463,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36634,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36683,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36762,"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":37118,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37211,"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":37701,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37746,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37803,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37893,"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":38263,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38355,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38456,"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":38601,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38704,"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":38939,"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":39117,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39181,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39244,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39315,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39394,"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":39566,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39653,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":39748,"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":40033,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40117,"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":40436,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40474,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40547,"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":40711,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40810,"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":40951,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41043,"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":41284,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41343,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41406,"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":41768,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41885,"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":41945,"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":42340,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42408,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42501,"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":42754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43050,"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":43418,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43512,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43562,"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":44018,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44090,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44181,"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":44503,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44617,"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":44677,"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":44930,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45045,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45162,"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":45543,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45640,"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":45907,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46031,"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":46389,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46502,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46573,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46677,"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":47108,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47224,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47327,"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":47743,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47847,"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":48002,"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":48173,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48283,"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":48608,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48725,"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":49059,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49097,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49168,"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":49616,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49654,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":49719,"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":49991,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50062,"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":50622,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50743,"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":51226,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51350,"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":51552,"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":51844,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51931,"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":52339,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":52457,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":52518,"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":52996,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53083,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53177,"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":53459,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53576,"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":53648,"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":53911,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53965,"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":54356,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":54407,"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":54850,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55011,"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":55567,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55668,"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":56214,"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":56482,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":56561,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":56733,"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":57069,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":57238,"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":57698,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":57756,"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":58198,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":58349,"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":58465,"length":6,"messageText":"Parameter 'action' implicitly has an 'any' type.","category":1,"code":7006},{"start":58919,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":58975,"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":59193,"length":5,"messageText":"Parameter 'model' implicitly has an 'any' type.","category":1,"code":7006},{"start":59200,"length":6,"messageText":"Parameter 'effort' implicitly has an 'any' type.","category":1,"code":7006},{"start":59208,"length":5,"messageText":"Parameter 'label' implicitly has an 'any' type.","category":1,"code":7006},{"start":59215,"length":7,"messageText":"Parameter 'warning' implicitly has an 'any' type.","category":1,"code":7006},{"start":59367,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59507,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59571,"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":59678,"length":5,"messageText":"Parameter 'model' implicitly has an 'any' type.","category":1,"code":7006},{"start":59802,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59950,"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":60019,"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":60173,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":60457,"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":60763,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":60917,"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":60989,"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":61374,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":61448,"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":61881,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":62183,"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":62591,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":62906,"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":63185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":63332,"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":63738,"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":64338,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":64346,"length":5,"messageText":"Parameter 'value' implicitly has an 'any' type.","category":1,"code":7006},{"start":64398,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":64482,"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":64845,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":64937,"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":65991,"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":66137,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":66232,"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":66455,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":66604,"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":66964,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":67088,"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":67264,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":67348,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":67444,"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":67699,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":67756,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":67813,"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":68094,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":68251,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":68308,"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":68447,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":68519,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":68569,"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":68785,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":68977,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":69074,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":69153,"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":69442,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":69526,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":69648,"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":69919,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":69997,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":70113,"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":70549,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":70650,"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":71037,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":71115,"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":71233,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":71310,"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":71435,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":71531,"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":71824,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":71911,"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":72476,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":72592,"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":73177,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":73299,"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":73895,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":73960,"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":74178,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":74275,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":74386,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":74463,"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":74933,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75001,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75046,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75192,"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":75500,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75601,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75691,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":75817,"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":76249,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":76345,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":76435,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":76556,"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":77078,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":77170,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":77271,"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":77467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":77558,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":77682,"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":78289,"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":78568,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":78606,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":78728,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":78815,"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":79163,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":79454,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":79644,"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":80140,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":80175,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":80278,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":80468,"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":80723,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3946,[{"start":10172,"length":28,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to parameter of type 'UseQueryResult'.","category":1,"code":2345,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverRefetchErrorResult | QueryObserverSuccessResult | QueryObserverPlaceholderResult<...>'.","category":1,"code":2322,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is missing the following properties from type 'QueryObserverPlaceholderResult': error, isLoading, isLoadingError, isRefetchError, and 18 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverPlaceholderResult'."}}]}]}},{"start":11331,"length":28,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to parameter of type 'UseQueryResult'.","category":1,"code":2345,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverRefetchErrorResult | QueryObserverSuccessResult | QueryObserverPlaceholderResult<...>'.","category":1,"code":2322,"next":[{"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is missing the following properties from type 'QueryObserverPlaceholderResult': error, isLoading, isLoadingError, isRefetchError, and 18 more.","category":1,"code":2740,"canonicalHead":{"code":2322,"messageText":"Type '{ data: ComplexityScorerDefaults; isPending: boolean; isError: boolean; refetch: Mock; }' is not assignable to type 'QueryObserverPlaceholderResult'."}}]}]}}]],[3947,[{"start":1213,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1412,"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":1478,"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":1878,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2020,"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":2305,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2574,"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":2996,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3076,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3207,"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":3717,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3910,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4162,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4286,"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":4544,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4630,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4727,"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":5116,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5238,"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":5478,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5600,"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":5817,"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":6317,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6562,"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":6708,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6776,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6866,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7042,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7129,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7231,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7331,"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":7640,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7722,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8356,"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":8618,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8663,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8736,"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":8982,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9239,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9341,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9448,"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":9710,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9883,"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":10017,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10109,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10204,"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":10624,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10809,"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":11251,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3949,[{"start":670,"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":995,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1089,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1162,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1222,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1294,"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":1454,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1549,"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":1753,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1842,"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":2056,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3950,[{"start":450,"length":5,"code":2322,"category":1,"messageText":{"messageText":"Type '{ SIMPLE: string; MEDIUM: string; COMPLEX: string; REASONING: string; }' is not assignable to type 'ComplexityTiers'.","category":1,"code":2322,"next":[{"messageText":"Types of property 'SIMPLE' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string' is not assignable to type 'string[]'.","category":1,"code":2322,"canonicalHead":{"code":2322,"messageText":"Type '{ SIMPLE: string; MEDIUM: string; COMPLEX: string; REASONING: string; }' is not assignable to type 'ComplexityTiers'."}}]}]},"relatedInformation":[{"file":"./src/components/add_model/complexityrouterconfig.tsx","start":14750,"length":5,"messageText":"The expected type comes from property 'tiers' which is declared here on type 'ComplexityRouterConfigValue'","category":3,"code":6500}]},{"start":833,"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":884,"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":974,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1098,"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":1205,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1331,"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":1389,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1460,"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":1504,"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":1589,"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":1724,"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":1894,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1939,"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":2415,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2460,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2655,"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":3021,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3066,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3177,"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":3520,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3558,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3625,"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":3894,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3932,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4009,"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":4133,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4196,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4276,"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":4450,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4545,"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":4747,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4845,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4883,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3952,[{"start":3331,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3861,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6669,"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":6708,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":7325,"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":7445,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7610,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7698,"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":8039,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8117,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8214,"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":8985,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9032,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9086,"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":9795,"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":10087,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10266,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10549,"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":10665,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10755,"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":11066,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11155,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11246,"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":11379,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11459,"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":11673,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11742,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12087,"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":12683,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12742,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":12857,"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":13419,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13477,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":13544,"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":14178,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14378,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14463,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14526,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":14595,"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":15088,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":15444,"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":15941,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16107,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16200,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16267,"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":16761,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":16949,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17033,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":17130,"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":17917,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18019,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18109,"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":18796,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18888,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":18978,"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":19715,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19774,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":19977,"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":20478,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20571,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":20824,"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":21467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21560,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":21627,"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":22064,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22319,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":22476,"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":23025,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23191,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23250,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":23423,"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":24161,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24325,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24400,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":24480,"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":25331,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25390,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":25561,"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":25604,"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":26053,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26199,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26278,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":26362,"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":27089,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27235,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27306,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":27382,"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":28080,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28284,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28430,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28501,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":28776,"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":29473,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29532,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":29697,"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":30557,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30616,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":30813,"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":31424,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31483,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":31649,"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":32097,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32286,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32345,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":32503,"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":32929,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33166,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33225,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":33384,"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":34015,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34074,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34234,"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":34696,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34755,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":34948,"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":35735,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":35794,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36109,"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":36352,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":36530,"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":36567,"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":36938,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":37026,"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":38304,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38451,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38553,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38611,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38758,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38875,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":38946,"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":39752,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40011,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40103,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40209,"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":40250,"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":40712,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":40772,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41120,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41217,"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":41438,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41629,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41689,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":41789,"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":42193,"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":42531,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42657,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":42743,"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":43048,"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":43370,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43467,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":43778,"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":43995,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44093,"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":44412,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44587,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":44944,"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":45477,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":45538,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":46240,"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":46701,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47376,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47544,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47645,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":47716,"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":48260,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48321,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":48628,"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":49308,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":49459,"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":49852,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50175,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50365,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":50436,"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":50771,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51281,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51326,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51373,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":51448,"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":51491,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":51592,"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":52050,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":52111,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":52276,"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":52956,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53017,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":53194,"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":53991,"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":54371,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":54461,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":54564,"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":54974,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55395,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55456,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":55910,"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":56324,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":56520,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":56716,"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":56920,"length":6,"messageText":"Parameter '_label' implicitly has an 'any' type.","category":1,"code":7006},{"start":56928,"length":9,"messageText":"Parameter 'modelName' implicitly has an 'any' type.","category":1,"code":7006},{"start":57278,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":57365,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":57452,"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":58005,"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":58369,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":58459,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":58562,"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":58925,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59255,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59316,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":59780,"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":60250,"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":60309,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":60432,"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":60531,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":60696,"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":61146,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":61301,"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":61453,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":61610,"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":61660,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":61751,"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":62011,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":62124,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":62200,"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":62461,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":62540,"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":62871,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":63038,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":63075,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":63210,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3955,[{"start":907,"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":954,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":1006,"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":1338,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1383,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1456,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1533,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1610,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1716,"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":2009,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2084,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2163,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2237,"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":2746,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2827,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2918,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3025,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3104,"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":3519,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3978,[{"start":3670,"length":6,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ metadata: { key: string; value?: string | undefined; }[]; }' is not assignable to parameter of type '{ metadata?: MetadataPair[] | undefined; }'.","category":1,"code":2345,"next":[{"messageText":"Types of property 'metadata' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type '{ key: string; value?: string | undefined; }[]' is not assignable to type 'MetadataPair[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ key: string; value?: string | undefined; }' is not assignable to type 'MetadataPair'.","category":1,"code":2322,"next":[{"messageText":"Types of property 'value' are incompatible.","category":1,"code":2326,"next":[{"messageText":"Type 'string | undefined' is not assignable to type 'string'.","category":1,"code":2322,"next":[{"messageText":"Type 'undefined' is not assignable to type 'string'.","category":1,"code":2322}],"canonicalHead":{"code":2322,"messageText":"Type '{ key: string; value?: string | undefined; }' is not assignable to type 'MetadataPair'."}}]}]}]}]}]}}]],[3984,[{"start":806,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1045,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1459,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1920,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":2291,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":2754,"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":179,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]}]],[3987,[{"start":221,"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":265,"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":376,"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":589,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":667,"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":802,"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":1023,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1104,"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":1445,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4090,[{"start":2930,"length":304,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ isLoading: false; isAuthorized: true; userId: string; userRole: string; accessToken: string; token: string; userEmail: string; premiumUser: boolean; disabledPersonalKeyCreation: null; showSSOBanner: false; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ isLoading: false; isAuthorized: true; userId: string; userRole: string; accessToken: string; token: string; userEmail: string; premiumUser: boolean; disabledPersonalKeyCreation: null; showSSOBanner: false; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4098,[{"start":5253,"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, key_type, 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'."}}]],[4099,[{"start":5009,"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; isLoading: boolean; isAuthorized: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; 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; isLoading: boolean; isAuthorized: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10433,"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; isLoading: boolean; isAuthorized: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; 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; isLoading: boolean; isAuthorized: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4100,[{"start":4046,"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; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is missing the following properties from type 'KeyResponse': key_type, 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; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}},{"start":6416,"length":47,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":6903,"length":47,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":8038,"length":55,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userRole: string; accessToken: string; userId: 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":8468,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":9841,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10515,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":10960,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":11621,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":12378,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":13010,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":14297,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":15073,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":15868,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":16630,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":17972,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":19102,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":22855,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":24098,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":24544,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":25000,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":25484,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":26592,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":27013,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":27644,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":28274,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":28857,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":30053,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":30808,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":31694,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":32555,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":33756,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":37651,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}},{"start":45209,"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","category":1,"code":2739}]}}]],[4154,[{"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; userRoleLabel: string; isViewOnly: boolean; 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; userRoleLabel: string; isViewOnly: boolean; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized, userRoleLabel, isViewOnly","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":[4156,2683,2685,2687,2689,2691,2693,2676,2867,2858,1314,1313,1312,2864,2857,2855,2866,2856,2865,2861,2860,2859,1245,2862,2895,2893,2894,2812,2911,2912,2901,2913,2899,1315,2914,2903,1317,1316,2898,2915,2916,2904,1320,1319,2917,2902,2896,2909,2907,2910,2906,2905,2897,2900,2908,2918,2851,2919,2924,2921,2920,2923,2932,2925,2933,2929,1322,1321,2931,2927,2926,1323,2934,2928,2930,2949,2946,2950,2936,2939,2938,1324,1326,1325,2952,2953,2940,2951,2937,1327,2942,2941,2954,2943,1329,1328,2955,2956,2944,1087,2948,2945,2935,2947,2719,1331,1330,3051,2831,3052,3047,1334,1333,3053,3054,3049,1336,1335,3055,3048,3056,2959,3057,2829,2828,3058,2830,3059,2958,1344,1343,3060,3061,1342,1346,1345,3050,3063,1358,3064,3065,1356,3066,3067,1378,3068,1373,1379,3071,1367,3072,1365,3073,1364,1403,1363,1359,1404,1366,3069,1355,1380,1374,3070,1357,1349,1400,1377,1401,1375,1402,1376,3062,3149,3136,3151,3150,3152,3142,3154,3145,3155,3144,3153,3140,3156,3143,3148,3147,3112,3113,3090,1409,3093,3124,3081,3079,3125,3082,3126,3094,3127,3095,3128,3129,3075,3130,3076,3078,3131,3074,3077,3132,1697,3133,3080,3134,1410,1411,3114,3102,3115,3100,1405,1408,1407,3116,3101,3117,3118,3096,3119,1406,3084,3120,3085,3121,3092,3083,3109,3104,3091,3106,3098,3107,3099,3108,3097,3086,3122,3087,3123,3088,3110,3111,3103,3135,3089,3105,1422,1423,1421,1424,1425,1427,1426,1428,1114,1429,1431,1430,1455,1457,1456,1459,1458,1461,1460,1463,1462,1466,1465,1467,1107,3158,1454,1468,1469,1470,1471,1473,1472,1474,1475,1476,1478,1477,1480,1479,1481,1129,1483,1482,1484,1220,1486,1485,1488,1489,1487,1490,1492,1491,1239,1493,1494,1495,1496,1498,1497,1500,1499,1502,1501,1503,1504,1506,1505,1507,1509,1508,1510,1189,1512,1511,1513,1218,1516,1515,1518,1517,1520,1519,1521,1514,1523,1522,1525,1524,1527,1526,1222,1529,1528,1530,1532,1534,1533,1536,1535,1538,1537,1540,1539,1542,1541,1543,1545,1544,1547,1546,1548,1217,1549,1108,1552,1551,1553,1550,1555,1554,1412,1413,1109,1414,1231,1232,1415,1229,1416,1233,1417,1418,1219,1223,1557,1556,1559,1558,1561,1560,3157,1420,2815,2813,2811,1244,1243,3172,3192,3197,3240,3241,3218,1563,1562,1566,1565,3203,1568,1569,1567,3242,3215,3206,1571,1570,3219,3238,3258,3220,3259,3208,3260,3229,3261,3207,3262,3223,3263,3264,3222,3265,3224,3266,3232,3267,3209,3268,3237,1573,1572,3257,1574,3245,3243,3214,3244,3228,3246,3211,3247,3221,3248,3193,3194,3250,3196,3249,3195,1576,1575,3251,3201,3198,3213,3252,3212,3253,3204,3210,1654,3199,3205,3233,1656,1655,3254,3234,3255,3202,3200,3256,3231,3269,1564,3239,3277,3270,3278,3271,3279,3273,3272,3280,3274,3276,3275,3298,3368,3367,1666,1665,3375,3325,3376,3324,1670,1669,3379,3332,3331,3330,1672,1671,3377,3363,3323,3378,3371,1663,1662,3374,3373,3380,3369,3381,3342,3326,3382,3333,3383,3362,3347,3366,3364,3358,3372,1664,1674,1673,1216,3388,3386,3387,3401,3399,3402,3398,3397,3393,3392,3400,2853,2852,3508,3530,3500,3531,3522,3532,3509,3533,3501,1676,3510,3502,3534,3503,3535,3517,3536,3521,3537,3511,3504,3538,3505,3539,3506,3540,3507,3541,3520,3515,3518,3514,3516,3519,1678,1677,3542,3527,3543,3525,3544,3523,3545,3526,3547,3546,3548,3524,1681,1680,3404,1686,1685,1688,3424,3549,3492,3550,3493,3551,3494,3552,3495,1679,3496,3497,3499,3529,3528,3573,3563,3574,3557,3575,3568,3571,3560,3559,1691,1690,3576,3566,3577,3558,3578,3561,3579,3569,3580,3555,3581,3556,3582,3565,3583,3564,3572,3554,3553,1693,1692,3584,3567,3562,3570,3595,3590,3596,3589,3597,3588,3587,3600,3601,3585,3602,3603,3586,3604,1973,1694,1975,1974,3598,3593,3599,3592,3591,3594,3633,3610,3634,3630,3629,3646,3619,3651,3624,3647,3620,3648,3623,3649,3621,1979,1980,3650,3618,3622,3638,3616,3626,3628,3639,3613,3640,3608,3641,3612,3642,3617,3643,3625,3644,3614,1976,1978,1977,3645,3627,3635,3609,3605,3632,3607,3606,3636,3611,3637,3615,3631,3653,3046,3652,3663,3664,3655,3661,3665,3654,1983,1982,3669,3670,3660,3666,3657,3656,3667,3658,3668,3659,1981,3662,3678,3671,3676,3674,3677,3673,3672,3675,3688,3682,3686,3683,3687,3679,3685,3681,3680,3684,3696,3703,3706,3705,3704,3709,3708,3707,3732,3716,3733,3717,3734,3718,3731,3719,3735,3723,1987,1989,1988,3736,3724,1992,1991,3722,3737,3721,1985,1984,3720,3729,3725,3730,3727,3738,3726,1993,1341,3728,3749,3740,3752,3742,1996,1995,1997,1994,3747,3750,3739,3751,3746,3754,3755,3745,3753,3744,3743,3748,3769,3770,3765,3771,3763,3762,3779,3767,1210,3772,1209,1208,3773,3764,3774,3766,3780,3781,3761,3775,3776,3759,3777,3758,3757,3778,3760,3768,3785,3784,3783,3782,3793,3795,3799,3787,3786,3801,3791,3790,3803,3805,3804,3807,3806,2703,3809,3810,3808,3811,3812,3813,3814,3816,3815,3820,3819,3821,3822,3818,3823,3817,3824,3844,3711,2085,1104,3952,3329,3340,3942,3341,3954,3334,1095,3955,3300,1667,2041,2040,3943,3328,2044,2043,2046,2045,2047,1136,2042,1130,3956,3304,1121,1119,3944,1112,2048,1111,1118,1120,2049,1093,2050,1134,3945,1132,1131,3957,3335,1122,1127,3327,3958,3337,2051,1116,3946,1117,1135,3959,3336,1100,3960,3338,2086,1096,3947,1113,3961,3339,1123,3948,2087,3949,1128,3950,1124,2052,1133,2053,1125,1098,3951,1126,1097,1099,3845,3353,3962,1698,1318,3867,3281,3873,3282,3874,3284,3875,3286,3868,3283,3869,3297,3870,3287,3293,3871,3291,3872,3290,3162,3963,3161,3825,1230,3846,3741,1936,3788,2062,3964,2061,3965,3797,3966,3798,2060,3792,3967,3800,3968,3796,3969,3789,3794,2054,3802,2063,2055,3970,3295,3512,1675,3971,3513,3972,1683,1684,2065,2064,3294,3292,640,3847,3712,3876,3168,3877,3878,3165,3879,3163,3164,3880,3167,2004,2003,3881,3882,3166,1464,1372,1699,2833,1700,1085,3973,2818,3974,2834,3995,3389,3996,3390,3997,3391,2066,3998,3288,3999,3289,3975,1701,3976,2819,3977,2718,3318,3978,3311,3979,1934,3980,1933,3981,1084,3983,3982,3984,1952,3985,3352,1935,3351,3986,1939,1953,3987,1940,3988,1950,2068,2067,3989,2835,3991,1350,3992,1951,3993,2826,3994,3308,3990,2827,2868,3826,1959,3827,2715,3828,2720,3883,3175,3884,3174,3173,3885,3178,3886,3177,3176,3829,2922,2089,2090,2088,4000,2091,2092,638,3848,3159,3887,2012,3888,2008,3889,2009,3890,2010,2014,2007,3891,2013,2015,2011,4001,2843,2093,3830,3312,3137,3141,3892,3138,2016,3139,2018,2017,3831,1368,3849,2292,1998,1197,4002,1960,1961,4005,1089,2096,2095,1215,4003,2094,1046,2098,2097,4004,1962,2100,2099,2102,2101,3850,3348,3851,1242,3832,2722,4006,3403,1687,4007,1090,2104,2103,4008,3498,3852,2836,2105,1966,2106,4009,1963,4010,1967,4011,3230,1088,1965,4012,3422,4013,1086,2108,2107,4014,3343,4015,3346,4016,3345,3344,4017,3301,4018,3361,4019,3360,3359,4020,3322,2109,3285,3365,3853,3307,3305,3893,2854,2020,2019,4021,3299,4022,1354,4023,3038,3160,3854,2717,3895,2706,3896,2709,3897,2707,2021,1234,2022,3898,2710,3899,2716,3894,2712,3900,2714,1999,1214,3833,2721,1047,2840,3855,1958,4026,4027,1972,2110,1970,4024,4025,2841,2112,2111,2113,1971,2116,2115,4029,3395,2118,2117,4030,3394,2114,4028,3396,2000,2002,2001,3856,3354,3901,3356,3355,3902,3357,3857,3714,4031,2839,2121,2120,4032,2838,2837,4033,2844,1689,3858,3370,3859,1352,3860,3296,2123,2122,2126,2125,2124,3861,3349,3862,3350,4039,2960,4034,1941,4035,1942,4036,1945,4037,1943,4038,1944,4042,3045,4040,3044,2128,2127,4041,3043,3042,3041,2129,1531,3834,2869,4043,3313,3863,3171,2023,3903,2886,2884,3904,2885,2024,3905,2887,3906,2889,3907,2888,3908,2870,3909,3226,3910,3225,2026,2025,3911,3227,2028,2027,3912,2890,2029,2030,3918,2873,3919,2872,3920,2874,3921,3922,2875,3913,2876,3914,2877,3915,2880,3916,2878,3917,2879,2032,2031,3923,2881,3924,2882,3925,2883,3926,3170,3169,2033,3927,1949,3928,1946,3929,3039,1947,3931,3040,3930,1948,3146,4059,2821,4044,1956,4045,2816,4060,3713,4068,2277,4069,2278,4070,2279,4071,2276,2130,4072,2280,2282,4073,2281,4046,1235,4047,1968,1176,4061,4062,1180,4063,1182,4064,1179,4065,1184,4066,1187,4067,1186,1185,1188,1175,2006,4048,1195,2842,4074,1954,3309,3303,4049,1200,4050,1201,4051,1091,1937,4052,2814,4053,2825,4054,1696,4055,2957,2871,2823,4056,1041,4057,1226,2822,4075,1190,1191,4076,1192,4077,1194,4078,1196,1204,4079,1198,4080,1199,4081,1202,4082,1203,4058,2705,1695,3932,1238,3836,1241,3835,2891,4083,3423,637,4084,3691,3690,3689,2846,4085,4086,3314,3310,4087,1938,4092,3316,2284,2283,4088,3317,4093,3315,2286,2285,4089,3321,4090,3319,2287,2119,4091,3320,2288,3838,3695,2035,2034,3933,3694,3693,3837,3692,2290,2289,4098,2847,4099,4100,2848,4094,2832,2291,2294,2293,2845,4095,2820,4096,4097,2824,3934,2713,3839,3698,3935,3697,3936,3701,2036,3937,3700,3938,3699,3840,3702,4101,1347,1955,4102,1236,4103,1115,4104,2704,2708,4105,1035,1092,4106,2275,1183,1101,1040,1110,1362,4107,1044,2817,1038,1036,1042,1237,1102,4108,1225,4109,1045,1043,1181,1177,1103,2702,1094,1178,1348,1037,4110,1048,4111,1361,3841,2892,3842,3864,3302,3940,3385,3939,3710,2037,1986,1332,2039,2038,3865,3715,3843,2810,1138,4112,1969,3866,3756,4125,3181,4113,3182,4114,3180,3179,2301,4115,2313,2295,4116,2312,2311,2299,4126,2298,2309,2308,4127,2310,4128,2307,4121,4122,3191,4123,3183,2296,4129,2302,4130,2332,2300,4131,2305,4132,2335,2342,4133,2336,4134,2319,4135,2340,4136,2341,4137,2337,2329,2330,4138,2339,4139,2338,4140,1211,4141,2331,2304,4142,2334,4143,2333,4144,2316,4145,2315,2306,2343,2320,4124,3184,3185,4117,3186,4118,3190,3189,4119,3188,2323,2328,2324,2325,2326,4146,2327,2321,2344,2322,4120,3187,2297,2314,3306,3384,2849,3941,2850,2700,2056,4147,2711,2701,1224,2350,2348,2347,2349,2346,2345,2351,4151,2354,1360,4148,3216,4149,1371,3217,4150,3235,3236,2352,2353,2355,1351,2357,1137,2358,1034,2362,2361,2364,2363,4152,2084,2365,2366,1340,2367,630,2368,1212,2369,1213,2370,2359,1353,1205,2360,631,614,2371,2372,1227,2373,634,2374,1193,1453,2375,1207,2376,2378,2377,2379,636,2639,2638,2641,2640,2642,1240,2643,1228,2644,2646,2645,2647,633,2648,1964,2649,1668,2650,2651,1682,2652,1221,2655,2654,2658,2657,2659,2656,2660,1105,2661,1106,2663,2662,632,2664,1419,2665,2005,2666,1083,2667,1206,4153,2684,2686,2688,2690,2692,2694,2672,2673,2675,2677,2356,2695,3953,2678,2682,2863,4154,613],"version":"5.9.3"} \ No newline at end of file From 6a950df5498e1d40ec086e6f0ef2bb35b85e9128 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 12:37:34 -0700 Subject: [PATCH 038/105] fix(ui): put the non-reasoning switch above the tier rows The switch adds a row at the top of the tier list, so sitting below Reasoning put the control and the thing it changes at opposite ends of the card. It now heads the list, with a separator between it and the first row. --- .../src/components/add_model/ComplexityRouterConfig.tsx | 8 ++++---- .../src/components/add_model/NonReasoningTierToggle.tsx | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index 540b68cf9f7..c89c130af86 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -659,6 +659,10 @@ const ComplexityRouterConfig: React.FC = ({ + {!customTierSet && ( + + )} + {tierRows.map((row, index) => { const tierInfo = builtInTierInfo(row.id); const label = tierRowLabel(row, value.tier_labels); @@ -739,10 +743,6 @@ const ComplexityRouterConfig: React.FC = ({ ); })} - {!customTierSet && ( - - )} - -
+
Add a non-reasoning tier
- + Adds NON_REASONING below Simple, for operational agent traffic that relays or reformats information rather than reasoning about it. Escalation still moves up out of it when a request needs more. {!available && " Requires the LLM classification method."} + ); }; From cf0488316e23b3f9859691d77043e49eb97f6eb6 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:40:28 -0700 Subject: [PATCH 039/105] fix(spend-tracking): take each unresolved key's newest spend row and drop docstrings --- .../common_daily_activity.py | 4 +- .../spend_tracking/key_metadata_recovery.py | 42 +++++++------------ .../test_common_daily_activity.py | 5 --- .../test_key_metadata_recovery.py | 5 --- 4 files changed, 16 insertions(+), 40 deletions(-) diff --git a/litellm/proxy/management_endpoints/common_daily_activity.py b/litellm/proxy/management_endpoints/common_daily_activity.py index c6c5aad5926..a8aef30107c 100644 --- a/litellm/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/proxy/management_endpoints/common_daily_activity.py @@ -462,9 +462,7 @@ async def get_api_key_metadata( This ensures that key_alias and team_id are preserved in historical activity logs even after a key is deleted or regenerated. Also recovers aliases for api_key - values that were double-hashed by the v1.99 spend-log provenance gate, and, when - spend_logs_window is given, for keys never written to either token table (CLI - session tokens) from the spend-log rows those requests wrote in that window. + values that were double-hashed by the v1.99 spend-log provenance gate. """ key_records: Sequence[PrismaVerificationToken] = await VerificationTokenRepository(prisma_client).table.find_many( where={"token": {"in": list(api_keys)}} diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index 81c1f4cf119..e207f226255 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -29,16 +29,21 @@ ORDER BY token, deleted_at DESC """ _SPEND_LOG_ALIAS_SQL: Final = """ -SELECT DISTINCT ON (api_key) - api_key AS digest, - metadata->>'user_api_key_alias' AS key_alias, - COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, - COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id -FROM "LiteLLM_SpendLogs" -WHERE api_key = ANY($1::text[]) - AND "startTime" >= $2::timestamp - AND "startTime" < $3::timestamp -ORDER BY api_key, (metadata->>'user_api_key_alias') IS NULL, "startTime" DESC +SELECT newest.digest, newest.key_alias, newest.team_id, newest.user_id +FROM unnest($1::text[]) AS missing(digest) +CROSS JOIN LATERAL ( + SELECT + api_key AS digest, + metadata->>'user_api_key_alias' AS key_alias, + COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, + COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id + FROM "LiteLLM_SpendLogs" + WHERE api_key = missing.digest + AND "startTime" >= $2::timestamp + AND "startTime" < $3::timestamp + ORDER BY "startTime" DESC + LIMIT 1 +) AS newest """ @@ -152,14 +157,6 @@ async def recover_double_hashed_key_metadata( prisma_client: PrismaClient, missing_keys: AbstractSet[str], ) -> Mapping[str, KeyMetadataDict]: - """ - Recover key_alias/team_id/user_id for DailyUserSpend.api_key values that - were double-hashed by the v1.99 spend-log provenance gate. - - Those rows store hash(VerificationToken.token) instead of the token, so the - exact join misses. Postgres hashes the token column itself, one pass over - active keys and one over deleted keys, so no key row crosses the wire. - """ sha_missing: Final = frozenset(key for key in missing_keys if is_valid_sha256_hash(key)) if not sha_missing: return _EMPTY_KEY_METADATA @@ -187,15 +184,6 @@ async def recover_key_metadata_from_spend_logs( missing_keys: AbstractSet[str], window: tuple[datetime, datetime], ) -> Mapping[str, KeyMetadataDict]: - """ - Recover key_alias/team_id/user_id for hashed api_key values absent from both - verification-token tables, e.g. in-memory CLI session tokens that never get a - token row. Their owner is written to LiteLLM_SpendLogs metadata at request - time under the same hashed api_key, so it is the only surviving source. Only - sha256 digests are looked up, matching the reverse-hash recovery gate, since - every current api_key value in spend logs is a token hash. The [start, end) - bound keeps the lookup on the startTime index instead of scanning the table. - """ sha_missing: Final = frozenset(key for key in missing_keys if is_valid_sha256_hash(key)) if not sha_missing: return _EMPTY_KEY_METADATA diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index 4d3dc320d8f..4c8be415204 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -2109,11 +2109,6 @@ async def test_get_daily_activity_aggregated_with_entity_breakdown(): @pytest.mark.asyncio async def test_get_api_key_metadata_resolves_session_key_via_spend_log_window(): - """ - A CLI session token has no verification-token row, so the active/deleted lookups - and reverse-hash all miss. Given a spend-log window, its alias and owner are - recovered from the spend-log metadata and its email is filled from the user table. - """ from litellm.proxy.utils import hash_token session_digest = hash_token("cli-session-user-42") diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 9e42f017106..05a90137e3b 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -233,11 +233,6 @@ async def test_fill_missing_api_key_aliases_skips_named_keys_that_have_no_email( @pytest.mark.asyncio async def test_recover_key_metadata_from_spend_logs_resolves_session_token_from_metadata(): - """ - CLI session tokens never get a verification-token row, so both the exact join and - the reverse-hash lookup miss them. Their owner survives only in the spend-log - metadata written at request time, keyed by the same hashed api_key. - """ session_digest = hash_token("cli-session-repro-user-6852") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() From 61ab4307ecfac49aad12feffcba265a21a270e6e Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:47:31 -0700 Subject: [PATCH 040/105] test(cost_map): assert provenance without patching module state --- .../test_get_model_cost_map.py | 5 +-- .../test_routes_model_cost_map.py | 37 ++++++------------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index f72d175d579..0495440c51c 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -310,14 +310,13 @@ def test_openrouter_catalog_costs_match_live_headline_rates(cost_map: dict): assert entry["output_cost_per_token"] != stale_out, model -def test_get_model_cost_map_stamps_loaded_at(monkeypatch): +def test_get_model_cost_map_stamps_loaded_at(): """The load time feeds each pod's reload-due decision; a load that does not stamp it would make manual reload requests race the proxy's startup""" from datetime import datetime, timezone from litellm.litellm_core_utils import get_model_cost_map as module - monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) client, _calls = _mock_client( [httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client ) @@ -560,7 +559,6 @@ async def test_refetch_stamps_loaded_at_on_remote_and_local_reloads(monkeypatch) from litellm.litellm_core_utils import get_model_cost_map as module client, _ = _mock_client([httpx.Response(200, content=_real_map_bytes())]) - monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) before_remote = datetime.now(timezone.utc) await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) remote_loaded_at = module.get_model_cost_map_loaded_at() @@ -568,7 +566,6 @@ async def test_refetch_stamps_loaded_at_on_remote_and_local_reloads(monkeypatch) assert before_remote <= remote_loaded_at <= datetime.now(timezone.utc) monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") - monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) before_local = datetime.now(timezone.utc) await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0)) local_loaded_at = module.get_model_cost_map_loaded_at() diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py index 36c364fb82b..40bd66ea91c 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py @@ -15,16 +15,15 @@ from pathlib import Path from unittest.mock import AsyncMock, MagicMock +from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map_provenance + from .conftest import VOLATILE_KEYS, normalize # Some response bodies include a "timestamp" — extend the volatile set so # dict-equality assertions remain stable. _VOLATILE = VOLATILE_KEYS | frozenset({"timestamp"}) -_PROVENANCE = { - "source_revision": "0123456789abcdef0123456789abcdef01234567", - "etag": 'W/"cost-map-etag"', -} +_SERVED_ETAG = 'W/"cost-map-etag"' _ROOT_COST_MAP = Path(__file__).resolve().parents[4] / "model_prices_and_context_window.json" @@ -49,13 +48,6 @@ def _attach_litellm_config(mock_prisma): return table -def _pin_provenance(monkeypatch): - monkeypatch.setattr( - "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_provenance", - lambda: dict(_PROVENANCE), - ) - - # --------------------------------------------------------------------------- # POST /reload/model_cost_map # --------------------------------------------------------------------------- @@ -69,7 +61,6 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): table = _attach_litellm_config(mock_prisma) monkeypatch.setattr(ps, "prisma_client", mock_prisma) - _pin_provenance(monkeypatch) fake_cost_map = {"gpt-4": {"input_cost": 0.03}, "gpt-3.5": {"input_cost": 0.002}} monkeypatch.setattr( @@ -98,7 +89,7 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): "status": "success", "models_count": 2, "timestamp": "", - **_PROVENANCE, + **get_model_cost_map_provenance(), } assert table.upsert.await_count == 1 update_payload = table.upsert.await_args.kwargs["data"]["update"] @@ -120,8 +111,8 @@ def test_reload_model_cost_map_surfaces_the_blob_id_of_the_bytes_served_on_every monkeypatch.setattr(ps, "prisma_client", mock_prisma) monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) body = _ROOT_COST_MAP.read_bytes() - expected = {"source_revision": git_blob_id(body), "etag": _PROVENANCE["etag"]} - served = httpx.Response(200, headers={"ETag": _PROVENANCE["etag"]}, content=body) + expected = {"source_revision": git_blob_id(body), "etag": _SERVED_ETAG} + served = httpx.Response(200, headers={"ETag": _SERVED_ETAG}, content=body) monkeypatch.setattr( "litellm.litellm_core_utils.get_model_cost_map._default_reload_client", lambda: httpx.AsyncClient(transport=httpx.MockTransport(lambda request: served)), @@ -339,7 +330,6 @@ def test_get_model_cost_map_reload_status_no_db_not_scheduled( from litellm.proxy._types import LitellmUserRoles monkeypatch.setattr(ps, "prisma_client", None) - _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") assert response.status_code == 200 @@ -348,7 +338,7 @@ def test_get_model_cost_map_reload_status_no_db_not_scheduled( "interval_hours": None, "last_run": None, "next_run": None, - **_PROVENANCE, + **get_model_cost_map_provenance(), } @@ -366,7 +356,6 @@ def test_get_model_cost_map_reload_status_scheduled( config_row.last_run_at = None table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) - _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -376,7 +365,7 @@ def test_get_model_cost_map_reload_status_scheduled( "interval_hours": 12, "last_run": None, "next_run": None, - **_PROVENANCE, + **get_model_cost_map_provenance(), } @@ -396,7 +385,6 @@ def test_get_model_cost_map_reload_status_reports_persisted_last_run( config_row.last_run_at = datetime(2024, 1, 1, 6, 0, 0, tzinfo=timezone.utc) table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) - _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -406,7 +394,7 @@ def test_get_model_cost_map_reload_status_reports_persisted_last_run( "interval_hours": 6, "last_run": "2024-01-01T06:00:00+00:00", "next_run": "2024-01-01T12:00:00+00:00", - **_PROVENANCE, + **get_model_cost_map_provenance(), } @@ -426,7 +414,6 @@ def test_get_model_cost_map_reload_status_no_config_not_scheduled( config_row.last_run_at = None table.find_unique = AsyncMock(return_value=config_row) monkeypatch.setattr(ps, "prisma_client", mock_prisma) - _pin_provenance(monkeypatch) with auth_as(LitellmUserRoles.PROXY_ADMIN): response = client.get("/schedule/model_cost_map_reload/status") @@ -436,7 +423,7 @@ def test_get_model_cost_map_reload_status_no_config_not_scheduled( "interval_hours": None, "last_run": None, "next_run": None, - **_PROVENANCE, + **get_model_cost_map_provenance(), } @@ -464,7 +451,7 @@ def test_get_model_cost_map_source_happy(client, auth_as, monkeypatch): "is_env_forced": False, "fallback_reason": None, "loaded_at": "2026-09-07T01:02:03+00:00", - **_PROVENANCE, + **get_model_cost_map_provenance(), } monkeypatch.setattr( "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_source_info", @@ -481,7 +468,7 @@ def test_get_model_cost_map_source_happy(client, auth_as, monkeypatch): "is_env_forced": False, "fallback_reason": None, "loaded_at": "2026-09-07T01:02:03+00:00", - **_PROVENANCE, + **get_model_cost_map_provenance(), "model_count": 3, } From fbd8fa8d3e0d5ce5081ff248865ff5ff0a22e50a Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 12:59:07 -0700 Subject: [PATCH 041/105] refactor: tighten the comments added for the non-reasoning tier Review flagged the added comments as over-explaining. Cut the call-site comment that restated the helper's own docstring, and shortened the rest to the fact the code cannot state itself: why the constant excludes the tier, why the flag is cleared on a classifier change, and why the edit modal reads both keys back. --- litellm/router_strategy/complexity_router/config.py | 8 +++----- .../components/add_model/ClassificationMethodConfig.tsx | 9 +++------ .../src/components/add_model/NonReasoningTierToggle.tsx | 3 +-- .../edit_auto_router/edit_auto_router_modal.tsx | 6 ++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index d917c1b8041..1d03c56050a 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -56,11 +56,9 @@ DEFAULT_CLASSIFICATION_RUBRIC: Final[ClassificationRubric] = ClassificationRubri LLM_CLASSIFIER_TYPES: Final[frozenset[str]] = frozenset({"llm", "heuristic_first", "hybrid"}) -# The ladder as it has always shipped. NON_REASONING is absent because it is opt-in: an existing -# router must not gain a rubric bullet, a wire label, or a rung it never configured, and the -# heuristic_v2 artifact is trained on exactly these four classes. Read the active ladder off the -# config (`tier_names`, `active_tier_severity_order`) rather than this constant wherever the -# operator's `enable_non_reasoning_tier` can reach. +# Excludes NON_REASONING so an existing router keeps the ladder, rubric and wire labels it already +# has, and so heuristic_v2 keeps mapping onto the four classes its artifact is trained on. Anywhere +# `enable_non_reasoning_tier` can reach, read the ladder off the config instead. TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( ComplexityTier.SIMPLE, ComplexityTier.MEDIUM, diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 468dcf5b411..5ec9f72c2a1 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -237,9 +237,9 @@ const ClassifierTypeRadios: React.FC<{ }; /** - * The NON_REASONING keys a classifier switch should carry forward, or clear. Leaving the flag set - * under a classifier that cannot emit the tier produces a config the backend refuses on save, and - * the switch is disabled there, so the operator would have no way to undo it. + * The NON_REASONING keys a classifier switch carries forward, or clears. Only the LLM classifier + * can emit the tier, and the switch is disabled elsewhere, so a flag left set under another + * classifier would be an unsaveable config the operator could not undo. */ export const nonReasoningTierFields = ( classifierType: ClassifierType, @@ -303,9 +303,6 @@ const ClassificationMethodConfig: React.FC = ({ : undefined, hybrid_boundary_margin: classifierType === "hybrid" ? value.hybrid_boundary_margin ?? DEFAULT_HYBRID_BOUNDARY_MARGIN : undefined, - // Only the LLM classifier can produce NON_REASONING, and the backend rejects the flag - // beside any other type. Clearing it here (with the tier's own pool) is what keeps a - // switch away from LLM from stranding a config that can never be saved. ...nonReasoningTierFields(classifierType, value), }; onChange(nextValue); diff --git a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx index c0d26874357..622a460fb79 100644 --- a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx @@ -15,8 +15,7 @@ const NonReasoningTierToggle: React.FC<{ onChange: (value: ComplexityRouterConfigValue) => void; available: boolean; }> = ({ value, onChange, available }) => { - // Turning it off drops the tier's key rather than leaving an empty pool, which the backend - // rejects; turning it back on restores whatever pool the form still held. + // Off drops the tier's key rather than leaving the empty pool the backend rejects. const handleToggle = (enabled: boolean): void => { const { NON_REASONING: existingPool, ...keptTiers } = value.tiers; const next: ComplexityRouterConfigValue = { diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx index 283fe978790..1ef713cea02 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx @@ -136,10 +136,8 @@ export const hydrateComplexityRouterConfig = ( parsedConfig: StoredComplexityRouterConfig, complexityRouterDefaultModel: string | null | undefined, ): ComplexityRouterConfigValue => { - // `tiers` is rewritten wholesale on save, so a stored tier this misses is deleted from the - // router by any edit at all, including one made for an unrelated reason. NON_REASONING is - // therefore read back from the stored config rather than assumed absent, and the toggle follows - // what is actually stored so the round-trip cannot silently turn the tier off. + // `tiers` is rewritten wholesale on save, so a stored tier this misses is deleted by any edit, + // including one made for an unrelated reason. Hence reading both back rather than assuming four. const storedNonReasoning: string[] = normalizeTierModels(parsedConfig.tiers?.NON_REASONING); const enable_non_reasoning_tier: boolean = parsedConfig.enable_non_reasoning_tier === true || storedNonReasoning.length > 0; From 550e733bf35ac862970cfebeed872e45f539bc7d Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:09:43 -0700 Subject: [PATCH 042/105] perf(spend-tracking): recover key identity in one bounded spend-log pass with a per-worker cache The spend-log lookup for permanently unresolvable digests is back to a single DISTINCT ON scan over the requested window, keeping only rows that carry an alias, user, or team so a newer nameless row cannot hide an older named one. Results and misses are cached per worker for ten minutes keyed by digest and window, failed queries are not cached, and JWT rows keyed hashed-jwt- now pass the digest gate. Tests cover the JWT gate, cache reuse and partial misses, window changes, error handling, the with-window guard, and the daily activity wiring --- litellm/constants.py | 2 + .../spend_tracking/key_metadata_recovery.py | 105 +++++++++++++----- .../test_common_daily_activity.py | 74 +++++++++++- .../test_key_metadata_recovery.py | 99 ++++++++++++++++- 4 files changed, 248 insertions(+), 32 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 8ef9523a60a..c1896573eb5 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1763,6 +1763,8 @@ LITELLM_SETTINGS_SAFE_DB_OVERRIDES: Final = [ SPECIAL_LITELLM_AUTH_TOKEN: Final = ["ui-token"] DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL = int(os.getenv("DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL", 60)) DEFAULT_ACCESS_GROUP_CACHE_TTL: Final = int(os.getenv("DEFAULT_ACCESS_GROUP_CACHE_TTL", 600)) +SPEND_LOG_KEY_METADATA_CACHE_TTL: Final = int(os.getenv("SPEND_LOG_KEY_METADATA_CACHE_TTL", "600")) +SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS: Final = int(os.getenv("SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS", "10000")) # Short TTL for negative MCP access-group existence lookups. Keeps unauthenticated # callers from forcing a DB query per request for unknown names, while bounding # staleness so a transient DB error (which surfaces as an empty list) cannot diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index e207f226255..7212d8b6acb 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -8,6 +8,8 @@ from pydantic import BaseModel, TypeAdapter from typing_extensions import ReadOnly, TypedDict from litellm._logging import verbose_proxy_logger +from litellm.caching.in_memory_cache import InMemoryCache +from litellm.constants import SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, SPEND_LOG_KEY_METADATA_CACHE_TTL from litellm.litellm_core_utils.litellm_logging import is_valid_sha256_hash from litellm.proxy.utils import PrismaClient from litellm.repositories.user_repository import UserRepository @@ -29,23 +31,27 @@ ORDER BY token, deleted_at DESC """ _SPEND_LOG_ALIAS_SQL: Final = """ -SELECT newest.digest, newest.key_alias, newest.team_id, newest.user_id -FROM unnest($1::text[]) AS missing(digest) -CROSS JOIN LATERAL ( - SELECT - api_key AS digest, - metadata->>'user_api_key_alias' AS key_alias, - COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, - COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id - FROM "LiteLLM_SpendLogs" - WHERE api_key = missing.digest - AND "startTime" >= $2::timestamp - AND "startTime" < $3::timestamp - ORDER BY "startTime" DESC - LIMIT 1 -) AS newest +SELECT DISTINCT ON (api_key) + api_key AS digest, + metadata->>'user_api_key_alias' AS key_alias, + COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, + COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id +FROM "LiteLLM_SpendLogs" +WHERE api_key = ANY($1::text[]) + AND "startTime" >= $2::timestamp + AND "startTime" < $3::timestamp + AND COALESCE( + metadata->>'user_api_key_alias', + NULLIF("user", ''), + metadata->>'user_api_key_user_id', + NULLIF(team_id, ''), + metadata->>'user_api_key_team_id' + ) IS NOT NULL +ORDER BY api_key, "startTime" DESC """ +_HASHED_JWT_PREFIX: Final = "hashed-jwt-" + class KeyMetadataDict(TypedDict, total=False): key_alias: ReadOnly[str | None] @@ -62,6 +68,11 @@ class _TokenDigestRow(BaseModel): _TOKEN_DIGEST_ROWS: Final = TypeAdapter(tuple[_TokenDigestRow, ...]) +_CACHED_KEY_METADATA: Final = TypeAdapter(KeyMetadataDict) +_SPEND_LOG_METADATA_CACHE: Final = InMemoryCache( + max_size_in_memory=SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, + default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL, +) _EMPTY_KEY_METADATA: Final[Mapping[str, KeyMetadataDict]] = MappingProxyType({}) _EMPTY_EMAILS: Final[Mapping[str, str]] = MappingProxyType({}) @@ -179,31 +190,73 @@ async def recover_double_hashed_key_metadata( return MappingProxyType({**from_active, **from_deleted}) -async def recover_key_metadata_from_spend_logs( - prisma_client: PrismaClient, - missing_keys: AbstractSet[str], +def _is_spend_log_digest(key: str) -> bool: + return is_valid_sha256_hash(key.removeprefix(_HASHED_JWT_PREFIX)) + + +def _spend_log_cache_key(digest: str, window: tuple[datetime, datetime]) -> str: + start, end = window + return f"spend_log_key_metadata:{digest}:{start.isoformat()}:{end.isoformat()}" + + +def _cached_spend_log_metadata( + cache: InMemoryCache, + digest: str, window: tuple[datetime, datetime], -) -> Mapping[str, KeyMetadataDict]: - sha_missing: Final = frozenset(key for key in missing_keys if is_valid_sha256_hash(key)) - if not sha_missing: - return _EMPTY_KEY_METADATA +) -> KeyMetadataDict | None: + cached: Final[object] = cache.get_cache(_spend_log_cache_key(digest, window)) + return None if cached is None else _CACHED_KEY_METADATA.validate_python(cached) + + +async def _query_spend_log_metadata( + prisma_client: PrismaClient, + digests: AbstractSet[str], + window: tuple[datetime, datetime], +) -> Mapping[str, KeyMetadataDict] | None: start, end = window rows: Final = await _db_or_empty( - lambda: prisma_client.db.query_raw(_SPEND_LOG_ALIAS_SQL, sorted(sha_missing), start, end), + lambda: prisma_client.db.query_raw(_SPEND_LOG_ALIAS_SQL, sorted(digests), start, end), "Failed spend-log alias recovery for %d missing keys: %s", - len(sha_missing), + len(digests), ) if rows is None: - return _EMPTY_KEY_METADATA + return None return MappingProxyType( { row.digest: KeyMetadataDict(key_alias=row.key_alias, team_id=row.team_id, user_id=row.user_id) for row in _TOKEN_DIGEST_ROWS.validate_python(rows) - if row.digest in sha_missing and (row.key_alias or row.user_id or row.team_id) + if row.digest in digests and (row.key_alias or row.user_id or row.team_id) } ) +async def recover_key_metadata_from_spend_logs( + prisma_client: PrismaClient, + missing_keys: AbstractSet[str], + window: tuple[datetime, datetime], + cache: InMemoryCache = _SPEND_LOG_METADATA_CACHE, +) -> Mapping[str, KeyMetadataDict]: + digests: Final = frozenset(key for key in missing_keys if _is_spend_log_digest(key)) + if not digests: + return _EMPTY_KEY_METADATA + cached: Final = MappingProxyType( + { + digest: meta + for digest in digests + for meta in (_cached_spend_log_metadata(cache, digest, window),) + if meta is not None + } + ) + uncached: Final = digests - frozenset(cached) + fresh: Final = await _query_spend_log_metadata(prisma_client, uncached, window) if uncached else _EMPTY_KEY_METADATA + if fresh is not None: + for digest in uncached: + cache.set_cache(_spend_log_cache_key(digest, window), fresh.get(digest, KeyMetadataDict())) + return MappingProxyType( + {digest: meta for digest, meta in (*cached.items(), *(fresh or _EMPTY_KEY_METADATA).items()) if meta} + ) + + def _row_with_recovered_fields( row: Mapping[str, object], recovered: Mapping[str, KeyMetadataDict], diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index 4c8be415204..d214ab6b5ac 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -492,7 +492,7 @@ async def test_get_api_key_metadata_recovers_double_hashed_key_via_reverse_hash( @pytest.mark.asyncio async def test_get_api_key_metadata_permanent_miss_never_pages_tokens_or_reads_spend_logs(): - """A dirty key no table can explain costs two digest lookups, never a token page walk or a SpendLogs scan.""" + """Without a spend-log window a dirty key no table can explain costs two digest lookups and never a token page walk.""" from litellm.proxy.utils import hash_token double_hashed = hash_token("b" * 64) @@ -518,6 +518,78 @@ async def test_get_api_key_metadata_permanent_miss_never_pages_tokens_or_reads_s assert all("take" not in call.kwargs and "skip" not in call.kwargs for call in token_lookups) +@pytest.mark.asyncio +async def test_get_api_key_metadata_permanent_miss_with_a_window_reads_spend_logs_once_within_it(): + from litellm.proxy.utils import hash_token + + double_hashed = hash_token("permanent-miss-with-window-6852") + window = (datetime(2024, 1, 1), datetime(2024, 1, 4)) + mock_prisma = MagicMock() + mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_deletedverificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_usertable.find_many = AsyncMock(return_value=[]) + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + + result = await get_api_key_metadata(prisma_client=mock_prisma, api_keys={double_hashed}, spend_logs_window=window) + + assert double_hashed not in result + assert mock_prisma.db.query_raw.await_count == 3 + ((_, digests, start, end),) = [ + call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0] + ] + assert digests == [double_hashed] + assert (start, end) == window + + +@pytest.mark.asyncio +async def test_get_daily_activity_recovers_a_session_key_alias_from_spend_logs_around_the_page_dates(): + from litellm.proxy.utils import hash_token + + session_digest = hash_token("cli-session-daily-activity-6852") + records = [_daily_user_spend_record(user_id="session-user", api_key=session_digest, spend=1.5)] + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_table = MagicMock() + mock_table.count = AsyncMock(return_value=len(records)) + mock_table.find_many = AsyncMock(return_value=records) + mock_prisma.db.litellm_dailyuserspend = mock_table + mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_deletedverificationtoken.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_usertable.find_many = AsyncMock( + return_value=[SimpleNamespace(user_id="session-user", user_email="session@example.com")] + ) + + async def query_raw(sql, *params): + if "LiteLLM_SpendLogs" in sql: + return [{"digest": session_digest, "key_alias": "cli-session-alias", "team_id": None, "user_id": "session-user"}] + return [] + + mock_prisma.db.query_raw = AsyncMock(side_effect=query_raw) + + result = await get_daily_activity( + prisma_client=mock_prisma, + table_name="litellm_dailyuserspend", + entity_id_field="user_id", + entity_id=None, + entity_metadata_field=None, + start_date="2024-01-01", + end_date="2024-01-01", + model=None, + api_key=None, + page=1, + page_size=1000, + ) + + key_metadata = result.results[0].breakdown.api_keys[session_digest].metadata + assert key_metadata.key_alias == "cli-session-alias" + assert key_metadata.user_email == "session@example.com" + ((_, digests, start, end),) = [ + call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0] + ] + assert digests == [session_digest] + assert (start, end) == (datetime(2023, 12, 31), datetime(2024, 1, 3)) + + def test_key_metadata_includes_recovered_user_email(): from litellm.proxy.management_endpoints.common_daily_activity import _key_metadata diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 05a90137e3b..3d7362b9de0 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest from prisma.errors import PrismaError +from litellm.caching.in_memory_cache import InMemoryCache from litellm.proxy.spend_tracking.key_metadata_recovery import ( fill_missing_api_key_aliases, recover_double_hashed_key_metadata, @@ -240,7 +241,7 @@ async def test_recover_key_metadata_from_spend_logs_resolves_session_token_from_ [_digest_row(session_digest, "cli-session-repro-user-6852", None, "repro-user-6852")] ) - result = await recover_key_metadata_from_spend_logs(mock_prisma, {session_digest}, window) + result = await recover_key_metadata_from_spend_logs(mock_prisma, {session_digest}, window, cache=InMemoryCache()) assert result[session_digest]["key_alias"] == "cli-session-repro-user-6852" assert result[session_digest]["user_id"] == "repro-user-6852" @@ -255,7 +256,7 @@ async def test_recover_key_metadata_from_spend_logs_skips_query_when_no_missing_ mock_prisma = MagicMock() mock_prisma.db.query_raw = AsyncMock(return_value=[]) - result = await recover_key_metadata_from_spend_logs(mock_prisma, set(), window) + result = await recover_key_metadata_from_spend_logs(mock_prisma, set(), window, cache=InMemoryCache()) assert result == {} mock_prisma.db.query_raw.assert_not_called() @@ -267,7 +268,9 @@ async def test_recover_key_metadata_from_spend_logs_returns_empty_on_prisma_erro mock_prisma = MagicMock() mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("db down")) - result = await recover_key_metadata_from_spend_logs(mock_prisma, {hash_token("cli-session-x")}, window) + result = await recover_key_metadata_from_spend_logs( + mock_prisma, {hash_token("cli-session-x")}, window, cache=InMemoryCache() + ) assert result == {} @@ -287,7 +290,7 @@ async def test_recover_key_metadata_from_spend_logs_ignores_foreign_and_all_null ] ) - result = await recover_key_metadata_from_spend_logs(mock_prisma, {wanted, all_null}, window) + result = await recover_key_metadata_from_spend_logs(mock_prisma, {wanted, all_null}, window, cache=InMemoryCache()) assert set(result) == {wanted} assert result[wanted]["key_alias"] == "kept-alias" @@ -301,8 +304,94 @@ async def test_recover_key_metadata_from_spend_logs_skips_non_sha256_keys(): mock_prisma.db.query_raw = AsyncMock(return_value=[]) result = await recover_key_metadata_from_spend_logs( - mock_prisma, {"cli-session-raw-1798", "key-hash-short"}, window + mock_prisma, {"cli-session-raw-1798", "key-hash-short"}, window, cache=InMemoryCache() ) assert result == {} mock_prisma.db.query_raw.assert_not_called() + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_accepts_hashed_jwt_digests(): + jwt_digest = f"hashed-jwt-{hash_token('jwt-subject-1')}" + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(jwt_digest, None, "team-jwt", "jwt-user")]) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {jwt_digest}, window, cache=InMemoryCache()) + + assert result[jwt_digest]["team_id"] == "team-jwt" + assert result[jwt_digest]["user_id"] == "jwt-user" + ((_, digests, _, _),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + assert digests == [jwt_digest] + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_serves_repeat_lookups_from_the_cache(): + found = hash_token("cli-session-found") + unknown = hash_token("cli-session-unknown") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache() + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(found, "found-alias", None, "owner-1")]) + + first = await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) + second = await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) + + assert first == second + assert set(first) == {found} + assert first[found]["key_alias"] == "found-alias" + assert mock_prisma.db.query_raw.await_count == 1 + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_only_queries_digests_the_cache_has_not_seen(): + cached_digest = hash_token("cli-session-cached") + new_digest = hash_token("cli-session-new") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache() + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(cached_digest, "cached-alias", None, None)]) + await recover_key_metadata_from_spend_logs(mock_prisma, {cached_digest}, window, cache=cache) + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(new_digest, "new-alias", None, None)]) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {cached_digest, new_digest}, window, cache=cache) + + assert result[cached_digest]["key_alias"] == "cached-alias" + assert result[new_digest]["key_alias"] == "new-alias" + ((_, digests, _, _),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + assert digests == [new_digest] + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_rescans_when_the_window_changes(): + digest = hash_token("cli-session-windowed") + cache = InMemoryCache() + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([]) + await recover_key_metadata_from_spend_logs( + mock_prisma, {digest}, (datetime(2026, 9, 1), datetime(2026, 9, 4)), cache=cache + ) + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(digest, "later-alias", None, None)]) + + result = await recover_key_metadata_from_spend_logs( + mock_prisma, {digest}, (datetime(2026, 9, 7), datetime(2026, 9, 10)), cache=cache + ) + + assert result[digest]["key_alias"] == "later-alias" + assert mock_prisma.db.query_raw.await_count == 1 + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_does_not_cache_a_failed_query(): + digest = hash_token("cli-session-retry") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache() + mock_prisma = MagicMock() + mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("db down")) + assert await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) == {} + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(digest, "back-online", None, None)]) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) + + assert result[digest]["key_alias"] == "back-online" From a5cfe625e395c4edb401cf0d2daec3ccb686ed82 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 13:14:52 -0700 Subject: [PATCH 043/105] refactor: trim the comments this PR added Cuts the explanatory comments and docstrings added here down to one line each, or removes them where the code already says it. Restores the four pre-existing docstrings this PR had reworded to their original text; the one remaining edit to existing text is TierDefinition.description, whose hardcoded tier list would otherwise misstate that a tier named NON_REASONING may also omit its description. --- .../complexity_router/complexity_router.py | 7 +- .../complexity_router/config.py | 20 +--- .../router_strategy/test_complexity_router.py | 91 ++++++------------- .../add_model/ClassificationMethodConfig.tsx | 7 +- .../add_model/ComplexityRouterConfig.tsx | 15 +-- .../add_model/NonReasoningTierToggle.tsx | 6 -- .../build_complexity_router_config.ts | 3 +- .../src/components/add_model/tier_rows.ts | 6 +- 8 files changed, 41 insertions(+), 114 deletions(-) diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index c40405ecf0f..66c328c49ea 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -100,12 +100,7 @@ else: class TierClassification(BaseModel): - """Structured response schema for the LLM-based complexity classifier. - - The four-tier ladder, which is what a router that did not opt into NON_REASONING sends. The - enum actually put on the wire is rebuilt per router from `classifier_wire_labels`, so a - five-tier or renamed ladder widens it there rather than here. - """ + """Structured response schema for the LLM-based complexity classifier.""" tier: Literal["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index 1d03c56050a..9cd4ce1d2fa 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -56,9 +56,6 @@ DEFAULT_CLASSIFICATION_RUBRIC: Final[ClassificationRubric] = ClassificationRubri LLM_CLASSIFIER_TYPES: Final[frozenset[str]] = frozenset({"llm", "heuristic_first", "hybrid"}) -# Excludes NON_REASONING so an existing router keeps the ladder, rubric and wire labels it already -# has, and so heuristic_v2 keeps mapping onto the four classes its artifact is trained on. Anywhere -# `enable_non_reasoning_tier` can reach, read the ladder off the config instead. TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( ComplexityTier.SIMPLE, ComplexityTier.MEDIUM, @@ -73,7 +70,6 @@ NON_REASONING_TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( def tier_severity_order(non_reasoning_enabled: bool) -> tuple[ComplexityTier, ...]: - """The built-in ladder for one router, tier 0 included only when it opted in.""" return NON_REASONING_TIER_SEVERITY_ORDER if non_reasoning_enabled else TIER_SEVERITY_ORDER @@ -1524,8 +1520,7 @@ class ComplexityRouterConfig(BaseModel): return self.classifier_type in LLM_CLASSIFIER_TYPES def active_tier_severity_order(self) -> tuple[ComplexityTier, ...]: - """This router's built-in ladder, ascending. Meaningless for a custom tier set, whose - severity order is tier_definitions list order over names that are not enum members.""" + """This router's built-in ladder, ascending; not meaningful for a custom tier set.""" return tier_severity_order(self.enable_non_reasoning_tier) def tier_names(self) -> tuple[str, ...]: @@ -1626,12 +1621,7 @@ class ComplexityRouterConfig(BaseModel): @model_validator(mode="after") def _validate_non_reasoning_tier(self) -> "ComplexityRouterConfig": - """Gate the opt-in fifth tier on the two things that make it reachable and routable. - - The heuristic scorers cannot emit it (the v1 score ladder has no rung below simple_medium - and the v2 artifact is trained on four classes), so a router whose classifier can never - return the tier would pay for a rubric bullet and a configured pool that no request reaches. - """ + """Require a classifier that can emit the opt-in tier and a model to route it to.""" non_reasoning_key: Final = ComplexityTier.NON_REASONING.value if not self.enable_non_reasoning_tier: if not self.has_custom_tiers and non_reasoning_key in self.tiers: @@ -1834,13 +1824,11 @@ class ComplexityRouterConfig(BaseModel): return self.tier_labels.get(tier, "").strip() or tier.value def labeled_tiers(self) -> tuple[tuple[ComplexityTier, str], ...]: - """Every active tier paired with its display name, in ascending severity order.""" + """Every tier paired with its display name, in ascending severity order.""" return tuple((tier, self.tier_label(tier)) for tier in self.active_tier_severity_order()) def tier_for_label(self, label: str) -> ComplexityTier | None: - """Resolve a display name back to its active tier, case-insensitively, then canonical - names. A tier this router did not opt into resolves to None, so a classifier naming - NON_REASONING on a four-tier router is an unparseable reply rather than a fifth rung.""" + """Resolve a display name back to its tier, case-insensitively, then canonical names.""" folded: Final = label.strip().casefold() labeled: Final = self.labeled_tiers() return next( diff --git a/tests/test_litellm/router_strategy/test_complexity_router.py b/tests/test_litellm/router_strategy/test_complexity_router.py index ce9cd5d3b7d..2732694ab7e 100644 --- a/tests/test_litellm/router_strategy/test_complexity_router.py +++ b/tests/test_litellm/router_strategy/test_complexity_router.py @@ -1512,6 +1512,7 @@ class TestRouterComplexityDeploymentMethods: def test_the_shipped_rubric_and_default_prompt_stay_free(self) -> None: """Only an operator-written prompt is gated: picking a shipped rubric preset, or writing no prompt at all, leaves a router unmetered, so several of them register under a ceiling of one.""" + def rubric(model_name: str, model_id: str, preset: str | None) -> dict[str, object]: llm_config: dict[str, object] = {"model": "gpt-4o-mini"} if preset is not None: @@ -1648,6 +1649,7 @@ class TestRouterComplexityDeploymentMethods: def test_renaming_built_in_tiers_is_not_a_custom_tier_set(self) -> None: """tier_labels renames the built-in ladder without defining one, so it stays ungated: two such routers register under a ceiling of one.""" + def labeled(model_name: str, model_id: str) -> dict[str, object]: row = self._router_row(model_name, model_id, "heuristic") row["litellm_params"]["complexity_router_config"]["tier_labels"] = {"SIMPLE": "Cheap", "MEDIUM": "Standard"} @@ -2521,9 +2523,7 @@ class TestLLMClassifier: assert outcome.classifier_cost == pytest.approx(1.35e-05) @pytest.mark.asyncio - async def test_aclassify_timeout_does_not_inherit_router_retries_or_fallbacks( - self, llm_classifier_config - ): + async def test_aclassify_timeout_does_not_inherit_router_retries_or_fallbacks(self, llm_classifier_config): real_router = Router( model_list=[ { @@ -2566,9 +2566,7 @@ class TestLLMClassifier: assert real_router.total_calls["openai/mock-backup-classifier"] == 0 @pytest.mark.asyncio - async def test_aclassify_enforces_total_classifier_deadline( - self, mock_router_instance, llm_classifier_config - ): + async def test_aclassify_enforces_total_classifier_deadline(self, mock_router_instance, llm_classifier_config): cancelled = asyncio.Event() async def slow_classifier(**_kwargs: object) -> None: @@ -12415,9 +12413,7 @@ class TestTierHealthFailover: llm_provider="", ) filtered = (*cooling, *blocked, *excluded) - healthy = [ - {"model_name": model, "model_info": {"id": i}} for i in ids_by_model[model] if i not in filtered - ] + healthy = [{"model_name": model, "model_info": {"id": i}} for i in ids_by_model[model] if i not in filtered] if not healthy: raise RouterRateLimitError( model=model, cooldown_time=60.0, enable_pre_call_checks=False, cooldown_list=[] @@ -12846,9 +12842,7 @@ class TestTierHealthFailover: assert all(probed is not request_kwargs for probed in router.litellm_router_instance.probed_kwargs) @pytest.mark.asyncio - async def test_a_peer_whose_every_deployment_is_over_its_rpm_is_not_a_failover_target( - self, mock_router_instance - ): + async def test_a_peer_whose_every_deployment_is_over_its_rpm_is_not_a_failover_target(self, mock_router_instance): """RPM exhaustion is its own verdict from the owner (RouterRateLimitErrorBasic). A peer in that state would be rejected downstream, so it cannot be the substitute.""" from litellm.types.router import RouterRateLimitErrorBasic @@ -12881,9 +12875,7 @@ class TestTierHealthFailover: assert {r.model for r in results} == {"live-c"} @pytest.mark.asyncio - async def test_the_probe_forwards_input_so_window_checks_run_on_input_only_surfaces( - self, mock_router_instance - ): + async def test_the_probe_forwards_input_so_window_checks_run_on_input_only_surfaces(self, mock_router_instance): """The Responses API carries its prompt as `input`, never as messages. The owner only runs its context-window pre-call check when one of them is present, so dropping `input` would silently skip window filtering on that whole surface.""" @@ -12909,9 +12901,7 @@ class TestTierHealthFailover: ), "the eligibility probe must forward `input` to the owner" @pytest.mark.asyncio - async def test_a_group_the_router_has_no_deployment_for_is_not_a_failover_target( - self, mock_router_instance - ): + async def test_a_group_the_router_has_no_deployment_for_is_not_a_failover_target(self, mock_router_instance): """The owner answers an unconfigured group with BadRequestError. Reading that as live would both skip failover off it and let it be chosen as a substitute.""" router = self._router( @@ -13100,9 +13090,7 @@ class TestClassifierVision: routed as default_fallback on text the request never contained. """ router = self._router(mock_router_instance, vision={"enabled": True}) - response = await router.async_pre_routing_hook( - model="m", request_kwargs={}, messages=self._turn(IMG_PART) - ) + response = await router.async_pre_routing_hook(model="m", request_kwargs={}, messages=self._turn(IMG_PART)) assert response.routing_decision["cause"] == "llm_classifier" assert response.model == "t-complex" assert [block["type"] for block in self._classifier_user_content(mock_router_instance)] == [ @@ -13113,9 +13101,7 @@ class TestClassifierVision: @pytest.mark.asyncio async def test_image_only_turn_still_falls_back_when_vision_is_off(self, mock_router_instance): router = self._router(mock_router_instance, vision={"enabled": False}) - response = await router.async_pre_routing_hook( - model="m", request_kwargs={}, messages=self._turn(IMG_PART) - ) + response = await router.async_pre_routing_hook(model="m", request_kwargs={}, messages=self._turn(IMG_PART)) assert response.routing_decision["cause"] == "default_fallback" mock_router_instance.acompletion.assert_not_awaited() @@ -13185,9 +13171,7 @@ class TestClassifierVision: makes the image the only variable; a margin loose enough to leave the score undecided would pass whether or not the guard exists. """ - router = self._router( - mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra - ) + router = self._router(mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra) response = await router.async_pre_routing_hook( model="m", request_kwargs={}, messages=self._turn({"type": "text", "text": "what is this"}, IMG_PART) ) @@ -13201,9 +13185,7 @@ class TestClassifierVision: self, mock_router_instance, classifier_type, extra, short_circuit_cause ): """The negative class: same router, same text, no image, and the scorer still decides.""" - router = self._router( - mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra - ) + router = self._router(mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra) response = await router.async_pre_routing_hook( model="m", request_kwargs={}, messages=[{"role": "user", "content": "what is this"}] ) @@ -13225,13 +13207,7 @@ NON_REASONING_TIERS: Final = { class TestNonReasoningTier: - """The opt-in fifth built-in tier below SIMPLE. - - Two properties carry the feature. A router that did not opt in must be byte-identical to one - built before the tier existed, because the tier set feeds the classifier rubric, the wire enum, - and the savings baseline, all of which move live routing decisions and spend. A router that did - opt in must be able to actually reach the tier and escalate off it. - """ + """The opt-in fifth built-in tier below SIMPLE: inert unless enabled, reachable when it is.""" @staticmethod def _router(mock_router_instance, **overrides) -> ComplexityRouter: @@ -13249,8 +13225,7 @@ class TestNonReasoningTier: ) def test_ladder_gains_a_rung_below_simple_only_when_enabled(self): - """Tier 0 sits at the bottom. Anywhere else and escalation, the savings baseline, and - heuristic_first's 'highest tier' check would all read a different ladder.""" + """Tier 0 sits at the bottom; anywhere else and escalation and the baseline shift.""" enabled: Final = ComplexityRouterConfig( tiers=dict(NON_REASONING_TIERS), enable_non_reasoning_tier=True, @@ -13261,8 +13236,7 @@ class TestNonReasoningTier: assert ComplexityRouterConfig().tier_names() == ("SIMPLE", "MEDIUM", "COMPLEX", "REASONING") def test_default_router_is_unchanged_by_the_tier_existing(self): - """The regression that matters for every already-deployed router: the enum grew a member, - and nothing a four-tier router sends or resolves may change because of it.""" + """The enum grew a member, and nothing a four-tier router sends or resolves may change.""" default: Final = ComplexityRouterConfig() assert default.enable_non_reasoning_tier is False assert "NON_REASONING" not in DEFAULT_COMPLEXITY_CONFIG.tiers @@ -13272,8 +13246,7 @@ class TestNonReasoningTier: @pytest.mark.parametrize("preset", tuple(ClassificationRubric)) def test_rubric_gains_the_bullet_only_when_enabled(self, preset): - """Every preset renders one bullet per active tier, so an unset toggle must leave all four - shipped rubrics byte-identical while an enabled one must actually describe the new tier.""" + """An unset toggle leaves every shipped rubric byte-identical; an enabled one adds a bullet.""" enabled: Final = ComplexityRouterConfig( tiers=dict(NON_REASONING_TIERS), enable_non_reasoning_tier=True, @@ -13286,18 +13259,18 @@ class TestNonReasoningTier: assert "- NON_REASONING" not in off def test_enabled_router_puts_the_tier_on_the_classifier_wire(self, mock_router_instance): - """The response schema's enum is what the classifier may return; without the new label the - tier would be unreachable no matter what the rubric says.""" + """The schema enum bounds what the classifier may return, whatever the rubric says.""" router: Final = self._router(mock_router_instance) enum: Final = router._classifier_response_format["json_schema"]["schema"]["properties"]["tier"]["enum"] assert enum == ["NON_REASONING", "SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] @pytest.mark.asyncio async def test_classifier_verdict_routes_to_the_tier_model(self, mock_router_instance): - """End to end on the LLM path: the classifier names the tier and the request lands on that - tier's model with the decision recording it.""" + """The classifier names the tier and the request lands on that tier's model.""" mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) - router: Final = self._router(mock_router_instance, tiers={**NON_REASONING_TIERS, "NON_REASONING": "cheap-relay"}) + router: Final = self._router( + mock_router_instance, tiers={**NON_REASONING_TIERS, "NON_REASONING": "cheap-relay"} + ) response = await router.async_pre_routing_hook( model="test-non-reasoning-router", request_kwargs={}, @@ -13308,9 +13281,10 @@ class TestNonReasoningTier: assert response.routing_decision["cause"] == "llm_classifier" @pytest.mark.asyncio - async def test_a_four_tier_router_ignores_a_non_reasoning_verdict(self, llm_complexity_router, mock_router_instance): - """A classifier that names the tier at a router which never opted in must be an unparseable - reply that falls back, not a silent route to a tier the operator did not configure.""" + async def test_a_four_tier_router_ignores_a_non_reasoning_verdict( + self, llm_complexity_router, mock_router_instance + ): + """Naming the tier at a router that never opted in falls back instead of routing there.""" mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) outcome = await llm_complexity_router.aclassify("relay this") assert outcome.tier != ComplexityTier.NON_REASONING @@ -13323,8 +13297,7 @@ class TestNonReasoningTier: assert router._escalate_tier(ComplexityTier.REASONING) == ComplexityTier.REASONING def test_escalation_skips_the_tier_when_unconfigured(self, mock_router_instance): - """SIMPLE must still escalate to MEDIUM rather than to the cheaper new rung, or escalation - would route below the model the caller would otherwise have received.""" + """SIMPLE still escalates to MEDIUM, so escalation never routes below the caller's model.""" router: Final = self._router( mock_router_instance, tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}, @@ -13332,8 +13305,7 @@ class TestNonReasoningTier: assert router._escalate_tier(ComplexityTier.SIMPLE) == ComplexityTier.MEDIUM def test_tier_zero_is_never_the_savings_baseline(self, mock_router_instance): - """Savings are measured against the hardest configured tier. If tier 0 could win that pick, - every enabled router's reported savings would invert.""" + """Savings use the hardest configured tier; tier 0 winning would invert every figure.""" assert self._router(mock_router_instance)._hardest_tier_models() == ("o1-preview",) cheap_only: Final = self._router( mock_router_instance, tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini"} @@ -13356,8 +13328,7 @@ class TestNonReasoningTier: ids=["heuristic", "heuristic_v2", "no_model"], ) def test_unreachable_or_unroutable_configs_are_rejected(self, overrides, expected): - """The toggle is refused wherever it could not do anything: the heuristic scorers cannot - emit the tier, and an unconfigured tier would fall through to the default model.""" + """Refused where it could do nothing: no scorer emits the tier, no pool routes it.""" config: Final = { "tiers": dict(NON_REASONING_TIERS), "enable_non_reasoning_tier": True, @@ -13386,9 +13357,7 @@ class TestNonReasoningTier: ) def test_heuristic_v2_predictions_never_reach_the_new_tier(self, mock_router_instance): - """The bundled artifact is trained on four classes, so its 1-based tier index must keep - mapping onto SIMPLE..REASONING. Reading the enabled ladder here would shift every - prediction down a rung and make REASONING unreachable.""" + """The four-class artifact's 1-based index must keep mapping onto SIMPLE..REASONING.""" router: Final = ComplexityRouter( model_name="v2-router", litellm_router_instance=mock_router_instance, @@ -13399,8 +13368,6 @@ class TestNonReasoningTier: ) outcome = router._classify_with_heuristic_v2("implement a distributed rate limiter under concurrency") assert outcome.tier in TIER_SEVERITY_ORDER - # One probability signal per trained class, named for the tier that class means. A ladder - # shifted by the new rung would relabel all four and lose REASONING off the end. assert tuple(signal.split(":")[1].split("=")[0] for signal in outcome.signals[1:]) == ( "simple", "medium", diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 5ec9f72c2a1..33bb7283a7e 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -236,11 +236,8 @@ const ClassifierTypeRadios: React.FC<{ ); }; -/** - * The NON_REASONING keys a classifier switch carries forward, or clears. Only the LLM classifier - * can emit the tier, and the switch is disabled elsewhere, so a flag left set under another - * classifier would be an unsaveable config the operator could not undo. - */ +/** The NON_REASONING keys a classifier switch carries forward, or clears for a classifier that + * cannot emit the tier. Leaving them set there is a config the backend refuses on save. */ export const nonReasoningTierFields = ( classifierType: ClassifierType, value: ComplexityRouterConfigValue, diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index c89c130af86..38c2f13b3f9 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -78,10 +78,7 @@ export const DEFAULT_CLASSIFICATION_MODE: ClassificationMode = "every_request"; */ export type ClassificationFrequency = ClassificationMode | "session"; -/** - * NON_REASONING is optional because it is the opt-in fifth tier: a router that never enabled it - * stores no such key, and hydrating one in would send an empty pool the backend rejects. - */ +/** NON_REASONING is optional: a router that never enabled it stores no such key. */ export type ComplexityTiers = { SIMPLE: string[]; MEDIUM: string[]; @@ -362,10 +359,7 @@ export type ComplexityTierLabels = Partial export interface ComplexityRouterConfigValue { tiers: ComplexityTiers; - /** - * Opt into the NON_REASONING tier below SIMPLE. Off means the router keeps the four-tier ladder - * it has always had, so an existing router's rubric and tier decisions cannot move under it. - */ + /** Opt into the NON_REASONING tier below SIMPLE; off keeps the four-tier ladder. */ enable_non_reasoning_tier?: boolean; custom_tier_set?: CustomTierSet; tier_labels?: ComplexityTierLabels; @@ -510,7 +504,6 @@ export const TIER_DESCRIPTIONS: Record< }, }; -/** Every built-in tier name, including the opt-in one, for label and membership checks. */ export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array; export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string => @@ -523,9 +516,7 @@ export const DEFAULT_HYBRID_BOUNDARY_MARGIN = 0.03; /** * Tiers the heuristic_first threshold may name. The top tier is excluded because it would short - * circuit every request and leave the classifier unreachable, which the backend rejects. So is - * NON_REASONING, which the backend refuses alongside heuristic_first because the local scorer - * cannot produce it. + * circuit every request and leave the classifier unreachable, which the backend rejects. */ export const HEURISTIC_FIRST_MAX_TIER_KEYS = TIER_ORDER.slice(0, -1); diff --git a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx index 622a460fb79..caadd79039d 100644 --- a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx @@ -5,17 +5,11 @@ import { Switch } from "@/components/ui/switch"; import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; -/** - * The opt-in fifth tier. Only offered on the LLM classification method, matching the backend: the - * heuristic scorers cannot produce the tier, so enabling it there would buy a rubric bullet and a - * model pool that no request ever reaches. - */ const NonReasoningTierToggle: React.FC<{ value: ComplexityRouterConfigValue; onChange: (value: ComplexityRouterConfigValue) => void; available: boolean; }> = ({ value, onChange, available }) => { - // Off drops the tier's key rather than leaving the empty pool the backend rejects. const handleToggle = (enabled: boolean): void => { const { NON_REASONING: existingPool, ...keptTiers } = value.tiers; const next: ComplexityRouterConfigValue = { diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index 2f36ec2f43d..b1edae6b78e 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts @@ -538,8 +538,7 @@ export const buildComplexityRouterConfig = ({ const payload: ComplexityRouterConfigPayload = { tiers, - // Only written when on, and never beside a custom tier set: the backend rejects the two - // together, and an explicit false on a four-tier router would be a key it never carried. + // The backend rejects the flag beside a custom tier set. ...(!customTierSet && enableNonReasoningTier && { enable_non_reasoning_tier: true }), ...(serializedTierModelConfigs && { tier_model_configs: serializedTierModelConfigs }), ...(defaultModel?.trim() && { default_model: defaultModel }), diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts index 289a5645b51..cd3082b9456 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts @@ -4,13 +4,9 @@ import type { TierModelParams, TierModelParamsByTier } from "./complexity_router export const TIER_ORDER: ComplexityTier[] = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]; -/** Every built-in tier name, so a stored NON_REASONING row is recognized as built-in either way. */ export const ALL_BUILT_IN_TIERS: ComplexityTier[] = ["NON_REASONING", ...TIER_ORDER]; -/** - * The ladder one router renders, ascending. NON_REASONING is tier 0 and appears only when enabled, - * which is what keeps an existing four-tier router's form, payload, and rubric unchanged. - */ +/** The ladder one router renders, ascending; NON_REASONING appears only when enabled. */ export const tierOrderFor = (enableNonReasoningTier: boolean | undefined): ComplexityTier[] => enableNonReasoningTier ? ALL_BUILT_IN_TIERS : TIER_ORDER; From f4aef5a1db67d35f606d09f5e8bfe008181af015 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:19:35 -0700 Subject: [PATCH 044/105] fix(spend-tracking): make the spend-log metadata cache limits plain constants The ten minute TTL and the 10000 item ceiling need no env override, and the documentation env-key check flags any os.getenv read that the docs do not list --- litellm/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index c1896573eb5..9654c84c412 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1763,8 +1763,8 @@ LITELLM_SETTINGS_SAFE_DB_OVERRIDES: Final = [ SPECIAL_LITELLM_AUTH_TOKEN: Final = ["ui-token"] DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL = int(os.getenv("DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL", 60)) DEFAULT_ACCESS_GROUP_CACHE_TTL: Final = int(os.getenv("DEFAULT_ACCESS_GROUP_CACHE_TTL", 600)) -SPEND_LOG_KEY_METADATA_CACHE_TTL: Final = int(os.getenv("SPEND_LOG_KEY_METADATA_CACHE_TTL", "600")) -SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS: Final = int(os.getenv("SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS", "10000")) +SPEND_LOG_KEY_METADATA_CACHE_TTL: Final = 600 +SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS: Final = 10000 # Short TTL for negative MCP access-group existence lookups. Keeps unauthenticated # callers from forcing a DB query per request for unknown names, while bounding # staleness so a transient DB error (which surfaces as an empty list) cannot From 0c2d0f4777f4113b42510e2daef2330ca0bb87a8 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:28:13 -0700 Subject: [PATCH 045/105] fix(spend-tracking): forget an empty spend-log lookup after thirty seconds The daily spend rows and the spend logs of one batch are written a moment apart, so a usage read landing between them used to remember the session as nameless for ten minutes on that worker. Found identities keep the ten minute entry --- litellm/constants.py | 1 + .../spend_tracking/key_metadata_recovery.py | 19 ++++++++++++++++-- .../test_key_metadata_recovery.py | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 9654c84c412..cfc5b6b86a7 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1764,6 +1764,7 @@ SPECIAL_LITELLM_AUTH_TOKEN: Final = ["ui-token"] DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL = int(os.getenv("DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL", 60)) DEFAULT_ACCESS_GROUP_CACHE_TTL: Final = int(os.getenv("DEFAULT_ACCESS_GROUP_CACHE_TTL", 600)) SPEND_LOG_KEY_METADATA_CACHE_TTL: Final = 600 +SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL: Final = 30 SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS: Final = 10000 # Short TTL for negative MCP access-group existence lookups. Keeps unauthenticated # callers from forcing a DB query per request for unknown names, while bounding diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index 7212d8b6acb..354c6479d34 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -9,7 +9,11 @@ from typing_extensions import ReadOnly, TypedDict from litellm._logging import verbose_proxy_logger from litellm.caching.in_memory_cache import InMemoryCache -from litellm.constants import SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, SPEND_LOG_KEY_METADATA_CACHE_TTL +from litellm.constants import ( + SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, + SPEND_LOG_KEY_METADATA_CACHE_TTL, + SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL, +) from litellm.litellm_core_utils.litellm_logging import is_valid_sha256_hash from litellm.proxy.utils import PrismaClient from litellm.repositories.user_repository import UserRepository @@ -230,6 +234,17 @@ async def _query_spend_log_metadata( ) +def _remember_spend_log_metadata( + cache: InMemoryCache, digest: str, window: tuple[datetime, datetime], meta: KeyMetadataDict | None +) -> None: + if meta is None: + cache.set_cache( + _spend_log_cache_key(digest, window), KeyMetadataDict(), ttl=SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL + ) + return + cache.set_cache(_spend_log_cache_key(digest, window), meta) + + async def recover_key_metadata_from_spend_logs( prisma_client: PrismaClient, missing_keys: AbstractSet[str], @@ -251,7 +266,7 @@ async def recover_key_metadata_from_spend_logs( fresh: Final = await _query_spend_log_metadata(prisma_client, uncached, window) if uncached else _EMPTY_KEY_METADATA if fresh is not None: for digest in uncached: - cache.set_cache(_spend_log_cache_key(digest, window), fresh.get(digest, KeyMetadataDict())) + _remember_spend_log_metadata(cache, digest, window, fresh.get(digest)) return MappingProxyType( {digest: meta for digest, meta in (*cached.items(), *(fresh or _EMPTY_KEY_METADATA).items()) if meta} ) diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 3d7362b9de0..4f065070330 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -1,3 +1,4 @@ +import time from collections.abc import Sequence from datetime import datetime from types import SimpleNamespace @@ -7,6 +8,7 @@ import pytest from prisma.errors import PrismaError from litellm.caching.in_memory_cache import InMemoryCache +from litellm.constants import SPEND_LOG_KEY_METADATA_CACHE_TTL, SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL from litellm.proxy.spend_tracking.key_metadata_recovery import ( fill_missing_api_key_aliases, recover_double_hashed_key_metadata, @@ -395,3 +397,21 @@ async def test_recover_key_metadata_from_spend_logs_does_not_cache_a_failed_quer result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) assert result[digest]["key_alias"] == "back-online" + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_forgets_a_miss_long_before_a_hit(): + found = hash_token("cli-session-found") + unknown = hash_token("cli-session-unknown") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(found, "found-alias", None, None)]) + started = time.time() + + await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) + + hit_expires = next(deadline for key, deadline in cache.ttl_dict.items() if found in key) + miss_expires = next(deadline for key, deadline in cache.ttl_dict.items() if unknown in key) + assert miss_expires - started <= SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL + 1 + assert hit_expires - started >= SPEND_LOG_KEY_METADATA_CACHE_TTL - 1 From d72eb4491ab2da2a56944c720a3200280cdd040d Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Tue, 8 Sep 2026 17:22:30 -0400 Subject: [PATCH 046/105] fix(ui): repair pass-through delete confirm dialog and disable delete for config endpoints --- .../PassThroughEndpointsTable.test.tsx | 47 ++++++++++++++ .../PassThroughEndpointsTableColumns.tsx | 30 +++++++-- .../PassThroughSettings.tsx | 62 ++++++++----------- 3 files changed, 98 insertions(+), 41 deletions(-) diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx index 040fa463f9c..7a3fc621fc1 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx @@ -89,6 +89,53 @@ describe("PassThroughEndpointsTable", () => { expect(onDeleteClick).toHaveBeenCalledWith("ep-1"); }); + it("should disable edit and delete for config-defined endpoints", async () => { + const user = userEvent.setup(); + const onEndpointClick = vi.fn(); + const onDeleteClick = vi.fn(); + const configEndpoint: passThroughItem = { + id: "ep-config", + path: "/from-config", + target: "https://config.example.com", + headers: {}, + is_from_config: true, + }; + render( + , + ); + + await user.click(screen.getByTestId("endpoint-actions-ep-config")); + const editItem = await screen.findByTestId("endpoint-action-edit"); + const deleteItem = await screen.findByTestId("endpoint-action-delete"); + + expect(editItem).toHaveAttribute("data-disabled"); + expect(deleteItem).toHaveAttribute("data-disabled"); + + await user.click(editItem); + await user.click(deleteItem); + + expect(onEndpointClick).not.toHaveBeenCalled(); + expect(onDeleteClick).not.toHaveBeenCalled(); + }); + + it("should label endpoint source as Config or DB", () => { + const configEndpoint: passThroughItem = { + id: "ep-config", + path: "/from-config", + target: "https://config.example.com", + headers: {}, + is_from_config: true, + }; + render(); + expect(screen.getByText("Config")).toBeInTheDocument(); + expect(screen.getAllByText("DB")).toHaveLength(2); + }); + it("should disable edit and delete for endpoints without an id", async () => { const user = userEvent.setup(); const onEndpointClick = vi.fn(); diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx index d22b274861a..d63be628110 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx @@ -18,6 +18,10 @@ import { cn } from "@/lib/cva.config"; import type { passThroughItem } from "./PassThroughSettings"; +const CONFIG_EDIT_HINT = "Config pass-through endpoints cannot be edited on the dashboard. Please edit the config file."; +const CONFIG_DELETE_HINT = + "Config pass-through endpoints cannot be deleted on the dashboard. Please edit the config file."; + function HeaderWithTooltip({ title, tooltip }: { title: string; tooltip: string }) { return (
@@ -73,6 +77,7 @@ interface EndpointRowActionsProps { function EndpointRowActions({ endpoint, onEndpointClick, onDeleteClick }: EndpointRowActionsProps) { const endpointId = endpoint.id; + const isFromConfig = endpoint.is_from_config ?? false; return ( endpointId && onEndpointClick(endpointId)} + disabled={isFromConfig || !endpointId} + title={isFromConfig ? CONFIG_EDIT_HINT : undefined} + onClick={() => !isFromConfig && endpointId && onEndpointClick(endpointId)} > Edit @@ -95,8 +101,9 @@ function EndpointRowActions({ endpoint, onEndpointClick, onDeleteClick }: Endpoi endpointId && onDeleteClick(endpointId)} + disabled={isFromConfig || !endpointId} + title={isFromConfig ? CONFIG_DELETE_HINT : undefined} + onClick={() => !isFromConfig && endpointId && onDeleteClick(endpointId)} > Delete @@ -124,7 +131,9 @@ export const getPassThroughEndpointsTableColumns = ({ enableSorting: false, cell: ({ row }) => { const endpointId = row.original.id; - if (!endpointId) return ; + if (!endpointId || row.original.is_from_config) { + return ; + } return ( { + const isFromConfig = row.original.is_from_config ?? false; + return ; + }, + }, { id: "path", accessorKey: "path", diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx index 2dc6fdbd32c..8ef2766d412 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx @@ -1,4 +1,13 @@ import React, { useState, useEffect } from "react"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { deletePassThroughEndpointsCall, getPassThroughEndpointsCall } from "../networking"; import AddPassThroughEndpoint from "../add_pass_through"; @@ -25,6 +34,7 @@ export interface passThroughItem { methods?: string[]; guardrails?: Record; default_query_params?: Record; + is_from_config?: boolean; } const PassThroughSettings: React.FC = ({ accessToken, userRole, userID, premiumUser }) => { @@ -133,42 +143,22 @@ const PassThroughSettings: React.FC = ({ accessToken, onDeleteClick={handleDelete} /> - {isDeleteModalOpen && ( -
-
- - - - -
-
-
-
-

Delete Pass-Through Endpoint

-
-

- Are you sure you want to delete this pass-through endpoint? This action cannot be undone. -

-
-
-
-
-
- - -
-
-
-
- )} + !open && cancelDelete()}> + + + Delete Pass-Through Endpoint + + Are you sure you want to delete this pass-through endpoint? This action cannot be undone. + + + + Cancel + + + +
); }; From 093b473710313f22efd864e5262e5a376a0ee2ce Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Tue, 8 Sep 2026 17:43:23 -0400 Subject: [PATCH 047/105] fix(ui): surface the config-endpoint hint as visible menu text --- .../PassThroughEndpointsTable.test.tsx | 12 ++++++++++++ .../PassThroughEndpointsTableColumns.tsx | 12 +++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx index 7a3fc621fc1..af3f98b746b 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.test.tsx @@ -115,6 +115,9 @@ describe("PassThroughEndpointsTable", () => { expect(editItem).toHaveAttribute("data-disabled"); expect(deleteItem).toHaveAttribute("data-disabled"); + expect(screen.getByTestId("endpoint-config-hint")).toHaveTextContent( + "This endpoint is defined in the config file and cannot be edited or deleted on the dashboard.", + ); await user.click(editItem); await user.click(deleteItem); @@ -123,6 +126,15 @@ describe("PassThroughEndpointsTable", () => { expect(onDeleteClick).not.toHaveBeenCalled(); }); + it("should not show the config hint for DB endpoints", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByTestId("endpoint-actions-ep-1")); + await screen.findByTestId("endpoint-action-delete"); + expect(screen.queryByTestId("endpoint-config-hint")).not.toBeInTheDocument(); + }); + it("should label endpoint source as Config or DB", () => { const configEndpoint: passThroughItem = { id: "ep-config", diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx index d63be628110..5b18685a140 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTableColumns.tsx @@ -18,9 +18,8 @@ import { cn } from "@/lib/cva.config"; import type { passThroughItem } from "./PassThroughSettings"; -const CONFIG_EDIT_HINT = "Config pass-through endpoints cannot be edited on the dashboard. Please edit the config file."; -const CONFIG_DELETE_HINT = - "Config pass-through endpoints cannot be deleted on the dashboard. Please edit the config file."; +const CONFIG_ENDPOINT_HINT = + "This endpoint is defined in the config file and cannot be edited or deleted on the dashboard."; function HeaderWithTooltip({ title, tooltip }: { title: string; tooltip: string }) { return ( @@ -91,7 +90,6 @@ function EndpointRowActions({ endpoint, onEndpointClick, onDeleteClick }: Endpoi !isFromConfig && endpointId && onEndpointClick(endpointId)} > @@ -102,12 +100,16 @@ function EndpointRowActions({ endpoint, onEndpointClick, onDeleteClick }: Endpoi variant="destructive" data-testid="endpoint-action-delete" disabled={isFromConfig || !endpointId} - title={isFromConfig ? CONFIG_DELETE_HINT : undefined} onClick={() => !isFromConfig && endpointId && onDeleteClick(endpointId)} > Delete + {isFromConfig && ( +
+ {CONFIG_ENDPOINT_HINT} +
+ )} ); From 907c200b3162368cfaa0e363411997330b2da4f2 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:00:56 -0700 Subject: [PATCH 048/105] fix(spend-tracking): run one spend-log scan at a time and back off repeated misses Concurrent usage reads on one worker now share a single spend-log query instead of each scanning the same window, and a digest that comes back nameless a second time is remembered for the full ten minutes rather than thirty seconds, so a key that never resolves costs at most two scans per worker per window per ten minutes. The first miss still expires after thirty seconds so a read that lands between the daily spend flush and the spend-log flush recovers on the next read --- .../spend_tracking/key_metadata_recovery.py | 71 +++++++++++++------ .../test_key_metadata_recovery.py | 44 ++++++++++++ 2 files changed, 92 insertions(+), 23 deletions(-) diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index 354c6479d34..319050b87cd 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -1,3 +1,4 @@ +import asyncio from collections.abc import Awaitable, Callable, Mapping, Sequence from collections.abc import Set as AbstractSet from datetime import datetime @@ -77,6 +78,7 @@ _SPEND_LOG_METADATA_CACHE: Final = InMemoryCache( max_size_in_memory=SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL, ) +_SPEND_LOG_QUERY_LOCK: Final = asyncio.Lock() _EMPTY_KEY_METADATA: Final[Mapping[str, KeyMetadataDict]] = MappingProxyType({}) _EMPTY_EMAILS: Final[Mapping[str, str]] = MappingProxyType({}) @@ -205,11 +207,17 @@ def _spend_log_cache_key(digest: str, window: tuple[datetime, datetime]) -> str: def _cached_spend_log_metadata( cache: InMemoryCache, - digest: str, + digests: AbstractSet[str], window: tuple[datetime, datetime], -) -> KeyMetadataDict | None: - cached: Final[object] = cache.get_cache(_spend_log_cache_key(digest, window)) - return None if cached is None else _CACHED_KEY_METADATA.validate_python(cached) +) -> Mapping[str, KeyMetadataDict]: + return MappingProxyType( + { + digest: _CACHED_KEY_METADATA.validate_python(cached) + for digest in digests + for cached in (cache.get_cache(_spend_log_cache_key(digest, window)),) + if cached is not None + } + ) async def _query_spend_log_metadata( @@ -237,12 +245,36 @@ async def _query_spend_log_metadata( def _remember_spend_log_metadata( cache: InMemoryCache, digest: str, window: tuple[datetime, datetime], meta: KeyMetadataDict | None ) -> None: - if meta is None: - cache.set_cache( - _spend_log_cache_key(digest, window), KeyMetadataDict(), ttl=SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL - ) + key: Final = _spend_log_cache_key(digest, window) + if meta is not None: + cache.set_cache(key, meta) return - cache.set_cache(_spend_log_cache_key(digest, window), meta) + missed_before: Final = f"{key}:missed-before" + if cache.get_cache(missed_before) is not None: + cache.set_cache(key, KeyMetadataDict()) + return + cache.set_cache(key, KeyMetadataDict(), ttl=SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL) + cache.set_cache(missed_before, True) + + +async def _spend_log_metadata_one_query_at_a_time( + prisma_client: PrismaClient, + cache: InMemoryCache, + lock: asyncio.Lock, + digests: AbstractSet[str], + window: tuple[datetime, datetime], +) -> Mapping[str, KeyMetadataDict]: + async with lock: + settled: Final = _cached_spend_log_metadata(cache, digests, window) + pending: Final = digests - frozenset(settled) + fresh: Final = ( + await _query_spend_log_metadata(prisma_client, pending, window) if pending else _EMPTY_KEY_METADATA + ) + if fresh is None: + return settled + for digest in pending: + _remember_spend_log_metadata(cache, digest, window, fresh.get(digest)) + return MappingProxyType({**settled, **fresh}) async def recover_key_metadata_from_spend_logs( @@ -250,26 +282,19 @@ async def recover_key_metadata_from_spend_logs( missing_keys: AbstractSet[str], window: tuple[datetime, datetime], cache: InMemoryCache = _SPEND_LOG_METADATA_CACHE, + lock: asyncio.Lock = _SPEND_LOG_QUERY_LOCK, ) -> Mapping[str, KeyMetadataDict]: digests: Final = frozenset(key for key in missing_keys if _is_spend_log_digest(key)) if not digests: return _EMPTY_KEY_METADATA - cached: Final = MappingProxyType( - { - digest: meta - for digest in digests - for meta in (_cached_spend_log_metadata(cache, digest, window),) - if meta is not None - } - ) + cached: Final = _cached_spend_log_metadata(cache, digests, window) uncached: Final = digests - frozenset(cached) - fresh: Final = await _query_spend_log_metadata(prisma_client, uncached, window) if uncached else _EMPTY_KEY_METADATA - if fresh is not None: - for digest in uncached: - _remember_spend_log_metadata(cache, digest, window, fresh.get(digest)) - return MappingProxyType( - {digest: meta for digest, meta in (*cached.items(), *(fresh or _EMPTY_KEY_METADATA).items()) if meta} + settled: Final = ( + await _spend_log_metadata_one_query_at_a_time(prisma_client, cache, lock, uncached, window) + if uncached + else _EMPTY_KEY_METADATA ) + return MappingProxyType({digest: meta for digest, meta in (*cached.items(), *settled.items()) if meta}) def _row_with_recovered_fields( diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 4f065070330..5b45777502a 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -1,3 +1,4 @@ +import asyncio import time from collections.abc import Sequence from datetime import datetime @@ -415,3 +416,46 @@ async def test_recover_key_metadata_from_spend_logs_forgets_a_miss_long_before_a miss_expires = next(deadline for key, deadline in cache.ttl_dict.items() if unknown in key) assert miss_expires - started <= SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL + 1 assert hit_expires - started >= SPEND_LOG_KEY_METADATA_CACHE_TTL - 1 + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_runs_one_query_for_concurrent_lookups(): + digest = hash_token("cli-session-shared") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache() + lock = asyncio.Lock() + mock_prisma = MagicMock() + + async def slow_query_raw(sql: str, *params: object) -> list[dict[str, str | None]]: + await asyncio.sleep(0.01) + return [_digest_row(digest, "shared-alias", None, None)] + + mock_prisma.db.query_raw = AsyncMock(side_effect=slow_query_raw) + + results = await asyncio.gather( + *( + recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache, lock=lock) + for _ in range(9) + ) + ) + + assert all(result[digest]["key_alias"] == "shared-alias" for result in results) + assert mock_prisma.db.query_raw.await_count == 1 + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_keeps_a_repeated_miss_as_long_as_a_hit(): + unknown = hash_token("cli-session-never-named") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs([]) + await recover_key_metadata_from_spend_logs(mock_prisma, {unknown}, window, cache=cache) + first_miss_key = next(key for key in cache.ttl_dict if unknown in key and not key.endswith(":missed-before")) + cache.ttl_dict[first_miss_key] = time.time() - 1 + started = time.time() + + await recover_key_metadata_from_spend_logs(mock_prisma, {unknown}, window, cache=cache) + + assert mock_prisma.db.query_raw.await_count == 2 + assert cache.ttl_dict[first_miss_key] - started >= SPEND_LOG_KEY_METADATA_CACHE_TTL - 1 From 00381ef03baedc9c9f7bec55f8c6139ecdb75f21 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 15:07:18 -0700 Subject: [PATCH 049/105] test: validate opaque stream IDs and hide log-reader credentials --- .../test_responses_bridge_streaming_e2e.py | 24 ++++++++++++------- tests/e2e/logging/datadog_reader.py | 10 ++++---- tests/e2e/logging/test_datadog_reader.py | 20 ++++++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 tests/e2e/logging/test_datadog_reader.py diff --git a/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py index 9a45743a0cd..75817340876 100644 --- a/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py +++ b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py @@ -16,8 +16,10 @@ into a chat completion chunk. Two customer-visible contracts only hold on that p from __future__ import annotations +from typing import Final, Literal + import pytest -from pydantic import BaseModel +from pydantic import BaseModel, Field from e2e_config import unique_marker from e2e_http import StreamingResponse @@ -51,7 +53,8 @@ class _BridgeChoice(BaseModel): class _BridgeChunk(BaseModel): id: str - choices: list[_BridgeChoice] = [] + object: Literal["chat.completion.chunk"] + choices: list[_BridgeChoice] = Field(default_factory=list) class _WeatherArgs(BaseModel): @@ -103,16 +106,19 @@ class TestResponsesBridgeChatCompletionsStreaming: resources.key(), ChatBody( model=bridged_model, - messages=[ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}")], + messages=[ + ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}") + ], max_tokens=64, stream=True, ), ) - chunks = _bridge_chunks(result) - ids = {chunk.id for chunk in chunks} + chunks: Final = _bridge_chunks(result) + assert len(chunks) > 1, "the shared-id contract needs more than one streamed chunk" + ids: Final = frozenset(chunk.id for chunk in chunks) assert len(ids) == 1, f"bridged stream used {len(ids)} different chunk ids: {sorted(ids)[:5]}" - assert ids.pop().startswith("chatcmpl-"), f"bridged chunk id is not chat-completion shaped: {chunks[0].id}" + assert chunks[0].id.strip(), "bridged stream emitted an empty chunk id" @pytest.mark.covers( "llm.chat_completions.openai.basic.stream.bridge_streams_sse", @@ -134,9 +140,9 @@ class TestResponsesBridgeChatCompletionsStreaming: chunks = _bridge_chunks(result) content = "".join(choice.delta.content or "" for chunk in chunks for choice in chunk.choices) assert content.strip(), f"bridged stream completed with no content deltas: {result.stream_events[:3]}" - assert any( - choice.finish_reason for chunk in chunks for choice in chunk.choices - ), f"bridged stream never emitted a finish_reason: {result.stream_events[-3:]}" + assert any(choice.finish_reason for chunk in chunks for choice in chunk.choices), ( + f"bridged stream never emitted a finish_reason: {result.stream_events[-3:]}" + ) assert result.stream_done, f"bridged stream did not terminate with [DONE]: {result.stream_events[-2:]}" @pytest.mark.covers( diff --git a/tests/e2e/logging/datadog_reader.py b/tests/e2e/logging/datadog_reader.py index d0f478185c2..7b4372ef9ba 100644 --- a/tests/e2e/logging/datadog_reader.py +++ b/tests/e2e/logging/datadog_reader.py @@ -13,7 +13,7 @@ empty result. External reads go through ``e2e_http``. from __future__ import annotations import time -from dataclasses import dataclass +from dataclasses import dataclass, field import pytest from pydantic import BaseModel, ConfigDict, Field @@ -36,8 +36,8 @@ _RATE_LIMIT_RETRIES = 5 class _DdAuthHeaders(Headers): - api_key: str = Field(serialization_alias="DD-API-KEY") - app_key: str = Field(serialization_alias="DD-APPLICATION-KEY") + api_key: str = Field(serialization_alias="DD-API-KEY", repr=False) + app_key: str = Field(serialization_alias="DD-APPLICATION-KEY", repr=False) class _SearchFilter(BaseModel): @@ -88,8 +88,8 @@ class _SearchResponse(BaseModel): @dataclass(frozen=True, slots=True) class DdLogsReader: site: str - api_key: str - app_key: str + api_key: str = field(repr=False) + app_key: str = field(repr=False) def events_for_marker(self, marker: str) -> list[DdLogEvent]: """Every ingested event whose attributes carry the marker. DataDog diff --git a/tests/e2e/logging/test_datadog_reader.py b/tests/e2e/logging/test_datadog_reader.py new file mode 100644 index 00000000000..00624bd3c84 --- /dev/null +++ b/tests/e2e/logging/test_datadog_reader.py @@ -0,0 +1,20 @@ +from typing import Final + +from datadog_reader import DdLogsReader +from datadog_reader import _DdAuthHeaders # pyright: ignore[reportPrivateUsage] # verifies private auth-header serialization + + +def test_failure_diagnostics_hide_credentials_without_changing_auth_headers() -> None: + api_key: Final = "test-datadog-api-secret" + app_key: Final = "test-datadog-app-secret" + reader: Final = DdLogsReader(site="datadoghq.com", api_key=api_key, app_key=app_key) + headers: Final = _DdAuthHeaders(api_key=api_key, app_key=app_key) + + for value in (reader, headers): + assert api_key not in repr(value) + assert app_key not in repr(value) + + assert headers.model_dump(by_alias=True) == { + "DD-API-KEY": api_key, + "DD-APPLICATION-KEY": app_key, + } From 64afa9d6eca8d238da02458a2769294eff93d08d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 15:36:34 -0700 Subject: [PATCH 050/105] test: isolate auto-router scenarios and clean partial setup --- .../test_auto_router_regressions_e2e.py | 239 +++++++++--------- 1 file changed, 120 insertions(+), 119 deletions(-) diff --git a/tests/e2e/router/test_auto_router_regressions_e2e.py b/tests/e2e/router/test_auto_router_regressions_e2e.py index 188db2a8eb5..374badcf5fc 100644 --- a/tests/e2e/router/test_auto_router_regressions_e2e.py +++ b/tests/e2e/router/test_auto_router_regressions_e2e.py @@ -41,6 +41,7 @@ which stores either the registered alias or the provider-prefixed form. import json import os from collections.abc import Iterator +from contextlib import ExitStack from dataclasses import dataclass from typing import Final @@ -120,19 +121,10 @@ class ResponsesApiResponse(BaseModel): @dataclass(frozen=True, slots=True) -class TagSplitDeployments: - """Scenario A mirrors the customer-shaped config from GitHub issue #36619: - plain deployment registered first, tier deployment and marker both tagged. - Scenario B flips both axes for GitHub issue #36621: marker registered first - and its tier deployment left untagged, so routing depends neither on - registration order nor on tier deployments carrying tags.""" - - tag_a: str - shared_a: str - tier_a: str - tag_b: str - shared_b: str - tier_b: str +class TagSplitDeployment: + tag: str + shared: str + tier: str @dataclass(frozen=True, slots=True) @@ -173,9 +165,7 @@ def _uniform_tier_config(tier_model: str) -> dict[str, object]: } -def _key_for( - proxy: ProxyClient, resources: ResourceManager, models: list[str], tag_filtering: bool = False -) -> str: +def _key_for(proxy: ProxyClient, resources: ResourceManager, models: list[str], tag_filtering: bool = False) -> str: key: Final = proxy.generate_key( KeyGenerateBody( models=models, @@ -211,46 +201,61 @@ def _assert_served_only_by(rows: list[SpendLogRow], allowed: frozenset[str], con ) -@pytest.fixture(scope="module") -def split(proxy: ProxyClient) -> Iterator[TagSplitDeployments]: +@pytest.fixture(scope="class") +def router_stack() -> Iterator[ExitStack]: + with ExitStack() as stack: + yield stack + + +def _register_models( + proxy: ProxyClient, stack: ExitStack, registrations: tuple[tuple[str, LiteLLMParamsBody], ...] +) -> None: + for name, params in registrations: + stack.callback(proxy.delete_model, proxy.create_model(name, params)) + + +def _tag_split(proxy: ProxyClient, stack: ExitStack, *, marker_first: bool) -> TagSplitDeployment: marker: Final = unique_marker() - deployments: Final = TagSplitDeployments( - tag_a=f"e2e-split-a-{marker}", - shared_a=f"e2e-autoroute-a-{marker}", - tier_a=f"e2e-tier-a-{marker}", - tag_b=f"e2e-split-b-{marker}", - shared_b=f"e2e-autoroute-b-{marker}", - tier_b=f"e2e-tier-b-{marker}", + named: Final = TagSplitDeployment( + tag=f"e2e-split-{marker}", + shared=f"e2e-autoroute-{marker}", + tier=f"e2e-tier-{marker}", ) anthropic_key: Final = _provider_key("ANTHROPIC_API_KEY") - marker_params_a: Final = LiteLLMParamsBody( - model="auto_router/complexity_router", - complexity_router_config=_uniform_tier_config(deployments.tier_a), - tags=[deployments.tag_a], + marker_registration: Final = ( + named.shared, + LiteLLMParamsBody( + model="auto_router/complexity_router", + complexity_router_config=_uniform_tier_config(named.tier), + tags=[named.tag], + ), ) - marker_params_b: Final = LiteLLMParamsBody( - model="auto_router/complexity_router", - complexity_router_config=_uniform_tier_config(deployments.tier_b), - tags=[deployments.tag_b], + tier_registration: Final = ( + named.tier, + LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key, tags=None if marker_first else [named.tag]), ) - registrations: Final[tuple[tuple[str, LiteLLMParamsBody], ...]] = ( - (deployments.shared_a, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)), - (deployments.tier_a, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key, tags=[deployments.tag_a])), - (deployments.shared_a, marker_params_a), - (deployments.shared_b, marker_params_b), - (deployments.tier_b, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key)), - (deployments.shared_b, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)), + plain_registration: Final = (named.shared, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)) + registrations: Final = ( + (marker_registration, tier_registration, plain_registration) + if marker_first + else (plain_registration, tier_registration, marker_registration) ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield deployments - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, stack, registrations) + return named -@pytest.fixture(scope="module") -def zero_priced_alias(proxy: ProxyClient) -> Iterator[ZeroPricedAlias]: +@pytest.fixture(scope="class") +def plain_first_split(proxy: ProxyClient, router_stack: ExitStack) -> TagSplitDeployment: + return _tag_split(proxy, router_stack, marker_first=False) + + +@pytest.fixture(scope="class") +def marker_first_split(proxy: ProxyClient, router_stack: ExitStack) -> TagSplitDeployment: + return _tag_split(proxy, router_stack, marker_first=True) + + +@pytest.fixture(scope="class") +def zero_priced_alias(proxy: ProxyClient, router_stack: ExitStack) -> ZeroPricedAlias: marker: Final = unique_marker() named: Final = ZeroPricedAlias(alias=f"e2e-priced-alias-{marker}", tier=f"e2e-priced-tier-{marker}") alias_params: Final = LiteLLMParamsBody( @@ -263,16 +268,12 @@ def zero_priced_alias(proxy: ProxyClient) -> Iterator[ZeroPricedAlias]: (named.tier, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.alias, alias_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def heuristic_split(proxy: ProxyClient) -> Iterator[HeuristicSplit]: +@pytest.fixture(scope="class") +def heuristic_split(proxy: ProxyClient, router_stack: ExitStack) -> HeuristicSplit: marker: Final = unique_marker() named: Final = HeuristicSplit( alias=f"e2e-heuristic-router-{marker}", @@ -289,16 +290,12 @@ def heuristic_split(proxy: ProxyClient) -> Iterator[HeuristicSplit]: (named.strong, LiteLLMParamsBody(model=STRONG_MODEL, api_key=_provider_key("OPENAI_API_KEY"))), (named.alias, LiteLLMParamsBody(model="auto_router/complexity_router", complexity_router_config=config)), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def semantic_auto_router(proxy: ProxyClient) -> Iterator[SemanticAutoRouter]: +@pytest.fixture(scope="class") +def semantic_auto_router(proxy: ProxyClient, router_stack: ExitStack) -> SemanticAutoRouter: marker: Final = unique_marker() named: Final = SemanticAutoRouter( marker=f"e2e-semantic-router-{marker}", @@ -321,16 +318,12 @@ def semantic_auto_router(proxy: ProxyClient) -> Iterator[SemanticAutoRouter]: (named.fallback, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.marker, marker_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def credentialed_alias(proxy: ProxyClient) -> Iterator[CredentialedAlias]: +@pytest.fixture(scope="class") +def credentialed_alias(proxy: ProxyClient, router_stack: ExitStack) -> CredentialedAlias: marker: Final = unique_marker() named: Final = CredentialedAlias(alias=f"e2e-cred-alias-{marker}", tier=f"e2e-cred-tier-{marker}") alias_params: Final = LiteLLMParamsBody( @@ -342,104 +335,110 @@ def credentialed_alias(proxy: ProxyClient) -> Iterator[CredentialedAlias]: (named.tier, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.alias, alias_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named class TestTagSplitRouting: @pytest.mark.covers("reliability.routing.tagged_marker.request_tag_selects_marker") def test_body_tagged_chat_routes_through_the_marker_to_its_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36619: with tag filtering on, a chat request whose body metadata tags match the tagged marker under a shared model name is answered by the marker's tier deployment, not by the plain deployment that was registered under the name first.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - chat: Final = unwrap(proxy.chat(key, _hello_chat_body(split.shared_a, tags=[split.tag_a]))) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + chat: Final = unwrap(proxy.chat(key, _hello_chat_body(plain_first_split.shared, tags=[plain_first_split.tag]))) assert chat.choices, "tagged chat through the shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "body-tagged chat on the shared name") + _assert_served_only_by(rows, CHEAP_SERVED | {plain_first_split.tier}, "body-tagged chat on the shared name") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_chat_is_always_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36620: untagged chat requests to the shared name succeed on every call and are all served by the plain deployment; the tagged marker never captures them, so no intermittent auto-router errors and no tier hijacking.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) for _ in range(5): - chat = unwrap(proxy.chat(key, _hello_chat_body(split.shared_a))) + chat = unwrap(proxy.chat(key, _hello_chat_body(plain_first_split.shared))) assert chat.choices, "untagged chat through the shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=5) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged chat on the shared name") + _assert_served_only_by(rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged chat on the shared name") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_messages_is_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36620 on the /v1/messages surface: an untagged Anthropic-native request to the shared name is served by the plain deployment, not captured by the tagged marker.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - answer: Final = unwrap(proxy.messages(key, _hello_messages_body(split.shared_a))) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + answer: Final = unwrap(proxy.messages(key, _hello_messages_body(plain_first_split.shared))) assert answer.content or answer.choices, "untagged /v1/messages returned neither content nor choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged /v1/messages on the shared name") + _assert_served_only_by( + rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged /v1/messages on the shared name" + ) class TestUntaggedTierDeployments: @pytest.mark.covers("reliability.routing.tagged_marker.header_tag_selects_marker") def test_header_tagged_messages_routes_through_the_marker_to_an_untagged_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36621: a /v1/messages request tagged only via the x-litellm-tags header selects the tagged marker, and the rewrite still lands on the tier deployment even though that deployment carries no tags, because the marker consumed the routing tags.""" - key: Final = _key_for(proxy, resources, [split.shared_b, split.tier_b], tag_filtering=True) - headers: Final = TaggedAnthropicHeaders(authorization=f"Bearer {key}", x_litellm_tags=split.tag_b) + key: Final = _key_for( + proxy, resources, [marker_first_split.shared, marker_first_split.tier], tag_filtering=True + ) + headers: Final = TaggedAnthropicHeaders(authorization=f"Bearer {key}", x_litellm_tags=marker_first_split.tag) answer: Final = unwrap( proxy.transport.post( "/v1/messages", headers=headers, - json=_hello_messages_body(split.shared_b), + json=_hello_messages_body(marker_first_split.shared), response_type=AnthropicMessagesResponse, ) ) assert answer.content or answer.choices, "header-tagged /v1/messages returned neither content nor choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_b}, "header-tagged /v1/messages on the shared name") + _assert_served_only_by( + rows, CHEAP_SERVED | {marker_first_split.tier}, "header-tagged /v1/messages on the shared name" + ) @pytest.mark.covers("reliability.routing.tagged_marker.untagged_tier_deployments_still_served") def test_body_tagged_chat_reaches_the_untagged_tier_after_marker_rewrite( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """Pins the tag-consumption half of GitHub issue #36621: after the tagged marker rewrites the request to its tier model, the consumed routing tags no longer constrain deployment selection, so the untagged tier deployment serves the request instead of a strict-tag denial.""" - key: Final = _key_for(proxy, resources, [split.shared_b, split.tier_b], tag_filtering=True) - chat: Final = unwrap(proxy.chat(key, _hello_chat_body(split.shared_b, tags=[split.tag_b]))) + key: Final = _key_for( + proxy, resources, [marker_first_split.shared, marker_first_split.tier], tag_filtering=True + ) + chat: Final = unwrap( + proxy.chat(key, _hello_chat_body(marker_first_split.shared, tags=[marker_first_split.tag])) + ) assert chat.choices, "body-tagged chat through the marker-first shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_b}, "body-tagged chat with untagged tier") + _assert_served_only_by(rows, CHEAP_SERVED | {marker_first_split.tier}, "body-tagged chat with untagged tier") @pytest.mark.covers("reliability.routing.tagged_marker.tag_semantics_stay_strict") def test_tagged_call_straight_at_an_untagged_deployment_stays_denied( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """The tag-consumption fix must not loosen strict tag semantics: a tagged request aimed directly at an untagged deployment (no marker involved) is still rejected with the 401 tags-configuration error.""" - key: Final = _key_for(proxy, resources, [split.tier_b], tag_filtering=True) - result: Final = proxy.chat(key, _hello_chat_body(split.tier_b, tags=[split.tag_b])) + key: Final = _key_for(proxy, resources, [marker_first_split.tier], tag_filtering=True) + result: Final = proxy.chat(key, _hello_chat_body(marker_first_split.tier, tags=[marker_first_split.tag])) assert isinstance(result, UnauthorizedError), ( f"expected the tagged direct call to an untagged deployment to be denied with 401, got {result}" ) @@ -451,37 +450,39 @@ class TestUntaggedTierDeployments: class TestResponsesApiTagRouting: @pytest.mark.covers("reliability.routing.tagged_marker.responses_input_routes_through_marker") def test_header_tagged_responses_with_string_input_routes_to_the_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the /v1/responses surface of the tag split (GitHub issues #36620/#36621): a /v1/responses request with string input, tagged via the x-litellm-tags header, succeeds and routes through the tagged marker to its tier.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - headers: Final = TaggedAuthHeaders(authorization=f"Bearer {key}", x_litellm_tags=split.tag_a) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + headers: Final = TaggedAuthHeaders(authorization=f"Bearer {key}", x_litellm_tags=plain_first_split.tag) body: Final = ResponsesBody( - model=split.shared_a, input=f"say hello {unique_marker()}", max_output_tokens=64 + model=plain_first_split.shared, input=f"say hello {unique_marker()}", max_output_tokens=64 ) answer: Final = unwrap( proxy.transport.post("/v1/responses", headers=headers, json=body, response_type=ResponsesApiResponse) ) assert answer.id, "header-tagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "header-tagged /v1/responses string input") + _assert_served_only_by( + rows, CHEAP_SERVED | {plain_first_split.tier}, "header-tagged /v1/responses string input" + ) @pytest.mark.covers("reliability.routing.tagged_marker.responses_input_routes_through_marker") def test_body_tagged_responses_with_list_input_routes_to_the_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the body-tag and list-input combination of the same split: /v1/responses with litellm_metadata.tags and structured input items routes through the tagged marker to its tier.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) body: Final = ResponsesBody( - model=split.shared_a, + model=plain_first_split.shared, input=[ResponsesInputItem(role="user", content=f"say hello {unique_marker()}")], max_output_tokens=64, - litellm_metadata=ResponsesTagMetadata(tags=[split.tag_a]), + litellm_metadata=ResponsesTagMetadata(tags=[plain_first_split.tag]), ) answer: Final = unwrap( proxy.transport.post( @@ -493,18 +494,18 @@ class TestResponsesApiTagRouting: ) assert answer.id, "body-tagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "body-tagged /v1/responses list input") + _assert_served_only_by(rows, CHEAP_SERVED | {plain_first_split.tier}, "body-tagged /v1/responses list input") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_responses_is_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the untagged half of the /v1/responses tag split: an untagged request to the shared name is served by the plain deployment, matching the chat and messages surfaces.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) body: Final = ResponsesBody( - model=split.shared_a, input=f"say hello {unique_marker()}", max_output_tokens=64 + model=plain_first_split.shared, input=f"say hello {unique_marker()}", max_output_tokens=64 ) answer: Final = unwrap( proxy.transport.post( @@ -516,7 +517,9 @@ class TestResponsesApiTagRouting: ) assert answer.id, "untagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged /v1/responses on the shared name") + _assert_served_only_by( + rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged /v1/responses on the shared name" + ) class TestStrategyAliasPricing: @@ -551,9 +554,7 @@ class TestComplexityHeuristicScope: while the accompanying ~2KB agent system prompt is packed with enough reasoning and complexity keywords that scoring the combined text lands in REASONING; only ask-only scoring keeps this on the cheap tier.""" - key: Final = _key_for( - proxy, resources, [heuristic_split.alias, heuristic_split.cheap, heuristic_split.strong] - ) + key: Final = _key_for(proxy, resources, [heuristic_split.alias, heuristic_split.cheap, heuristic_split.strong]) body: Final = ChatBody( model=heuristic_split.alias, messages=[ From 17e3dc1c791236bb59bbafa038a02611007d6560 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 15:57:08 -0700 Subject: [PATCH 051/105] refactor(ui): move nonReasoningTierFields into its own module Upstream grew ClassificationMethodConfig.tsx to 783 lines, so the 14 lines this PR added there pushed the merge result past the 800-line max-lines cap. The helper is standalone logic with its own unit tests, so it moves out rather than the cap moving up. --- .../add_model/ClassificationMethodConfig.tsx | 14 +------------- .../add_model/nonReasoningTierFields.test.ts | 2 +- .../components/add_model/nonReasoningTierFields.ts | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 61706985654..594c6d022ce 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -17,6 +17,7 @@ import ClassifierReasoningEffortSelect from "./ClassifierReasoningEffortSelect"; import ClassifierCircuitBreakerConfig from "./ClassifierCircuitBreakerConfig"; import ClassifierVisionConfig from "./ClassifierVisionConfig"; import type { ReasoningEffort } from "./complexity_router_tiers"; +import { nonReasoningTierFields } from "./nonReasoningTierFields"; import { useComplexityScorerDefaults } from "@/app/(dashboard)/hooks/autoRouter/useComplexityScorerDefaults"; import { ClassificationFrequency, @@ -237,19 +238,6 @@ const ClassifierTypeRadios: React.FC<{ ); }; -/** The NON_REASONING keys a classifier switch carries forward, or clears for a classifier that - * cannot emit the tier. Leaving them set there is a config the backend refuses on save. */ -export const nonReasoningTierFields = ( - classifierType: ClassifierType, - value: ComplexityRouterConfigValue, -): Pick => { - if (classifierType === "llm") { - return { enable_non_reasoning_tier: value.enable_non_reasoning_tier, tiers: value.tiers }; - } - const { NON_REASONING: _cleared, ...keptTiers } = value.tiers; - return { enable_non_reasoning_tier: undefined, tiers: keptTiers }; -}; - const ClassificationMethodConfig: React.FC = ({ value, onChange, diff --git a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts index 86e50130e43..3f986d57dd9 100644 --- a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; -import { nonReasoningTierFields } from "./ClassificationMethodConfig"; +import { nonReasoningTierFields } from "./nonReasoningTierFields"; const enabledValue: ComplexityRouterConfigValue = { classifier_type: "llm", diff --git a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts new file mode 100644 index 00000000000..2ea7a3f3b97 --- /dev/null +++ b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts @@ -0,0 +1,14 @@ +import type { ClassifierType, ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; + +/** The NON_REASONING keys a classifier switch carries forward, or clears for a classifier that + * cannot emit the tier. Leaving them set there is a config the backend refuses on save. */ +export const nonReasoningTierFields = ( + classifierType: ClassifierType, + value: ComplexityRouterConfigValue, +): Pick => { + if (classifierType === "llm") { + return { enable_non_reasoning_tier: value.enable_non_reasoning_tier, tiers: value.tiers }; + } + const { NON_REASONING: _cleared, ...keptTiers } = value.tiers; + return { enable_non_reasoning_tier: undefined, tiers: keptTiers }; +}; From a4f865b1bec11278f3d98f84e9a3f335a714c6dc Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 16:04:16 -0700 Subject: [PATCH 052/105] refactor(ui): extract hydrateBuiltInTiers so the edit modal stays under max-lines Upstream's edit_auto_router_modal.tsx sits at 799 countable lines, one under the 800 cap, so this PR's 11 added lines put the merge result over. The built-in tier hydration moves next to its sibling hydrators in build_complexity_router_config, which is where hydrateCustomTierSet and hydrateTierLabels already live. --- .../build_complexity_router_config.ts | 20 +++++++++++++++++++ .../edit_auto_router_modal.tsx | 20 +++++-------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index 0fa2268f2ed..5b53941bc10 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts @@ -385,6 +385,26 @@ export const customTierWireFields = ( }; }; +/** The built-in tier pools and the opt-in flag, read back from a stored config. `tiers` is + * rewritten wholesale on save, so a stored tier this misses is deleted by any unrelated edit. */ +export const hydrateBuiltInTiers = ( + storedTiers: Partial> | undefined, + storedFlag: boolean | undefined, +): { tiers: ComplexityTiers; enable_non_reasoning_tier: boolean } => { + const nonReasoning: string[] = normalizeTierModels(storedTiers?.NON_REASONING); + const enable_non_reasoning_tier: boolean = storedFlag === true || nonReasoning.length > 0; + return { + enable_non_reasoning_tier, + tiers: { + SIMPLE: normalizeTierModels(storedTiers?.SIMPLE), + MEDIUM: normalizeTierModels(storedTiers?.MEDIUM), + COMPLEX: normalizeTierModels(storedTiers?.COMPLEX), + REASONING: normalizeTierModels(storedTiers?.REASONING), + ...(enable_non_reasoning_tier && { NON_REASONING: nonReasoning }), + }, + }; +}; + // plan_mode_min_tier rides the strip list because the base payload carries it as a row id; // customTierWireFields re-emits it as the row's name, and an unresolvable floor stays off. const CUSTOM_TIER_STRIPPED_KEYS: readonly string[] = [...CUSTOM_TIER_OMITTED_KEYS, "plan_mode_min_tier"]; diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx index 76082a9444f..8a62e86e842 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx @@ -14,7 +14,7 @@ import ModelChoiceCombobox, { type ModelChoice } from "../add_model/ModelChoiceC import { modelAvailableCall, modelPatchUpdateCall, validateAutoRouterConfig } from "../networking"; import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models"; import RouterConfigBuilder from "../add_model/RouterConfigBuilder"; -import { hydrateTierModelParams, normalizeTierModels } from "../add_model/complexity_router_tiers"; +import { hydrateTierModelParams } from "../add_model/complexity_router_tiers"; import { type ActiveTierSet, CUSTOM_TIER_OMITTED_KEYS, @@ -34,6 +34,7 @@ import { getSemanticConfigError, getPlanModeTierError, getTierLabelsError, + hydrateBuiltInTiers, hydrateCustomTierSet, hydratePlanModeMinTier, hydrateTierLabels, @@ -139,21 +140,10 @@ export const hydrateComplexityRouterConfig = ( parsedConfig: StoredComplexityRouterConfig, complexityRouterDefaultModel: string | null | undefined, ): ComplexityRouterConfigValue => { - // `tiers` is rewritten wholesale on save, so a stored tier this misses is deleted by any edit, - // including one made for an unrelated reason. Hence reading both back rather than assuming four. - const storedNonReasoning: string[] = normalizeTierModels(parsedConfig.tiers?.NON_REASONING); - const enable_non_reasoning_tier: boolean = - parsedConfig.enable_non_reasoning_tier === true || storedNonReasoning.length > 0; - const hydratedTiers: ComplexityTiers = { - SIMPLE: normalizeTierModels(parsedConfig.tiers?.SIMPLE), - MEDIUM: normalizeTierModels(parsedConfig.tiers?.MEDIUM), - COMPLEX: normalizeTierModels(parsedConfig.tiers?.COMPLEX), - REASONING: normalizeTierModels(parsedConfig.tiers?.REASONING), - ...(enable_non_reasoning_tier && { NON_REASONING: storedNonReasoning }), - }; - + const builtIn = hydrateBuiltInTiers(parsedConfig.tiers, parsedConfig.enable_non_reasoning_tier); + const { tiers: hydratedTiers, enable_non_reasoning_tier } = builtIn; const custom_tier_set = hydrateCustomTierSet(parsedConfig); - const activeTiers = { tiers: hydratedTiers, enable_non_reasoning_tier, custom_tier_set }; + const activeTiers = { ...builtIn, custom_tier_set }; return { tiers: hydratedTiers, From 7a6c0cbf08d9c564697c4df7d70d661b0e6241b9 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:43:28 -0700 Subject: [PATCH 053/105] fix(spend-tracking): drop the owner of a digest shared by several users and back off failed scans --- .../spend_tracking/key_metadata_recovery.py | 54 +++++++++++-------- .../test_key_metadata_recovery.py | 49 +++++++++++++++-- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index 319050b87cd..d555da99397 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -38,20 +38,23 @@ ORDER BY token, deleted_at DESC _SPEND_LOG_ALIAS_SQL: Final = """ SELECT DISTINCT ON (api_key) api_key AS digest, - metadata->>'user_api_key_alias' AS key_alias, - COALESCE(NULLIF(team_id, ''), metadata->>'user_api_key_team_id') AS team_id, - COALESCE(NULLIF("user", ''), metadata->>'user_api_key_user_id') AS user_id -FROM "LiteLLM_SpendLogs" -WHERE api_key = ANY($1::text[]) - AND "startTime" >= $2::timestamp - AND "startTime" < $3::timestamp - AND COALESCE( - metadata->>'user_api_key_alias', - NULLIF("user", ''), - metadata->>'user_api_key_user_id', - NULLIF(team_id, ''), - metadata->>'user_api_key_team_id' - ) IS NOT NULL + key_alias, + team_id, + user_id, + MIN(user_id) OVER (PARTITION BY api_key) AS first_owner, + MAX(user_id) OVER (PARTITION BY api_key) AS last_owner +FROM ( + SELECT api_key, + "startTime", + NULLIF(metadata->>'user_api_key_alias', '') AS key_alias, + COALESCE(NULLIF(team_id, ''), NULLIF(metadata->>'user_api_key_team_id', '')) AS team_id, + COALESCE(NULLIF("user", ''), NULLIF(metadata->>'user_api_key_user_id', '')) AS user_id + FROM "LiteLLM_SpendLogs" + WHERE api_key = ANY($1::text[]) + AND "startTime" >= $2::timestamp + AND "startTime" < $3::timestamp +) named +WHERE COALESCE(key_alias, user_id, team_id) IS NOT NULL ORDER BY api_key, "startTime" DESC """ @@ -72,7 +75,16 @@ class _TokenDigestRow(BaseModel): user_id: str | None = None +class _SpendLogDigestRow(_TokenDigestRow): + first_owner: str | None = None + last_owner: str | None = None + + def unanimous_owner(self) -> str | None: + return self.user_id if self.first_owner == self.last_owner else None + + _TOKEN_DIGEST_ROWS: Final = TypeAdapter(tuple[_TokenDigestRow, ...]) +_SPEND_LOG_DIGEST_ROWS: Final = TypeAdapter(tuple[_SpendLogDigestRow, ...]) _CACHED_KEY_METADATA: Final = TypeAdapter(KeyMetadataDict) _SPEND_LOG_METADATA_CACHE: Final = InMemoryCache( max_size_in_memory=SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, @@ -235,9 +247,10 @@ async def _query_spend_log_metadata( return None return MappingProxyType( { - row.digest: KeyMetadataDict(key_alias=row.key_alias, team_id=row.team_id, user_id=row.user_id) - for row in _TOKEN_DIGEST_ROWS.validate_python(rows) - if row.digest in digests and (row.key_alias or row.user_id or row.team_id) + row.digest: KeyMetadataDict(key_alias=row.key_alias, team_id=row.team_id, user_id=owner) + for row in _SPEND_LOG_DIGEST_ROWS.validate_python(rows) + for owner in (row.unanimous_owner(),) + if row.digest in digests and (row.key_alias or owner or row.team_id) } ) @@ -270,11 +283,10 @@ async def _spend_log_metadata_one_query_at_a_time( fresh: Final = ( await _query_spend_log_metadata(prisma_client, pending, window) if pending else _EMPTY_KEY_METADATA ) - if fresh is None: - return settled + found: Final = fresh if fresh is not None else _EMPTY_KEY_METADATA for digest in pending: - _remember_spend_log_metadata(cache, digest, window, fresh.get(digest)) - return MappingProxyType({**settled, **fresh}) + _remember_spend_log_metadata(cache, digest, window, found.get(digest)) + return MappingProxyType({**settled, **found}) async def recover_key_metadata_from_spend_logs( diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 5b45777502a..682b11f5bb7 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -386,20 +386,63 @@ async def test_recover_key_metadata_from_spend_logs_rescans_when_the_window_chan @pytest.mark.asyncio -async def test_recover_key_metadata_from_spend_logs_does_not_cache_a_failed_query(): +async def test_recover_key_metadata_from_spend_logs_retries_a_failed_query_only_after_the_miss_ttl(): digest = hash_token("cli-session-retry") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) - cache = InMemoryCache() + cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) mock_prisma = MagicMock() - mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("db down")) + mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("statement timeout")) + started = time.time() assert await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) == {} mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(digest, "back-online", None, None)]) + assert await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) == {} + mock_prisma.db.query_raw.assert_not_awaited() + miss_key = next(key for key in cache.ttl_dict if digest in key and not key.endswith(":missed-before")) + assert cache.ttl_dict[miss_key] - started <= SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL + 1 + cache.ttl_dict[miss_key] = time.time() - 1 + result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) assert result[digest]["key_alias"] == "back-online" +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_drops_the_owner_of_a_digest_shared_by_several_users(): + shared_ui_digest = hash_token("ui-token") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs( + [ + { + **_digest_row(shared_ui_digest, "ui-token", "litellm-dashboard", "bob"), + "first_owner": "alice", + "last_owner": "bob", + } + ] + ) + + result = await recover_key_metadata_from_spend_logs( + mock_prisma, {shared_ui_digest}, window, cache=InMemoryCache() + ) + + assert result[shared_ui_digest] == {"key_alias": "ui-token", "team_id": "litellm-dashboard", "user_id": None} + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_keeps_the_owner_when_every_named_row_agrees(): + digest = hash_token("cli-session-one-owner") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + mock_prisma.db.query_raw = _query_raw_spend_logs( + [{**_digest_row(digest, None, None, "carol"), "first_owner": "carol", "last_owner": "carol"}] + ) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=InMemoryCache()) + + assert result[digest]["user_id"] == "carol" + + @pytest.mark.asyncio async def test_recover_key_metadata_from_spend_logs_forgets_a_miss_long_before_a_hit(): found = hash_token("cli-session-found") From 253600fc6144747c1ffd954c3ebdfadd686adc2e Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 16:52:56 -0700 Subject: [PATCH 054/105] test: wait for requested guardrail propagation --- tests/e2e/guardrails/guardrails_client.py | 22 ++++++- .../e2e/guardrails/test_guardrails_client.py | 63 +++++++++++++++++++ .../test_tool_permission_guardrail_e2e.py | 24 ++++--- 3 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 tests/e2e/guardrails/test_guardrails_client.py diff --git a/tests/e2e/guardrails/guardrails_client.py b/tests/e2e/guardrails/guardrails_client.py index 1f55a0f9a56..3770de0c6d5 100644 --- a/tests/e2e/guardrails/guardrails_client.py +++ b/tests/e2e/guardrails/guardrails_client.py @@ -7,7 +7,7 @@ from __future__ import annotations import time from collections.abc import Callable from dataclasses import dataclass -from typing import Literal +from typing import Final, Literal from e2e_config import POLL_INTERVAL, POLL_TIMEOUT, settle_propagation, unique_marker from e2e_http import NoBody, Result, StreamingResponse, Success, unwrap @@ -405,6 +405,26 @@ def build_client(proxy: ProxyClient) -> GuardrailsClient: return GuardrailsClient(proxy=proxy) +def poll_until_guardrail_applied( + call: Callable[[], StreamingResponse], + guardrail_name: str, + *, + timeout: float = POLL_TIMEOUT, + interval: float = POLL_INTERVAL, + now: Callable[[], float] = time.monotonic, + sleep: Callable[[float], None] = time.sleep, +) -> StreamingResponse: + deadline: Final = now() + timeout + while ( + (result := call()).ok + and guardrail_name + not in (name.strip() for name in result.headers.get("x-litellm-applied-guardrails", "").split(",")) + and (remaining := deadline - now()) > 0 + ): + sleep(min(interval, remaining)) + return result + + def poll_until_blocked[R: BaseModel](call: Callable[[], Result[R]]) -> Result[R]: """Retry a call that a guardrail should reject until it is, returning the last result. diff --git a/tests/e2e/guardrails/test_guardrails_client.py b/tests/e2e/guardrails/test_guardrails_client.py new file mode 100644 index 00000000000..aab163d86d2 --- /dev/null +++ b/tests/e2e/guardrails/test_guardrails_client.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from itertools import chain, repeat +from typing import Final + +import pytest + +from e2e_http import StreamingResponse +from guardrails_client import poll_until_guardrail_applied + + +@dataclass +class Clock: + elapsed: float = 0.0 + + def now(self) -> float: + return self.elapsed + + def sleep(self, seconds: float) -> None: + self.elapsed += seconds + + +def _response(applied: str, status: int = 200) -> StreamingResponse: + return StreamingResponse(status_code=status, body="{}", headers={"x-litellm-applied-guardrails": applied}) + + +def test_waits_for_requested_guardrail_after_an_unrelated_global_guardrail() -> None: + clock: Final = Clock() + expected: Final = _response("global-filter, tool-permission") + responses: Final = iter((_response("global-filter"), expected)) + + result: Final = poll_until_guardrail_applied( + lambda: next(responses), "tool-permission", timeout=5, interval=2, now=clock.now, sleep=clock.sleep + ) + + assert result is expected + assert clock.elapsed == 2 + + +@pytest.mark.parametrize("applied", ("", "global-filter", "tool-permission-sibling")) +def test_missing_exact_guardrail_returns_failure_evidence_at_deadline(applied: str) -> None: + clock: Final = Clock() + missing: Final = _response(applied) + + result: Final = poll_until_guardrail_applied( + lambda: missing, "tool-permission", timeout=5, interval=2, now=clock.now, sleep=clock.sleep + ) + + assert result is missing + assert clock.elapsed == 5 + + +@pytest.mark.parametrize("status", (400, 401, 429, 500)) +def test_http_failure_is_not_hidden_by_a_later_success(status: int) -> None: + clock: Final = Clock() + failed: Final = _response("", status) + responses: Final = iter(chain((failed,), repeat(_response("tool-permission")))) + + result: Final = poll_until_guardrail_applied( + lambda: next(responses), "tool-permission", timeout=5, interval=2, now=clock.now, sleep=clock.sleep + ) + + assert result is failed + assert clock.elapsed == 0 diff --git a/tests/e2e/guardrails/test_tool_permission_guardrail_e2e.py b/tests/e2e/guardrails/test_tool_permission_guardrail_e2e.py index 9ef3650625c..8d1047e53c7 100644 --- a/tests/e2e/guardrails/test_tool_permission_guardrail_e2e.py +++ b/tests/e2e/guardrails/test_tool_permission_guardrail_e2e.py @@ -30,6 +30,7 @@ from guardrails_client import ( ToolPermissionParamsBody, ToolPermissionRuleBody, poll_until_blocked, + poll_until_guardrail_applied, ) from lifecycle import ResourceManager from models import ChatResponse, ChatTool, ChatToolFunction @@ -84,8 +85,8 @@ def _register_tool_permission(client: GuardrailsClient, resources: ResourceManag resources.defer(lambda: client.delete_guardrail(guardrail_id)) -def _applied_guardrails(outcome: StreamingResponse) -> str: - return outcome.headers.get("x-litellm-applied-guardrails", "") +def _applied_guardrails(outcome: StreamingResponse) -> tuple[str, ...]: + return tuple(name.strip() for name in outcome.headers.get("x-litellm-applied-guardrails", "").split(",")) def _tool_call_names(response: ChatResponse) -> tuple[str, ...]: @@ -144,14 +145,17 @@ class TestToolPermissionPreCall: name = f"e2e-toolperm-allow-{unique_marker()}" _register_tool_permission(client, resources, name=name) - outcome = client.chat_raw( - scoped_key, - MODEL, - TOOL_PROMPT, - guardrails=[name], - max_tokens=128, - tools=[ALLOWED_TOOL], - tool_choice="required", + outcome = poll_until_guardrail_applied( + lambda: client.chat_raw( + scoped_key, + MODEL, + TOOL_PROMPT, + guardrails=[name], + max_tokens=128, + tools=[ALLOWED_TOOL], + tool_choice="required", + ), + name, ) assert outcome.ok, f"the permitted tool must be served, got {outcome.status_code}: {outcome.body[:400]}" From 9d7e09e4e8f6463516af1c0a5844d4a5dc7c2c55 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 16:53:52 -0700 Subject: [PATCH 055/105] fix(ui): release the plan-mode floor when the non-reasoning tier is cleared Turning the tier off, or switching to a classifier that cannot emit it, dropped the flag and the pool but left plan_mode_min_tier naming a tier that is no longer active. The backend rejects that on save, and the switch is disabled after a classifier change, so the operator had no way to clear it. Both paths now release the floor when it points at the cleared tier. An orphaned keyword rule is left alone on purpose: getKeywordTierRulesError already names it at the save gate, which is how a removed custom tier behaves. --- .../add_model/NonReasoningTierToggle.tsx | 15 ++++++++---- .../add_model/nonReasoningTierFields.test.ts | 19 +++++++++++++++ .../add_model/nonReasoningTierFields.ts | 24 +++++++++++++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx index caadd79039d..5ca0d5517af 100644 --- a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx @@ -12,11 +12,16 @@ const NonReasoningTierToggle: React.FC<{ }> = ({ value, onChange, available }) => { const handleToggle = (enabled: boolean): void => { const { NON_REASONING: existingPool, ...keptTiers } = value.tiers; - const next: ComplexityRouterConfigValue = { - ...value, - enable_non_reasoning_tier: enabled ? true : undefined, - tiers: enabled ? { ...keptTiers, NON_REASONING: existingPool ?? [] } : keptTiers, - }; + // Turning it off must also release the plan-mode floor, which the backend rejects while it + // names an inactive tier. An orphaned keyword rule is left for the save gate to name. + const next: ComplexityRouterConfigValue = enabled + ? { ...value, enable_non_reasoning_tier: true, tiers: { ...keptTiers, NON_REASONING: existingPool ?? [] } } + : { + ...value, + enable_non_reasoning_tier: undefined, + tiers: keptTiers, + plan_mode_min_tier: value.plan_mode_min_tier === "NON_REASONING" ? undefined : value.plan_mode_min_tier, + }; onChange(next); }; diff --git a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts index 3f986d57dd9..c09c859ac27 100644 --- a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.test.ts @@ -50,3 +50,22 @@ describe("nonReasoningTierFields", () => { }); }); }); + +describe("stale references to the cleared tier", () => { + const withFloorOnTierZero: ComplexityRouterConfigValue = { ...enabledValue, plan_mode_min_tier: "NON_REASONING" }; + + it("releases a plan-mode floor pointing at the tier it just cleared", () => { + // The backend rejects a floor naming an inactive tier, and the switch is disabled once the + // classifier changes, so a floor left behind is a config the operator cannot save or undo. + expect(nonReasoningTierFields("heuristic", withFloorOnTierZero).plan_mode_min_tier).toBeUndefined(); + }); + + it("leaves a floor on another tier alone", () => { + const floorOnComplex: ComplexityRouterConfigValue = { ...enabledValue, plan_mode_min_tier: "COMPLEX" }; + expect(nonReasoningTierFields("heuristic", floorOnComplex).plan_mode_min_tier).toBe("COMPLEX"); + }); + + it("keeps the floor while the classifier can still emit the tier", () => { + expect(nonReasoningTierFields("llm", withFloorOnTierZero).plan_mode_min_tier).toBe("NON_REASONING"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts index 2ea7a3f3b97..92a665a199c 100644 --- a/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts +++ b/ui/litellm-dashboard/src/components/add_model/nonReasoningTierFields.ts @@ -1,14 +1,28 @@ import type { ClassifierType, ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; +const NON_REASONING = "NON_REASONING"; + /** The NON_REASONING keys a classifier switch carries forward, or clears for a classifier that - * cannot emit the tier. Leaving them set there is a config the backend refuses on save. */ + * cannot emit the tier. Leaving them set there is a config the backend refuses on save. The floor + * goes with them: it is rejected on save while it names an inactive tier, and the switch is + * disabled once the classifier changes, so the operator could not clear it themselves. + * An orphaned keyword rule is left for getKeywordTierRulesError to name, matching how a removed + * custom tier already behaves. */ export const nonReasoningTierFields = ( classifierType: ClassifierType, value: ComplexityRouterConfigValue, -): Pick => { +): Pick => { if (classifierType === "llm") { - return { enable_non_reasoning_tier: value.enable_non_reasoning_tier, tiers: value.tiers }; + return { + enable_non_reasoning_tier: value.enable_non_reasoning_tier, + tiers: value.tiers, + plan_mode_min_tier: value.plan_mode_min_tier, + }; } - const { NON_REASONING: _cleared, ...keptTiers } = value.tiers; - return { enable_non_reasoning_tier: undefined, tiers: keptTiers }; + const { [NON_REASONING]: _cleared, ...tiers } = value.tiers; + return { + enable_non_reasoning_tier: undefined, + tiers, + plan_mode_min_tier: value.plan_mode_min_tier === NON_REASONING ? undefined : value.plan_mode_min_tier, + }; }; From b8be30219c5e5dea4343b3a924b89494e7350303 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 17:02:00 -0700 Subject: [PATCH 056/105] test: stop guardrail retries at the polling deadline --- tests/e2e/guardrails/guardrails_client.py | 7 +++++-- tests/e2e/guardrails/test_guardrails_client.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/e2e/guardrails/guardrails_client.py b/tests/e2e/guardrails/guardrails_client.py index 3770de0c6d5..ed112a79b9b 100644 --- a/tests/e2e/guardrails/guardrails_client.py +++ b/tests/e2e/guardrails/guardrails_client.py @@ -415,13 +415,16 @@ def poll_until_guardrail_applied( sleep: Callable[[float], None] = time.sleep, ) -> StreamingResponse: deadline: Final = now() + timeout + if not (result := call()).ok: + return result while ( - (result := call()).ok - and guardrail_name + guardrail_name not in (name.strip() for name in result.headers.get("x-litellm-applied-guardrails", "").split(",")) and (remaining := deadline - now()) > 0 ): sleep(min(interval, remaining)) + if now() >= deadline or not (result := call()).ok: + break return result diff --git a/tests/e2e/guardrails/test_guardrails_client.py b/tests/e2e/guardrails/test_guardrails_client.py index aab163d86d2..423c2ede599 100644 --- a/tests/e2e/guardrails/test_guardrails_client.py +++ b/tests/e2e/guardrails/test_guardrails_client.py @@ -40,13 +40,16 @@ def test_waits_for_requested_guardrail_after_an_unrelated_global_guardrail() -> def test_missing_exact_guardrail_returns_failure_evidence_at_deadline(applied: str) -> None: clock: Final = Clock() missing: Final = _response(applied) + responses: Final = iter((missing, missing, missing)) result: Final = poll_until_guardrail_applied( - lambda: missing, "tool-permission", timeout=5, interval=2, now=clock.now, sleep=clock.sleep + lambda: next(responses), "tool-permission", timeout=5, interval=2, now=clock.now, sleep=clock.sleep ) assert result is missing assert clock.elapsed == 5 + with pytest.raises(StopIteration): + next(responses) @pytest.mark.parametrize("status", (400, 401, 429, 500)) From 831a2a13fbd87aee9e77cc6831963ac4a9975fbb Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:12:18 -0700 Subject: [PATCH 057/105] fix(azure): price azure_ai transcriptions at the azure_ai cost-map entry --- litellm/llms/azure/audio_transcriptions.py | 7 ++- litellm/main.py | 1 + .../llms/azure/test_audio_transcriptions.py | 61 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/test_litellm/llms/azure/test_audio_transcriptions.py diff --git a/litellm/llms/azure/audio_transcriptions.py b/litellm/llms/azure/audio_transcriptions.py index 4a5ed2ccb0c..564ec94ba6b 100644 --- a/litellm/llms/azure/audio_transcriptions.py +++ b/litellm/llms/azure/audio_transcriptions.py @@ -37,6 +37,7 @@ class AzureAudioTranscription(AzureChatCompletion): azure_ad_token: str | None = None, atranscription: bool = False, litellm_params: dict | None = None, + custom_llm_provider: str = "azure", ) -> TranscriptionResponse | Coroutine[Any, Any, TranscriptionResponse]: data: Final = {"model": model, "file": audio_file, **optional_params} @@ -53,6 +54,7 @@ class AzureAudioTranscription(AzureChatCompletion): logging_obj=logging_obj, model=model, litellm_params=litellm_params, + custom_llm_provider=custom_llm_provider, ) azure_client: Final = self.get_azure_openai_client( @@ -99,7 +101,7 @@ class AzureAudioTranscription(AzureChatCompletion): additional_args={"complete_input_dict": data}, original_response=stringified_response, ) - hidden_params: Final = {"model": model, "custom_llm_provider": "azure"} + hidden_params: Final = {"model": model, "custom_llm_provider": custom_llm_provider} final_response: Final[TranscriptionResponse] = convert_to_model_response_object( response_object=stringified_response, model_response_object=model_response, @@ -122,6 +124,7 @@ class AzureAudioTranscription(AzureChatCompletion): client=None, max_retries=None, litellm_params: dict | None = None, + custom_llm_provider: str = "azure", ) -> TranscriptionResponse: response = None try: @@ -178,7 +181,7 @@ class AzureAudioTranscription(AzureChatCompletion): }, original_response=stringified_response, ) - hidden_params: Final = {"model": model, "custom_llm_provider": "azure"} + hidden_params: Final = {"model": model, "custom_llm_provider": custom_llm_provider} response = convert_to_model_response_object( _response_headers=headers, response_object=stringified_response, diff --git a/litellm/main.py b/litellm/main.py index 75b7f7f10a5..11b30c240aa 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -7805,6 +7805,7 @@ def transcription( azure_ad_token=azure_ad_token, max_retries=max_retries, litellm_params=litellm_params_dict, + custom_llm_provider=custom_llm_provider, ) elif custom_llm_provider == "openai" or (custom_llm_provider in litellm.openai_compatible_providers): api_base = ( diff --git a/tests/test_litellm/llms/azure/test_audio_transcriptions.py b/tests/test_litellm/llms/azure/test_audio_transcriptions.py new file mode 100644 index 00000000000..cd5fcbd85a9 --- /dev/null +++ b/tests/test_litellm/llms/azure/test_audio_transcriptions.py @@ -0,0 +1,61 @@ +import json +from pathlib import Path +from typing import Final + +import httpx +import pytest +from openai import AzureOpenAI + +import litellm +from litellm.cost_calculator import completion_cost +from litellm.litellm_core_utils.audio_utils.utils import calculate_request_duration + +AUDIO_FILE: Final = Path(__file__).parents[3] / "gettysburg.wav" +WHISPER_COST_PER_SECOND: Final = 0.0001 + + +def _transcription_client() -> AzureOpenAI: + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={"text": "Four score and seven years ago"}) + + return AzureOpenAI( + api_key="test-key", + api_version="2024-06-01", + azure_endpoint="https://example.cognitiveservices.azure.com", + http_client=httpx.Client(transport=httpx.MockTransport(handler)), + ) + + +def test_azure_ai_transcription_is_priced_at_the_azure_ai_entry(): + with AUDIO_FILE.open("rb") as audio: + response = litellm.transcription( + model="azure_ai/whisper", + file=audio, + api_base="https://example.cognitiveservices.azure.com", + api_key="test-key", + api_version="2024-06-01", + client=_transcription_client(), + ) + with AUDIO_FILE.open("rb") as audio: + duration = calculate_request_duration(audio) + + assert duration is not None and duration > 0 + assert response._hidden_params["custom_llm_provider"] == "azure_ai" + assert completion_cost(completion_response=response, call_type="transcription") == pytest.approx( + WHISPER_COST_PER_SECOND * duration + ) + + +def test_azure_transcription_keeps_the_azure_provider(): + with AUDIO_FILE.open("rb") as audio: + response = litellm.transcription( + model="azure/whisper-1", + file=audio, + api_base="https://example.openai.azure.com", + api_key="test-key", + api_version="2024-06-01", + client=_transcription_client(), + ) + + assert response._hidden_params["custom_llm_provider"] == "azure" + assert json.loads(response.model_dump_json())["text"] == "Four score and seven years ago" From 268b944081e7139e54dc88dda6e311152274dc68 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:24:22 -0700 Subject: [PATCH 058/105] fix(spend-tracking): bound the spend-log scan with a statement timeout and name only unanimous alias, team, and owner --- litellm/constants.py | 1 + .../spend_tracking/key_metadata_recovery.py | 62 ++++-- .../test_key_metadata_recovery.py | 176 +++++++++++++----- 3 files changed, 176 insertions(+), 63 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index cfc5b6b86a7..e599a5dd3f9 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1766,6 +1766,7 @@ DEFAULT_ACCESS_GROUP_CACHE_TTL: Final = int(os.getenv("DEFAULT_ACCESS_GROUP_CACH SPEND_LOG_KEY_METADATA_CACHE_TTL: Final = 600 SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL: Final = 30 SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS: Final = 10000 +SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS: Final = 5000 # Short TTL for negative MCP access-group existence lookups. Keeps unauthenticated # callers from forcing a DB query per request for unknown names, while bounding # staleness so a transient DB error (which surfaces as an empty list) cannot diff --git a/litellm/proxy/spend_tracking/key_metadata_recovery.py b/litellm/proxy/spend_tracking/key_metadata_recovery.py index d555da99397..29688b61b3d 100644 --- a/litellm/proxy/spend_tracking/key_metadata_recovery.py +++ b/litellm/proxy/spend_tracking/key_metadata_recovery.py @@ -1,7 +1,7 @@ import asyncio from collections.abc import Awaitable, Callable, Mapping, Sequence from collections.abc import Set as AbstractSet -from datetime import datetime +from datetime import datetime, timedelta from types import MappingProxyType from typing import Final, TypeVar @@ -14,6 +14,7 @@ from litellm.constants import ( SPEND_LOG_KEY_METADATA_CACHE_MAX_ITEMS, SPEND_LOG_KEY_METADATA_CACHE_TTL, SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL, + SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS, ) from litellm.litellm_core_utils.litellm_logging import is_valid_sha256_hash from litellm.proxy.utils import PrismaClient @@ -36,16 +37,15 @@ ORDER BY token, deleted_at DESC """ _SPEND_LOG_ALIAS_SQL: Final = """ -SELECT DISTINCT ON (api_key) - api_key AS digest, - key_alias, - team_id, - user_id, - MIN(user_id) OVER (PARTITION BY api_key) AS first_owner, - MAX(user_id) OVER (PARTITION BY api_key) AS last_owner +SELECT api_key AS digest, + MIN(key_alias) AS first_alias, + MAX(key_alias) AS last_alias, + MIN(team_id) AS first_team, + MAX(team_id) AS last_team, + MIN(user_id) AS first_owner, + MAX(user_id) AS last_owner FROM ( SELECT api_key, - "startTime", NULLIF(metadata->>'user_api_key_alias', '') AS key_alias, COALESCE(NULLIF(team_id, ''), NULLIF(metadata->>'user_api_key_team_id', '')) AS team_id, COALESCE(NULLIF("user", ''), NULLIF(metadata->>'user_api_key_user_id', '')) AS user_id @@ -55,9 +55,12 @@ FROM ( AND "startTime" < $3::timestamp ) named WHERE COALESCE(key_alias, user_id, team_id) IS NOT NULL -ORDER BY api_key, "startTime" DESC +GROUP BY api_key """ +_SPEND_LOG_STATEMENT_TIMEOUT_SQL: Final = f"SET LOCAL statement_timeout = {SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS}" +_SPEND_LOG_TRANSACTION_TIMEOUT: Final = timedelta(milliseconds=2 * SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS) + _HASHED_JWT_PREFIX: Final = "hashed-jwt-" @@ -75,12 +78,25 @@ class _TokenDigestRow(BaseModel): user_id: str | None = None -class _SpendLogDigestRow(_TokenDigestRow): +def _unanimous(first: str | None, last: str | None) -> str | None: + return first if first == last else None + + +class _SpendLogDigestRow(BaseModel): + digest: str + first_alias: str | None = None + last_alias: str | None = None + first_team: str | None = None + last_team: str | None = None first_owner: str | None = None last_owner: str | None = None - def unanimous_owner(self) -> str | None: - return self.user_id if self.first_owner == self.last_owner else None + def metadata(self) -> KeyMetadataDict: + return KeyMetadataDict( + key_alias=_unanimous(self.first_alias, self.last_alias), + team_id=_unanimous(self.first_team, self.last_team), + user_id=_unanimous(self.first_owner, self.last_owner), + ) _TOKEN_DIGEST_ROWS: Final = TypeAdapter(tuple[_TokenDigestRow, ...]) @@ -232,14 +248,24 @@ def _cached_spend_log_metadata( ) +async def _spend_log_rows_within_the_statement_timeout( + prisma_client: PrismaClient, + digests: AbstractSet[str], + window: tuple[datetime, datetime], +) -> Sequence[Mapping[str, object]]: + start, end = window + async with prisma_client.db.tx(timeout=_SPEND_LOG_TRANSACTION_TIMEOUT) as transaction: + await transaction.execute_raw(_SPEND_LOG_STATEMENT_TIMEOUT_SQL) + return await transaction.query_raw(_SPEND_LOG_ALIAS_SQL, sorted(digests), start, end) + + async def _query_spend_log_metadata( prisma_client: PrismaClient, digests: AbstractSet[str], window: tuple[datetime, datetime], ) -> Mapping[str, KeyMetadataDict] | None: - start, end = window rows: Final = await _db_or_empty( - lambda: prisma_client.db.query_raw(_SPEND_LOG_ALIAS_SQL, sorted(digests), start, end), + lambda: _spend_log_rows_within_the_statement_timeout(prisma_client, digests, window), "Failed spend-log alias recovery for %d missing keys: %s", len(digests), ) @@ -247,10 +273,10 @@ async def _query_spend_log_metadata( return None return MappingProxyType( { - row.digest: KeyMetadataDict(key_alias=row.key_alias, team_id=row.team_id, user_id=owner) + row.digest: meta for row in _SPEND_LOG_DIGEST_ROWS.validate_python(rows) - for owner in (row.unanimous_owner(),) - if row.digest in digests and (row.key_alias or owner or row.team_id) + for meta in (row.metadata(),) + if row.digest in digests and any(meta.values()) } ) diff --git a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py index 682b11f5bb7..89be341c87b 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py +++ b/tests/test_litellm/proxy/spend_tracking/test_key_metadata_recovery.py @@ -1,7 +1,7 @@ import asyncio import time from collections.abc import Sequence -from datetime import datetime +from datetime import datetime, timedelta from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock @@ -9,7 +9,11 @@ import pytest from prisma.errors import PrismaError from litellm.caching.in_memory_cache import InMemoryCache -from litellm.constants import SPEND_LOG_KEY_METADATA_CACHE_TTL, SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL +from litellm.constants import ( + SPEND_LOG_KEY_METADATA_CACHE_TTL, + SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL, + SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS, +) from litellm.proxy.spend_tracking.key_metadata_recovery import ( fill_missing_api_key_aliases, recover_double_hashed_key_metadata, @@ -31,6 +35,26 @@ def _query_raw_spend_logs(rows: Sequence[dict[str, str | None]]) -> AsyncMock: return AsyncMock(side_effect=query_raw) +def _spend_log_row(digest: str, key_alias: str | None, team_id: str | None, user_id: str | None) -> dict[str, str | None]: + return { + "digest": digest, + "first_alias": key_alias, + "last_alias": key_alias, + "first_team": team_id, + "last_team": team_id, + "first_owner": user_id, + "last_owner": user_id, + } + + +def _spend_log_transaction(mock_prisma: MagicMock, query_raw: AsyncMock) -> AsyncMock: + transaction = MagicMock() + transaction.execute_raw = AsyncMock(return_value=0) + transaction.query_raw = query_raw + mock_prisma.db.tx.return_value.__aenter__.return_value = transaction + return query_raw + + def _query_raw_by_table( active_rows: Sequence[dict[str, str | None]], deleted_rows: Sequence[dict[str, str | None]], @@ -240,15 +264,18 @@ async def test_recover_key_metadata_from_spend_logs_resolves_session_token_from_ session_digest = hash_token("cli-session-repro-user-6852") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs( - [_digest_row(session_digest, "cli-session-repro-user-6852", None, "repro-user-6852")] + query_raw = _spend_log_transaction( + mock_prisma, + _query_raw_spend_logs( + [_spend_log_row(session_digest, "cli-session-repro-user-6852", None, "repro-user-6852")] + ), ) result = await recover_key_metadata_from_spend_logs(mock_prisma, {session_digest}, window, cache=InMemoryCache()) assert result[session_digest]["key_alias"] == "cli-session-repro-user-6852" assert result[session_digest]["user_id"] == "repro-user-6852" - ((_, digests, start, end),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + ((_, digests, start, end),) = [call.args for call in query_raw.call_args_list] assert digests == [session_digest] assert (start, end) == window @@ -257,19 +284,19 @@ async def test_recover_key_metadata_from_spend_logs_resolves_session_token_from_ async def test_recover_key_metadata_from_spend_logs_skips_query_when_no_missing_keys(): window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = AsyncMock(return_value=[]) + query_raw = _spend_log_transaction(mock_prisma, AsyncMock(return_value=[])) result = await recover_key_metadata_from_spend_logs(mock_prisma, set(), window, cache=InMemoryCache()) assert result == {} - mock_prisma.db.query_raw.assert_not_called() + query_raw.assert_not_called() @pytest.mark.asyncio async def test_recover_key_metadata_from_spend_logs_returns_empty_on_prisma_error(): window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("db down")) + query_raw = _spend_log_transaction(mock_prisma, AsyncMock(side_effect=PrismaError("db down"))) result = await recover_key_metadata_from_spend_logs( mock_prisma, {hash_token("cli-session-x")}, window, cache=InMemoryCache() @@ -285,12 +312,15 @@ async def test_recover_key_metadata_from_spend_logs_ignores_foreign_and_all_null foreign = hash_token("cli-session-foreign") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs( - [ - _digest_row(wanted, "kept-alias", None, "owner-1"), - _digest_row(all_null, None, None, None), - _digest_row(foreign, "foreign-alias", None, "owner-2"), - ] + query_raw = _spend_log_transaction( + mock_prisma, + _query_raw_spend_logs( + [ + _spend_log_row(wanted, "kept-alias", None, "owner-1"), + _spend_log_row(all_null, None, None, None), + _spend_log_row(foreign, "foreign-alias", None, "owner-2"), + ] + ), ) result = await recover_key_metadata_from_spend_logs(mock_prisma, {wanted, all_null}, window, cache=InMemoryCache()) @@ -304,14 +334,14 @@ async def test_recover_key_metadata_from_spend_logs_ignores_foreign_and_all_null async def test_recover_key_metadata_from_spend_logs_skips_non_sha256_keys(): window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = AsyncMock(return_value=[]) + query_raw = _spend_log_transaction(mock_prisma, AsyncMock(return_value=[])) result = await recover_key_metadata_from_spend_logs( mock_prisma, {"cli-session-raw-1798", "key-hash-short"}, window, cache=InMemoryCache() ) assert result == {} - mock_prisma.db.query_raw.assert_not_called() + query_raw.assert_not_called() @pytest.mark.asyncio @@ -319,13 +349,13 @@ async def test_recover_key_metadata_from_spend_logs_accepts_hashed_jwt_digests() jwt_digest = f"hashed-jwt-{hash_token('jwt-subject-1')}" window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(jwt_digest, None, "team-jwt", "jwt-user")]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(jwt_digest, None, "team-jwt", "jwt-user")])) result = await recover_key_metadata_from_spend_logs(mock_prisma, {jwt_digest}, window, cache=InMemoryCache()) assert result[jwt_digest]["team_id"] == "team-jwt" assert result[jwt_digest]["user_id"] == "jwt-user" - ((_, digests, _, _),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + ((_, digests, _, _),) = [call.args for call in query_raw.call_args_list] assert digests == [jwt_digest] @@ -336,7 +366,7 @@ async def test_recover_key_metadata_from_spend_logs_serves_repeat_lookups_from_t window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) cache = InMemoryCache() mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(found, "found-alias", None, "owner-1")]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(found, "found-alias", None, "owner-1")])) first = await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) second = await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) @@ -344,7 +374,7 @@ async def test_recover_key_metadata_from_spend_logs_serves_repeat_lookups_from_t assert first == second assert set(first) == {found} assert first[found]["key_alias"] == "found-alias" - assert mock_prisma.db.query_raw.await_count == 1 + assert query_raw.await_count == 1 @pytest.mark.asyncio @@ -354,15 +384,15 @@ async def test_recover_key_metadata_from_spend_logs_only_queries_digests_the_cac window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) cache = InMemoryCache() mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(cached_digest, "cached-alias", None, None)]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(cached_digest, "cached-alias", None, None)])) await recover_key_metadata_from_spend_logs(mock_prisma, {cached_digest}, window, cache=cache) - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(new_digest, "new-alias", None, None)]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(new_digest, "new-alias", None, None)])) result = await recover_key_metadata_from_spend_logs(mock_prisma, {cached_digest, new_digest}, window, cache=cache) assert result[cached_digest]["key_alias"] == "cached-alias" assert result[new_digest]["key_alias"] == "new-alias" - ((_, digests, _, _),) = [call.args for call in mock_prisma.db.query_raw.call_args_list] + ((_, digests, _, _),) = [call.args for call in query_raw.call_args_list] assert digests == [new_digest] @@ -371,18 +401,18 @@ async def test_recover_key_metadata_from_spend_logs_rescans_when_the_window_chan digest = hash_token("cli-session-windowed") cache = InMemoryCache() mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([])) await recover_key_metadata_from_spend_logs( mock_prisma, {digest}, (datetime(2026, 9, 1), datetime(2026, 9, 4)), cache=cache ) - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(digest, "later-alias", None, None)]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(digest, "later-alias", None, None)])) result = await recover_key_metadata_from_spend_logs( mock_prisma, {digest}, (datetime(2026, 9, 7), datetime(2026, 9, 10)), cache=cache ) assert result[digest]["key_alias"] == "later-alias" - assert mock_prisma.db.query_raw.await_count == 1 + assert query_raw.await_count == 1 @pytest.mark.asyncio @@ -391,13 +421,13 @@ async def test_recover_key_metadata_from_spend_logs_retries_a_failed_query_only_ window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) mock_prisma = MagicMock() - mock_prisma.db.query_raw = AsyncMock(side_effect=PrismaError("statement timeout")) + query_raw = _spend_log_transaction(mock_prisma, AsyncMock(side_effect=PrismaError("statement timeout"))) started = time.time() assert await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) == {} - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(digest, "back-online", None, None)]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(digest, "back-online", None, None)])) assert await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=cache) == {} - mock_prisma.db.query_raw.assert_not_awaited() + query_raw.assert_not_awaited() miss_key = next(key for key in cache.ttl_dict if digest in key and not key.endswith(":missed-before")) assert cache.ttl_dict[miss_key] - started <= SPEND_LOG_KEY_METADATA_MISS_CACHE_TTL + 1 cache.ttl_dict[miss_key] = time.time() - 1 @@ -412,14 +442,11 @@ async def test_recover_key_metadata_from_spend_logs_drops_the_owner_of_a_digest_ shared_ui_digest = hash_token("ui-token") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs( - [ - { - **_digest_row(shared_ui_digest, "ui-token", "litellm-dashboard", "bob"), - "first_owner": "alice", - "last_owner": "bob", - } - ] + query_raw = _spend_log_transaction( + mock_prisma, + _query_raw_spend_logs( + [{**_spend_log_row(shared_ui_digest, "ui-token", "litellm-dashboard", None), "first_owner": "alice", "last_owner": "bob"}] + ), ) result = await recover_key_metadata_from_spend_logs( @@ -434,8 +461,11 @@ async def test_recover_key_metadata_from_spend_logs_keeps_the_owner_when_every_n digest = hash_token("cli-session-one-owner") window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs( - [{**_digest_row(digest, None, None, "carol"), "first_owner": "carol", "last_owner": "carol"}] + query_raw = _spend_log_transaction( + mock_prisma, + _query_raw_spend_logs( + [_spend_log_row(digest, None, None, "carol")] + ), ) result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=InMemoryCache()) @@ -450,7 +480,7 @@ async def test_recover_key_metadata_from_spend_logs_forgets_a_miss_long_before_a window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([_digest_row(found, "found-alias", None, None)]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(found, "found-alias", None, None)])) started = time.time() await recover_key_metadata_from_spend_logs(mock_prisma, {found, unknown}, window, cache=cache) @@ -471,9 +501,9 @@ async def test_recover_key_metadata_from_spend_logs_runs_one_query_for_concurren async def slow_query_raw(sql: str, *params: object) -> list[dict[str, str | None]]: await asyncio.sleep(0.01) - return [_digest_row(digest, "shared-alias", None, None)] + return [_spend_log_row(digest, "shared-alias", None, None)] - mock_prisma.db.query_raw = AsyncMock(side_effect=slow_query_raw) + query_raw = _spend_log_transaction(mock_prisma, AsyncMock(side_effect=slow_query_raw)) results = await asyncio.gather( *( @@ -483,7 +513,7 @@ async def test_recover_key_metadata_from_spend_logs_runs_one_query_for_concurren ) assert all(result[digest]["key_alias"] == "shared-alias" for result in results) - assert mock_prisma.db.query_raw.await_count == 1 + assert query_raw.await_count == 1 @pytest.mark.asyncio @@ -492,7 +522,7 @@ async def test_recover_key_metadata_from_spend_logs_keeps_a_repeated_miss_as_lon window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) cache = InMemoryCache(default_ttl=SPEND_LOG_KEY_METADATA_CACHE_TTL) mock_prisma = MagicMock() - mock_prisma.db.query_raw = _query_raw_spend_logs([]) + query_raw = _spend_log_transaction(mock_prisma, _query_raw_spend_logs([])) await recover_key_metadata_from_spend_logs(mock_prisma, {unknown}, window, cache=cache) first_miss_key = next(key for key in cache.ttl_dict if unknown in key and not key.endswith(":missed-before")) cache.ttl_dict[first_miss_key] = time.time() - 1 @@ -500,5 +530,61 @@ async def test_recover_key_metadata_from_spend_logs_keeps_a_repeated_miss_as_lon await recover_key_metadata_from_spend_logs(mock_prisma, {unknown}, window, cache=cache) - assert mock_prisma.db.query_raw.await_count == 2 + assert query_raw.await_count == 2 assert cache.ttl_dict[first_miss_key] - started >= SPEND_LOG_KEY_METADATA_CACHE_TTL - 1 + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_keeps_the_owner_older_rows_agree_on_when_the_newest_is_nameless(): + digest = hash_token("cli-session-owner-from-older-rows") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + _spend_log_transaction(mock_prisma, _query_raw_spend_logs([_spend_log_row(digest, None, "team-x", "alice")])) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=InMemoryCache()) + + assert result[digest] == {"key_alias": None, "team_id": "team-x", "user_id": "alice"} + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_names_nothing_for_a_field_whose_rows_disagree(): + digest = hash_token("cli-session-disagreeing-rows") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + _spend_log_transaction( + mock_prisma, + _query_raw_spend_logs( + [ + { + **_spend_log_row(digest, None, None, "carol"), + "first_alias": "old-alias", + "last_alias": "renamed-alias", + "first_team": "team-a", + "last_team": "team-b", + } + ] + ), + ) + + result = await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=InMemoryCache()) + + assert result[digest] == {"key_alias": None, "team_id": None, "user_id": "carol"} + + +@pytest.mark.asyncio +async def test_recover_key_metadata_from_spend_logs_bounds_the_scan_with_a_statement_timeout(): + digest = hash_token("cli-session-bounded-scan") + window = (datetime(2026, 9, 7), datetime(2026, 9, 10)) + mock_prisma = MagicMock() + calls: list[str] = [] + transaction = MagicMock() + transaction.execute_raw = AsyncMock(side_effect=lambda sql: calls.append(sql) or 0) + transaction.query_raw = AsyncMock(side_effect=lambda sql, *args: calls.append("scan") or []) + mock_prisma.db.tx.return_value.__aenter__.return_value = transaction + + await recover_key_metadata_from_spend_logs(mock_prisma, {digest}, window, cache=InMemoryCache()) + + assert calls == [f"SET LOCAL statement_timeout = {SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS}", "scan"] + assert mock_prisma.db.tx.call_args.kwargs["timeout"] == timedelta( + milliseconds=2 * SPEND_LOG_KEY_METADATA_QUERY_TIMEOUT_MS + ) From 754a2afe12891f0fc9e8b20fbf17f7504d71215a Mon Sep 17 00:00:00 2001 From: tin-berri Date: Tue, 8 Sep 2026 17:30:20 -0700 Subject: [PATCH 059/105] feat(mcp): add schema discovery proxy mode (#40298) --- .../_experimental/mcp_server/mcp_context.py | 3 + .../proxy/_experimental/mcp_server/server.py | 100 ++++- .../_experimental/mcp_server/tool_search.py | 265 ++++++++++++- litellm/proxy/_lazy_openapi_snapshot.json | 128 +++++++ litellm/proxy/_types.py | 1 + litellm/proxy/proxy_server.py | 25 ++ tests/mcp_tests/test_proxy_mcp_e2e.py | 152 ++++++-- .../mcp_server/test_mcp_proxy_mode.py | 354 ++++++++++++++++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 184 +++++++++ 9 files changed, 1154 insertions(+), 58 deletions(-) create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py diff --git a/litellm/proxy/_experimental/mcp_server/mcp_context.py b/litellm/proxy/_experimental/mcp_server/mcp_context.py index 7936edad753..74cc0c900d9 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_context.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_context.py @@ -21,3 +21,6 @@ _mcp_gateway_initialize_instructions: Final[ContextVar[str | None]] = ContextVar # Per-request scoped server name; set in MCP HTTP/SSE handlers when the path # identifies exactly one upstream server. Never populated from client-supplied headers. _mcp_gateway_server_name: Final[ContextVar[str | None]] = ContextVar("_mcp_gateway_server_name", default=None) + +# Set server-side by the /mcp/proxy route. Never populated from client-supplied headers. +_mcp_proxy_mode: Final[ContextVar[bool]] = ContextVar("_mcp_proxy_mode", default=False) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 1b1e77d27ef..44c00df996c 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -15,7 +15,7 @@ import types import uuid from collections.abc import AsyncIterator, Callable, Mapping, Sequence from datetime import datetime -from typing import TYPE_CHECKING, Any, Final, Protocol +from typing import TYPE_CHECKING, Any, Final, NoReturn, Protocol import httpx from fastapi import FastAPI, HTTPException @@ -47,6 +47,7 @@ from litellm.proxy._experimental.mcp_server.mcp_context import ( _mcp_active_toolset_id, _mcp_gateway_initialize_instructions, _mcp_gateway_server_name, + _mcp_proxy_mode, # pyright: ignore[reportPrivateUsage] # server-owned request mode ) from litellm.proxy._experimental.mcp_server.mcp_debug import MCPDebug from litellm.proxy._experimental.mcp_server.oauth_utils import ( @@ -537,11 +538,22 @@ if MCP_AVAILABLE: notification_options: NotificationOptions | None = None, experimental_capabilities: dict[str, dict[str, object]] | None = None, ) -> InitializationOptions: - opts: Final = Server.create_initialization_options( + base_options: Final = Server.create_initialization_options( self, notification_options=notification_options, experimental_capabilities=experimental_capabilities or {}, ) + opts: Final = ( + base_options.model_copy( + update={ # mutable-ok: Pydantic update payload + "capabilities": base_options.capabilities.model_copy( + update={"prompts": None, "resources": None} # mutable-ok: Pydantic update payload + ) + } + ) + if _mcp_proxy_mode.get() + else base_options + ) updates: Final[dict[str, str]] = {} merged: Final = _mcp_gateway_initialize_instructions.get() if merged is not None: @@ -822,17 +834,20 @@ if MCP_AVAILABLE: "MCP list_tools - MCP server auth headers: %s", list(mcp_server_auth_headers.keys()) if mcp_server_auth_headers else None, ) + from mcp.types import Tool + + from litellm.proxy._experimental.mcp_server.tool_search import ( + get_mcp_proxy_tool_definitions, + get_virtual_tool_definitions, + ) + + if _mcp_proxy_mode.get(): + return [Tool.model_validate(d) for d in get_mcp_proxy_tool_definitions()] # mutable-ok: MCP SDK list if getattr( getattr(user_api_key_auth, "object_permission", None), "mcp_tool_search_enabled", False, ): - from mcp.types import Tool - - from litellm.proxy._experimental.mcp_server.tool_search import ( - get_virtual_tool_definitions, - ) - return [Tool.model_validate(d) for d in get_virtual_tool_definitions()] # Get mcp_servers from context variable @@ -906,6 +921,12 @@ if MCP_AVAILABLE: verbose_logger.debug("Host progressToken captured: %s...", str(host_token)[:8]) return forward_progress + def _reject_mcp_proxy_operation() -> NoReturn: + from mcp.shared.exceptions import McpError + from mcp.types import METHOD_NOT_FOUND, ErrorData + + raise McpError(ErrorData(code=METHOD_NOT_FOUND, message="Operation unavailable on /mcp/proxy")) + async def _build_virtual_call_logging_obj( name: str, arguments: dict[str, object], @@ -961,16 +982,53 @@ if MCP_AVAILABLE: from litellm.proxy._experimental.mcp_server.tool_search import ( AGENT_SEARCH_TOOL_NAME, DEFAULT_AGENT_SEARCH_TOP_K, + MCP_PROXY_CALL_TOOL_NAME, + MCP_PROXY_TOOL_NAMES, MCP_TOOL_SEARCH_TOOL_NAME, SKILL_SEARCH_TOOL_NAME, VIRTUAL_TOOL_NAMES, coerce_top_k, handle_agent_search, + handle_mcp_proxy_tool, handle_mcp_tool_call, handle_mcp_tool_search, handle_skill_search, ) + if _mcp_proxy_mode.get() and name not in MCP_PROXY_TOOL_NAMES: + return CallToolResult( + content=[ # mutable-ok: MCP result content + TextContent(type="text", text=f"Tool {name} is unavailable on /mcp/proxy") + ], + isError=True, + ) + + if _mcp_proxy_mode.get() and name in MCP_PROXY_TOOL_NAMES: + assert user_api_key_auth is not None + proxy_logging_obj: Final = ( + await _build_virtual_call_logging_obj( + name=name, + arguments=arguments or {}, # mutable-ok: logging pipeline payload + user_api_key_auth=user_api_key_auth, + raw_headers=raw_headers, + client_ip=client_ip, + ) + if name == MCP_PROXY_CALL_TOOL_NAME + else None + ) + return await handle_mcp_proxy_tool( + name=name, + arguments=arguments or {}, # mutable-ok: proxy handler payload + user_api_key_dict=user_api_key_auth, + client_ip=client_ip, + mcp_servers=mcp_servers, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers, + litellm_logging_obj=proxy_logging_obj, + ) + if name not in VIRTUAL_TOOL_NAMES: return None @@ -1216,6 +1274,8 @@ if MCP_AVAILABLE: """ List all available prompts """ + if _mcp_proxy_mode.get(): + _reject_mcp_proxy_operation() from mcp.server.lowlevel.server import request_ctx req_ctx: Final = request_ctx.get(None) @@ -1273,8 +1333,8 @@ if MCP_AVAILABLE: Returns: GetPromptResult: Getting prompt execution results """ - - # Validate arguments + if _mcp_proxy_mode.get(): + _reject_mcp_proxy_operation() from mcp.server.lowlevel.server import request_ctx req_ctx: Final = request_ctx.get(None) @@ -1311,6 +1371,8 @@ if MCP_AVAILABLE: @server.list_resources() async def list_resources() -> list[Resource]: """List all available resources.""" + if _mcp_proxy_mode.get(): + _reject_mcp_proxy_operation() from mcp.server.lowlevel.server import request_ctx req_ctx: Final = request_ctx.get(None) @@ -1355,6 +1417,8 @@ if MCP_AVAILABLE: @server.list_resource_templates() async def list_resource_templates() -> list[ResourceTemplate]: """List all available resource templates.""" + if _mcp_proxy_mode.get(): + _reject_mcp_proxy_operation() from mcp.server.lowlevel.server import request_ctx req_ctx: Final = request_ctx.get(None) @@ -1400,6 +1464,8 @@ if MCP_AVAILABLE: @server.read_resource() async def read_resource(url: AnyUrl) -> list[ReadResourceContents]: + if _mcp_proxy_mode.get(): + _reject_mcp_proxy_operation() from mcp.server.lowlevel.server import request_ctx req_ctx: Final = request_ctx.get(None) @@ -1998,6 +2064,7 @@ if MCP_AVAILABLE: litellm_trace_id: str | None = None, request_tags: list[str] | None = None, client_ip: str | None = None, + mcp_proxy_mode: bool = False, ) -> AggregateToolListing: """ Helper method to fetch tools from MCP servers based on server filtering criteria. @@ -2177,9 +2244,14 @@ if MCP_AVAILABLE: user_api_key_auth=user_api_key_auth, ) - # Apply display-name/description overrides last so that - # permission filtering always works against original names. - filtered_tools = apply_tool_overrides(filtered_tools, server) + if mcp_proxy_mode: + from litellm.proxy._experimental.mcp_server.tool_search import with_mcp_proxy_identity + + filtered_tools = [ # mutable-ok: MCP tool pipeline + with_mcp_proxy_identity(tool, server.server_id) for tool in filtered_tools + ] + else: + filtered_tools = apply_tool_overrides(filtered_tools, server) verbose_logger.debug( "Successfully fetched %s tools from server %s, %s after filtering", @@ -2491,6 +2563,7 @@ if MCP_AVAILABLE: log_list_tools_to_spendlogs: bool = False, list_tools_log_source: str | None = None, client_ip: str | None = None, + mcp_proxy_mode: bool = False, ) -> AggregateToolListing: """ List all available MCP tools. @@ -2520,6 +2593,7 @@ if MCP_AVAILABLE: log_list_tools_to_spendlogs=log_list_tools_to_spendlogs, list_tools_log_source=list_tools_log_source, client_ip=client_ip, + mcp_proxy_mode=mcp_proxy_mode, ) verbose_logger.debug("Successfully fetched %s tools from managed MCP servers", len(listing.tools)) return listing diff --git a/litellm/proxy/_experimental/mcp_server/tool_search.py b/litellm/proxy/_experimental/mcp_server/tool_search.py index 24e2f5ce64d..2c73f9b863b 100644 --- a/litellm/proxy/_experimental/mcp_server/tool_search.py +++ b/litellm/proxy/_experimental/mcp_server/tool_search.py @@ -1,5 +1,6 @@ from __future__ import annotations +import hashlib import json from collections.abc import Mapping, Sequence from dataclasses import dataclass @@ -30,6 +31,12 @@ if TYPE_CHECKING: MCP_TOOL_SEARCH_SETTINGS_KEY: Final[str] = "mcp_tool_search" MCP_TOOL_SEARCH_TOOL_NAME: Final[str] = "mcp_tool_search" MCP_TOOL_CALL_TOOL_NAME: Final[str] = "mcp_tool_call" +MCP_PROXY_SEARCH_TOOL_NAME: Final[str] = "search_tools" +MCP_PROXY_SCHEMA_TOOL_NAME: Final[str] = "get_tool_schema" +MCP_PROXY_CALL_TOOL_NAME: Final[str] = "call_tool" +MCP_PROXY_TOOL_NAMES: Final = frozenset( + (MCP_PROXY_SEARCH_TOOL_NAME, MCP_PROXY_SCHEMA_TOOL_NAME, MCP_PROXY_CALL_TOOL_NAME) +) AGENT_SEARCH_TOOL_NAME: Final[str] = "agent_search" SKILL_SEARCH_TOOL_NAME: Final[str] = "skill_search" VIRTUAL_TOOL_NAMES: Final = frozenset( @@ -51,6 +58,29 @@ class ToolSearchResult(TypedDict, total=False): score: ReadOnly[float] +class MCPProxySearchResult(TypedDict, total=False): + tool_id: Required[ReadOnly[str]] + name: Required[ReadOnly[str]] + description: Required[ReadOnly[str]] + score: ReadOnly[float] + + +class MCPProxySchemaResult(MCPProxySearchResult, total=False): + inputSchema: Required[ReadOnly[Mapping[str, object]]] + outputSchema: ReadOnly[Mapping[str, object]] + + +class MCPProxyToolIdentity(TypedDict): + server_id: ReadOnly[str] + tool_name: ReadOnly[str] + + +@dataclass(frozen=True, slots=True) +class MCPToolSearchHit: + tool: Tool + score: float | None = None + + @dataclass(frozen=True, slots=True) class SemanticToolRanker: embed: Embedder @@ -76,6 +106,55 @@ def _scored_result(tool: Tool, score: float) -> ToolSearchResult: return {"name": tool.name, "description": tool.description or "", "inputSchema": tool.inputSchema, "score": score} +_MCP_PROXY_IDENTITY_META_KEY: Final[str] = "litellm.ai/proxy_tool_identity" + + +def with_mcp_proxy_identity(tool: Tool, server_id: str) -> Tool: + identity: Final[MCPProxyToolIdentity] = {"server_id": server_id, "tool_name": tool.name} + return tool.model_copy( # mutable-ok: Pydantic requires mutable update and metadata mappings + update={ # mutable-ok: Pydantic update payload + "meta": {**(tool.meta or {}), _MCP_PROXY_IDENTITY_META_KEY: identity} # mutable-ok: metadata mapping + } + ) + + +def _mcp_proxy_identity(tool: Tool) -> MCPProxyToolIdentity: + identity: Final = (tool.meta or {}).get(_MCP_PROXY_IDENTITY_META_KEY) # mutable-ok: absent metadata default + if not isinstance(identity, Mapping): + raise TypeError("MCP proxy tool identity is missing") + server_id: Final = identity.get("server_id") + tool_name: Final = identity.get("tool_name") + if not isinstance(server_id, str) or not isinstance(tool_name, str): + raise TypeError("MCP proxy tool identity is invalid") + return {"server_id": server_id, "tool_name": tool_name} # mutable-ok: TypedDict identity payload + + +def mcp_proxy_tool_id(tool: Tool) -> str: + identity: Final = _mcp_proxy_identity(tool) + return hashlib.sha256(f"{identity['server_id']}\0{identity['tool_name']}".encode()).hexdigest()[:32] + + +def _proxy_search_result(hit: MCPToolSearchHit) -> MCPProxySearchResult: + base: Final[MCPProxySearchResult] = { + "tool_id": mcp_proxy_tool_id(hit.tool), + "name": hit.tool.name, + "description": hit.tool.description or "", + } + return {**base, "score": hit.score} if hit.score is not None else base # mutable-ok: wire result payload + + +def _proxy_schema_result(tool: Tool) -> MCPProxySchemaResult: + base: Final[MCPProxySchemaResult] = { + "tool_id": mcp_proxy_tool_id(tool), + "name": tool.name, + "description": tool.description or "", + "inputSchema": tool.inputSchema, + } + if tool.outputSchema is None: + return base + return {**base, "outputSchema": tool.outputSchema} # mutable-ok: wire schema payload + + def _tool_text(tool: Tool) -> str: return "\n".join(part for part in (tool.name, tool.description or "") if part) @@ -107,6 +186,38 @@ def search_tools(query: str, tools: Sequence[Tool], top_k: int = 5) -> tuple[Too return tuple(_tool_result(tool) for _, tool in _top_hits(tools, scores, minimum=1.0, limit=top_k)) +async def rank_mcp_tools( + query: str, + tools: Sequence[Tool], + top_k: int, + settings: MCPToolSearchSettings, + ranker: SemanticToolRanker | None, +) -> tuple[MCPToolSearchHit, ...] | EmbeddingFailed: + core, rest = _split_core_tools(tools, settings.core_tools) + core_hits: Final = tuple(MCPToolSearchHit(tool) for tool in core) + if not query: + return core_hits + limit: Final = min(top_k, settings.top_k) + if ranker is None: + scores: Final = tuple(_keyword_score(query, tool) for tool in rest) + return ( + *core_hits, + *(MCPToolSearchHit(tool) for _, tool in _top_hits(rest, scores, minimum=1.0, limit=limit)), + ) + semantic_scores: Final = await ranker.index.scores( + query, tuple(_tool_text(tool) for tool in rest), ranker.embed, ranker.embedding_model + ) + if isinstance(semantic_scores, EmbeddingFailed): + return semantic_scores + return ( + *core_hits, + *( + MCPToolSearchHit(tool, score) + for score, tool in _top_hits(rest, semantic_scores, settings.similarity_threshold, limit) + ), + ) + + async def search_mcp_tools( query: str, tools: Sequence[Tool], @@ -114,21 +225,12 @@ async def search_mcp_tools( settings: MCPToolSearchSettings, ranker: SemanticToolRanker | None, ) -> tuple[ToolSearchResult, ...] | EmbeddingFailed: - """Core tools the caller can access come first, then up to `top_k` ranked matches from the remaining tools.""" - core, rest = _split_core_tools(tools, settings.core_tools) - limit: Final = min(top_k, settings.top_k) - core_results: Final = tuple(_tool_result(tool) for tool in core) - if ranker is None: - return (*core_results, *search_tools(query, rest, limit)) - if not query: - return core_results - scores: Final = await ranker.index.scores( - query, tuple(_tool_text(tool) for tool in rest), ranker.embed, ranker.embedding_model + hits: Final = await rank_mcp_tools(query, tools, top_k, settings, ranker) + if isinstance(hits, EmbeddingFailed): + return hits + return tuple( + _scored_result(hit.tool, hit.score) if hit.score is not None else _tool_result(hit.tool) for hit in hits ) - if isinstance(scores, EmbeddingFailed): - return scores - hits: Final = _top_hits(rest, scores, minimum=settings.similarity_threshold, limit=limit) - return (*core_results, *(_scored_result(tool, score) for score, tool in hits)) class _ToolParamSchema(TypedDict, total=False): @@ -223,10 +325,48 @@ _SKILL_SEARCH_DEFINITION: Final[VirtualToolDefinition] = { } +_MCP_PROXY_SEARCH_DEFINITION: Final[VirtualToolDefinition] = { + "name": MCP_PROXY_SEARCH_TOOL_NAME, + "description": "Search accessible MCP tools by describing what you need. Returns opaque tool IDs.", + "inputSchema": { + "type": "object", + "properties": {"query": {"type": "string", "description": "What the tool should do."}}, + "required": _json_array("query"), + }, +} + +_MCP_PROXY_SCHEMA_DEFINITION: Final[VirtualToolDefinition] = { + "name": MCP_PROXY_SCHEMA_TOOL_NAME, + "description": "Return the complete schema for an accessible MCP tool ID.", + "inputSchema": { + "type": "object", + "properties": {"tool_id": {"type": "string", "description": "Opaque ID from search_tools."}}, + "required": _json_array("tool_id"), + }, +} + +_MCP_PROXY_CALL_DEFINITION: Final[VirtualToolDefinition] = { + "name": MCP_PROXY_CALL_TOOL_NAME, + "description": "Call an accessible MCP tool by opaque ID with schema-valid arguments.", + "inputSchema": { + "type": "object", + "properties": { + "tool_id": {"type": "string", "description": "Opaque ID from search_tools."}, + "arguments": {"type": "object", "description": "Arguments validated against the selected tool schema."}, + }, + "required": _json_array("tool_id"), + }, +} + + def get_virtual_tool_definitions() -> tuple[VirtualToolDefinition, ...]: return (_MCP_TOOL_SEARCH_DEFINITION, _MCP_TOOL_CALL_DEFINITION, _AGENT_SEARCH_DEFINITION, _SKILL_SEARCH_DEFINITION) +def get_mcp_proxy_tool_definitions() -> tuple[VirtualToolDefinition, ...]: + return (_MCP_PROXY_SEARCH_DEFINITION, _MCP_PROXY_SCHEMA_DEFINITION, _MCP_PROXY_CALL_DEFINITION) + + def _text_tool_result(text: str, is_error: bool) -> CallToolResult: from mcp.types import CallToolResult, TextContent @@ -314,7 +454,9 @@ async def handle_mcp_tool_search( oauth2_headers: dict[str, str] | None = None, raw_headers: dict[str, str] | None = None, ) -> CallToolResult: - from litellm.proxy._experimental.mcp_server.server import _list_mcp_tools + from litellm.proxy._experimental.mcp_server.server import ( + _list_mcp_tools, # pyright: ignore[reportPrivateUsage] # shared catalog owner + ) from litellm.proxy.proxy_server import llm_router, proxy_logging_obj settings: Final = mcp_tool_search_settings() @@ -351,6 +493,97 @@ async def handle_mcp_tool_search( return _text_tool_result(json.dumps(results), is_error=False) +async def handle_mcp_proxy_tool( + name: str, + arguments: dict[str, object], # mutable-ok: MCP dispatcher passes mutable call arguments + user_api_key_dict: UserAPIKeyAuth, + client_ip: str | None = None, + mcp_servers: list[str] | None = None, # mutable-ok: preserve MCP scope container for existing resolver + mcp_auth_header: str | None = None, + mcp_server_auth_headers: dict[str, dict[str, str]] | None = None, # mutable-ok: preserve forwarded headers + oauth2_headers: dict[str, str] | None = None, # mutable-ok: preserve forwarded headers + raw_headers: dict[str, str] | None = None, # mutable-ok: preserve request headers + litellm_logging_obj: LiteLLMLoggingObj | None = None, +) -> CallToolResult: + from fastapi import HTTPException + from jsonschema import ValidationError as JsonSchemaValidationError + from jsonschema import validate + + from litellm.proxy import proxy_server + from litellm.proxy._experimental.mcp_server.server import ( # pyright: ignore[reportPrivateUsage] # shared catalog owner + _list_mcp_tools, # pyright: ignore[reportPrivateUsage] # shared catalog owner + ) + + listing: Final = await _list_mcp_tools( + user_api_key_auth=user_api_key_dict, + mcp_servers=mcp_servers, + client_ip=client_ip, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers, + mcp_proxy_mode=True, + ) + tools_by_id: Final = {mcp_proxy_tool_id(tool): tool for tool in listing.tools} # mutable-ok: lookup index + + if name == MCP_PROXY_SEARCH_TOOL_NAME: + llm_router: Final = proxy_server.llm_router + proxy_logging_obj: Final = proxy_server.proxy_logging_obj + settings: Final = mcp_tool_search_settings() + if isinstance(settings, ValidationError): + return _text_tool_result(str(settings), is_error=True) + if settings.embedding_model is not None and llm_router is None: + return _text_tool_result( + f"litellm_settings.{MCP_TOOL_SEARCH_SETTINGS_KEY}.embedding_model needs a model_list so it can be called", + is_error=True, + ) + ranker: Final = ( + SemanticToolRanker( + embed=router_embedder(llm_router, settings.embedding_model, user_api_key_dict, proxy_logging_obj), + embedding_model=settings.embedding_model, + index=global_mcp_tool_search_index, + ) + if settings.embedding_model is not None and llm_router is not None + else None + ) + results: Final = await rank_mcp_tools(str(arguments.get("query", "")), listing.tools, 5, settings, ranker) + if isinstance(results, EmbeddingFailed): + return _text_tool_result(results.reason, is_error=True) + return _text_tool_result(json.dumps(tuple(_proxy_search_result(hit) for hit in results)), is_error=False) + + tool_id: Final = arguments.get("tool_id") + tool: Final = tools_by_id.get(tool_id) if isinstance(tool_id, str) else None + if tool is None: + return _text_tool_result("Unknown or unauthorized tool_id", is_error=True) + + if name == MCP_PROXY_SCHEMA_TOOL_NAME: + return _text_tool_result(json.dumps(_proxy_schema_result(tool)), is_error=False) + if name != MCP_PROXY_CALL_TOOL_NAME: + raise HTTPException(status_code=400, detail=f"Unknown MCP proxy tool: {name}") + + tool_arguments: Final = arguments.get("arguments", {}) # mutable-ok: JSON Schema validator consumes mapping + if not isinstance(tool_arguments, dict): + return _text_tool_result("arguments must be an object", is_error=True) + try: + validate(instance=tool_arguments, schema=tool.inputSchema) + except JsonSchemaValidationError as exc: + return _text_tool_result(f"Invalid arguments: {exc.message}", is_error=True) + + return await handle_mcp_tool_call( + tool_name=_mcp_proxy_identity(tool)["tool_name"], + arguments=tool_arguments, + user_api_key_dict=user_api_key_dict, + requested_server_id=_mcp_proxy_identity(tool)["server_id"], + client_ip=client_ip, + mcp_servers=mcp_servers, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers, + litellm_logging_obj=litellm_logging_obj, + ) + + async def handle_mcp_tool_call( tool_name: str, arguments: dict[str, Any], @@ -362,6 +595,7 @@ async def handle_mcp_tool_call( oauth2_headers: dict[str, str] | None = None, raw_headers: dict[str, str] | None = None, litellm_logging_obj: LiteLLMLoggingObj | None = None, + requested_server_id: str | None = None, ) -> CallToolResult: from litellm.proxy._experimental.mcp_server.server import ( _get_allowed_mcp_servers, @@ -400,4 +634,5 @@ async def handle_mcp_tool_call( oauth2_headers=oauth2_headers, raw_headers=raw_headers, litellm_logging_obj=litellm_logging_obj, + requested_server_id=requested_server_id, ) diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 71475320c2c..4d5cdcc8003 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -17027,6 +17027,134 @@ "mcp_app" ] } + }, + "/mcp/proxy": { + "delete": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_delete", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "get": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "head": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_head", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "options": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_options", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "patch": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_patch", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "post": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_post", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + }, + "put": { + "description": "Serve the fixed three-tool MCP proxy surface.", + "operationId": "proxy_mcp_route_mcp_proxy_put", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + } + }, + "summary": "Proxy Mcp Route", + "tags": [ + "mcp_app" + ] + } } } }, diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 1c8608305a2..d746cfd38d8 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -502,6 +502,7 @@ class LiteLLMRoutes(enum.Enum): mcp_inference_routes = [ "/mcp", "/mcp/", + "/mcp/proxy", "/mcp/{subpath}", "/mcp/tools", "/mcp/tools/list", diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a617aec9f5c..b22e9bba15b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -18484,6 +18484,31 @@ async def _stream_mcp_asgi_response(handle_fn, scope: dict, receive) -> "Streami ######################################################## +@app.api_route( + "/mcp/proxy", + methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"], # mutable-ok: FastAPI route methods +) +async def proxy_mcp_route(request: Request) -> Response: + """Serve the fixed three-tool MCP proxy surface.""" + from litellm.proxy._experimental.mcp_server.mcp_context import ( # pyright: ignore[reportPrivateUsage] # route-owned mode + _mcp_proxy_mode, # pyright: ignore[reportPrivateUsage] # route-owned mode + ) + from litellm.proxy._experimental.mcp_server.server import handle_streamable_http_mcp + from litellm.proxy._experimental.mcp_server.utils import is_mcp_available + + if not is_mcp_available(): + raise HTTPException(status_code=404, detail="Not Found") + + token: Final = _mcp_proxy_mode.set(True) + try: + scope: Final = dict(request.scope) # mutable-ok: ASGI scope rewrite + scope["_original_path"] = scope.get("path", "") + scope["path"] = BASE_MCP_ROUTE + return await _stream_mcp_asgi_response(handle_streamable_http_mcp, scope, request.receive) + finally: + _mcp_proxy_mode.reset(token) + + @app.api_route( BASE_MCP_ROUTE, methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"], diff --git a/tests/mcp_tests/test_proxy_mcp_e2e.py b/tests/mcp_tests/test_proxy_mcp_e2e.py index 2dd57e13d3b..a97eed82e18 100644 --- a/tests/mcp_tests/test_proxy_mcp_e2e.py +++ b/tests/mcp_tests/test_proxy_mcp_e2e.py @@ -1,4 +1,5 @@ import asyncio +import json import os import socket import subprocess @@ -134,15 +135,11 @@ def math_streamable_http_server() -> str: @pytest.fixture(scope="session") -def proxy_server_url( - tmp_path_factory: pytest.TempPathFactory, math_streamable_http_server: str -): +def proxy_server_url(tmp_path_factory: pytest.TempPathFactory, math_streamable_http_server: str): config_dir = tmp_path_factory.mktemp("mcp_e2e") config_path = config_dir / "config.yaml" config = yaml.safe_load(CONFIG_TEMPLATE_PATH.read_text()) - config["mcp_servers"]["math_streamable_http"][ - "url" - ] = f"{math_streamable_http_server}/mcp" + config["mcp_servers"]["math_streamable_http"]["url"] = f"{math_streamable_http_server}/mcp" config_path.write_text(yaml.safe_dump(config)) server_url, server, thread, sock = _start_proxy_server(str(config_path)) @@ -177,9 +174,7 @@ class TestProxyMcpSimpleConnections: assert text == "7" @pytest.mark.asyncio - async def test_proxy_mcp_streamable_http_roundtrip( - self, proxy_server_url: str - ) -> None: + async def test_proxy_mcp_streamable_http_roundtrip(self, proxy_server_url: str) -> None: async with asyncio.timeout(20): async with streamablehttp_client( url=f"{proxy_server_url}/mcp", @@ -200,9 +195,7 @@ class TestProxyMcpSimpleConnections: assert text == "11" @pytest.mark.asyncio - async def test_proxy_mcp_lists_all_servers_without_header( - self, proxy_server_url: str - ) -> None: + async def test_proxy_mcp_lists_all_servers_without_header(self, proxy_server_url: str) -> None: async with asyncio.timeout(20): async with streamablehttp_client( url=f"{proxy_server_url}/mcp", @@ -220,20 +213,14 @@ class TestProxyMcpSimpleConnections: } assert expected_tool_names <= tool_names - async def _call_and_get_text( - tool_name: str, *, a: int, b: int - ) -> str | None: - result = await session.call_tool( - tool_name, arguments={"a": a, "b": b} - ) + async def _call_and_get_text(tool_name: str, *, a: int, b: int) -> str | None: + result = await session.call_tool(tool_name, arguments={"a": a, "b": b}) assert result.content first_content = result.content[0] return getattr(first_content, "text", None) stdio_result = await _call_and_get_text("math_stdio-add", a=2, b=3) - streamable_result = await _call_and_get_text( - "math_streamable_http-add", a=4, b=5 - ) + streamable_result = await _call_and_get_text("math_streamable_http-add", a=4, b=5) assert stdio_result == "5" assert streamable_result == "9" @@ -254,9 +241,7 @@ class TestProxyMcpStatelessBehavior: """ @pytest.mark.asyncio - async def test_independent_clients_no_shared_session( - self, proxy_server_url: str - ) -> None: + async def test_independent_clients_no_shared_session(self, proxy_server_url: str) -> None: """Two independent clients connect and operate without sharing session state.""" async with asyncio.timeout(30): # --- Client A: connect, initialize, call tool --- @@ -269,9 +254,7 @@ class TestProxyMcpStatelessBehavior: ) as (read_a, write_a, _get_sid_a): async with ClientSession(read_a, write_a) as session_a: await session_a.initialize() - result_a = await session_a.call_tool( - "add", arguments={"a": 10, "b": 20} - ) + result_a = await session_a.call_tool("add", arguments={"a": 10, "b": 20}) assert result_a.content text_a = getattr(result_a.content[0], "text", None) assert text_a == "30" @@ -293,9 +276,118 @@ class TestProxyMcpStatelessBehavior: await session_b.initialize() tools = await session_b.list_tools() assert any(t.name.endswith("add") for t in tools.tools) - result_b = await session_b.call_tool( - "add", arguments={"a": 100, "b": 200} - ) + result_b = await session_b.call_tool("add", arguments={"a": 100, "b": 200}) assert result_b.content text_b = getattr(result_b.content[0], "text", None) assert text_b == "300" + + +PROXY_MODE_TOOLS = frozenset({"search_tools", "get_tool_schema", "call_tool"}) + + +def _payload(result: typing.Any) -> typing.Any: + assert result.content, f"empty tool result: {result}" + return json.loads(result.content[0].text) + + +def _proxy_session(proxy_server_url: str, **extra_headers: str): + return streamablehttp_client( + url=f"{proxy_server_url}/mcp/proxy", + headers={"Authorization": PROXY_AUTHORIZATION_HEADER, **extra_headers}, + ) + + +class TestProxyMcpSchemaDiscoveryMode: + """Drive /mcp/proxy over the real streamable-HTTP transport with the MCP SDK client: + the fixed three-tool surface, opaque-id discovery, schema-validated execution against + two upstreams that expose the same tool name, and the operations the surface refuses.""" + + @pytest.mark.asyncio + async def test_initialize_and_list_expose_only_discovery_tools(self, proxy_server_url: str) -> None: + async with asyncio.timeout(20): + async with _proxy_session(proxy_server_url) as (read, write, _sid): + async with ClientSession(read, write) as session: + init = await session.initialize() + assert init.capabilities.tools is not None + assert init.capabilities.prompts is None + assert init.capabilities.resources is None + + listed = await session.list_tools() + assert {tool.name for tool in listed.tools} == PROXY_MODE_TOOLS + + @pytest.mark.asyncio + async def test_search_schema_and_call_round_trip_keeps_server_identity(self, proxy_server_url: str) -> None: + async with asyncio.timeout(30): + async with _proxy_session(proxy_server_url) as (read, write, _sid): + async with ClientSession(read, write) as session: + await session.initialize() + + hits = _payload(await session.call_tool("search_tools", arguments={"query": "add"})) + by_name = {hit["name"]: hit for hit in hits} + assert {"math_stdio-add", "math_streamable_http-add"} <= set(by_name) + assert all("inputSchema" not in hit for hit in hits) + assert by_name["math_stdio-add"]["tool_id"] != by_name["math_streamable_http-add"]["tool_id"] + + schema = _payload( + await session.call_tool( + "get_tool_schema", arguments={"tool_id": by_name["math_stdio-add"]["tool_id"]} + ) + ) + assert schema["name"] == "math_stdio-add" + assert set(schema["inputSchema"]["required"]) == {"a", "b"} + assert schema["outputSchema"]["properties"]["result"]["type"] == "integer" + + stdio = await session.call_tool( + "call_tool", + arguments={"tool_id": by_name["math_stdio-add"]["tool_id"], "arguments": {"a": 3, "b": 4}}, + ) + http = await session.call_tool( + "call_tool", + arguments={ + "tool_id": by_name["math_streamable_http-add"]["tool_id"], + "arguments": {"a": 5, "b": 6}, + }, + ) + assert stdio.isError is False and stdio.content[0].text == "7" + assert http.isError is False and http.content[0].text == "11" + + @pytest.mark.asyncio + async def test_server_scope_header_narrows_discovery(self, proxy_server_url: str) -> None: + async with asyncio.timeout(20): + async with _proxy_session(proxy_server_url, **{"x-mcp-servers": "math_streamable_http"}) as ( + read, + write, + _sid, + ): + async with ClientSession(read, write) as session: + await session.initialize() + hits = _payload(await session.call_tool("search_tools", arguments={"query": "add"})) + assert {hit["name"] for hit in hits} == {"math_streamable_http-add"} + + @pytest.mark.asyncio + async def test_rejections_never_reach_upstream(self, proxy_server_url: str) -> None: + from mcp.shared.exceptions import McpError + from mcp.types import METHOD_NOT_FOUND + + async with asyncio.timeout(30): + async with _proxy_session(proxy_server_url) as (read, write, _sid): + async with ClientSession(read, write) as session: + await session.initialize() + hits = _payload(await session.call_tool("search_tools", arguments={"query": "add"})) + tool_id = next(hit["tool_id"] for hit in hits if hit["name"] == "math_stdio-add") + + bad_args = await session.call_tool( + "call_tool", arguments={"tool_id": tool_id, "arguments": {"a": "three", "b": 4}} + ) + assert bad_args.isError is True and "Invalid arguments" in bad_args.content[0].text + + stale = await session.call_tool("get_tool_schema", arguments={"tool_id": "0" * 32}) + assert stale.isError is True and "unauthorized tool_id" in stale.content[0].text + + direct = await session.call_tool("math_stdio-add", arguments={"a": 1, "b": 2}) + assert direct.isError is True and "unavailable on /mcp/proxy" in direct.content[0].text + + for operation in (session.list_prompts, session.list_resources): + with pytest.raises(McpError) as refused: + await operation() + assert refused.value.error.code == METHOD_NOT_FOUND diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py new file mode 100644 index 00000000000..e86fc27fed7 --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py @@ -0,0 +1,354 @@ +import json +from collections.abc import Iterator +from unittest.mock import AsyncMock, patch + +import pytest +from mcp.types import CallToolResult, TextContent, Tool + +from litellm.proxy._experimental.mcp_server import server +from litellm.proxy._experimental.mcp_server.faults.list_outcomes import AggregateToolListing +from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager +from litellm.proxy._experimental.mcp_server.tool_search import ( + MCP_PROXY_CALL_TOOL_NAME, + MCP_PROXY_SCHEMA_TOOL_NAME, + MCP_PROXY_SEARCH_TOOL_NAME, + handle_mcp_proxy_tool, + mcp_proxy_tool_id, + with_mcp_proxy_identity, +) +from litellm.proxy._types import LiteLLM_ObjectPermissionTable, UserAPIKeyAuth +from litellm.types.mcp import MCPTransport +from litellm.types.mcp_server.mcp_server_manager import MCPServer + +TOOL = Tool.model_validate( + { + "name": "math_stdio-add", + "description": "Add two numbers", + "inputSchema": { + "type": "object", + "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}, + "required": ["a", "b"], + }, + "outputSchema": {"type": "object"}, + "_meta": {"litellm.ai/proxy_tool_identity": {"server_id": "server-1", "tool_name": "math_stdio-add"}}, + } +) +AUTH = UserAPIKeyAuth(api_key="key") + + +def _text(result: CallToolResult) -> object: + return json.loads(result.content[0].text) + + +@pytest.mark.asyncio +async def test_proxy_search_returns_opaque_id_and_schema() -> None: + with ( + patch( # test-quality-ok: isolate authorized catalog owner + "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", + new_callable=AsyncMock, + return_value=AggregateToolListing(tools=[TOOL], outcomes={}), + ), + ): + result = await handle_mcp_proxy_tool(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, AUTH) + + item = _text(result)[0] + assert item["tool_id"] == mcp_proxy_tool_id(TOOL) + assert item["name"] == TOOL.name + assert "inputSchema" not in item + assert "outputSchema" not in item + assert len(item["tool_id"]) == 32 + + +@pytest.mark.asyncio +async def test_proxy_schema_and_call_resolve_current_authorized_catalog() -> None: + executed = CallToolResult(content=[TextContent(type="text", text="3")], isError=False) + with ( + patch( # test-quality-ok: isolate authorized catalog owner + "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", + new_callable=AsyncMock, + return_value=AggregateToolListing(tools=[TOOL], outcomes={}), + ), + patch( # test-quality-ok: isolate execution delegate seam + "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_tool_call", + new_callable=AsyncMock, + return_value=executed, + ) as call, + ): + schema = await handle_mcp_proxy_tool( + MCP_PROXY_SCHEMA_TOOL_NAME, + {"tool_id": mcp_proxy_tool_id(TOOL)}, + AUTH, + ) + result = await handle_mcp_proxy_tool( + MCP_PROXY_CALL_TOOL_NAME, + {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": {"a": 1, "b": 2}}, + AUTH, + ) + + assert _text(schema)["inputSchema"] == TOOL.inputSchema + assert result is executed + assert call.await_args.kwargs["tool_name"] == TOOL.name + assert call.await_args.kwargs["arguments"] == {"a": 1, "b": 2} + assert call.await_args.kwargs["requested_server_id"] == "server-1" + + +@pytest.mark.asyncio +async def test_proxy_rejects_stale_id_and_invalid_arguments_before_dispatch() -> None: + with ( + patch( # test-quality-ok: isolate authorized catalog owner + "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", + new_callable=AsyncMock, + return_value=AggregateToolListing(tools=[TOOL], outcomes={}), + ), + patch( # test-quality-ok: isolate execution delegate seam + "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_tool_call", new_callable=AsyncMock + ) as call, + ): + stale = await handle_mcp_proxy_tool(MCP_PROXY_SCHEMA_TOOL_NAME, {"tool_id": "stale"}, AUTH) + invalid = await handle_mcp_proxy_tool( + MCP_PROXY_CALL_TOOL_NAME, + {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": "wrong"}, + AUTH, + ) + falsy = await handle_mcp_proxy_tool( + MCP_PROXY_CALL_TOOL_NAME, + {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": False}, + AUTH, + ) + invalid_schema = await handle_mcp_proxy_tool( + MCP_PROXY_CALL_TOOL_NAME, + {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": {"a": "wrong"}}, + AUTH, + ) + + assert stale.isError is True + assert invalid.isError is True + assert falsy.isError is True + assert invalid_schema.isError is True + call.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_proxy_call_builds_logging_object() -> None: + from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode + + sentinel = object() + result = CallToolResult(content=[TextContent(type="text", text="ok")], isError=False) + token = _mcp_proxy_mode.set(True) + try: + with ( + patch.object( # test-quality-ok: isolate logging pipeline seam + server, "_build_virtual_call_logging_obj", new_callable=AsyncMock, return_value=sentinel + ) as build, + patch( # test-quality-ok: isolate proxy dispatch seam + "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_proxy_tool", + new_callable=AsyncMock, + return_value=result, + ) as handle, + ): + actual = await server._dispatch_virtual_mcp_tool( + name=MCP_PROXY_CALL_TOOL_NAME, + arguments={"tool_id": "id", "arguments": {}}, + user_api_key_auth=AUTH, + client_ip=None, + ) + finally: + _mcp_proxy_mode.reset(token) + + assert actual is result + build.assert_awaited_once() + assert handle.await_args.kwargs["litellm_logging_obj"] is sentinel + + +@pytest.mark.asyncio +async def test_proxy_call_rejects_non_proxy_tool_names() -> None: + from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode + + token = _mcp_proxy_mode.set(True) + try: + result = await server._dispatch_virtual_mcp_tool( + name="math_stdio-add", + arguments={"a": 1, "b": 2}, + user_api_key_auth=AUTH, + client_ip=None, + ) + finally: + _mcp_proxy_mode.reset(token) + + assert result is not None + assert result.isError is True + assert "unavailable" in result.content[0].text + + +@pytest.mark.asyncio +async def test_proxy_rejects_non_tool_protocol_operations() -> None: + from mcp.shared.exceptions import McpError + from pydantic import AnyUrl + + from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode + + token = _mcp_proxy_mode.set(True) + try: + with pytest.raises(McpError): + await server.list_prompts() + with pytest.raises(McpError): + await server.get_prompt("prompt", {}) + with pytest.raises(McpError): + await server.list_resources() + with pytest.raises(McpError): + await server.list_resource_templates() + with pytest.raises(McpError): + await server.read_resource(AnyUrl("https://example.com/resource")) + finally: + _mcp_proxy_mode.reset(token) + + +@pytest.mark.asyncio +async def test_proxy_list_mode_has_fixed_definitions_without_search_flag() -> None: + from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode + + token = _mcp_proxy_mode.set(True) + try: + with patch( # test-quality-ok: isolate authenticated MCP context seam + "litellm.proxy._experimental.mcp_server.server.get_or_extract_auth_context", + new_callable=AsyncMock, + return_value=(AUTH, None, None, None, None, None, None), + ): + tools = await server.handle_list_tools() + options = server.server.create_initialization_options() + finally: + _mcp_proxy_mode.reset(token) + + assert {tool.name for tool in tools} == { + MCP_PROXY_SEARCH_TOOL_NAME, + MCP_PROXY_SCHEMA_TOOL_NAME, + MCP_PROXY_CALL_TOOL_NAME, + } + assert options.capabilities.prompts is None + assert options.capabilities.resources is None + assert options.capabilities.tools is not None + + +def _server(server_id: str, name: str, **overrides: object) -> MCPServer: + return MCPServer( + server_id=server_id, + name=name, + server_name=name, + url=f"http://{name}.test", + transport=MCPTransport.http, + **overrides, + ) + + +def _upstream_tool(prefix: str, name: str) -> Tool: + return Tool( + name=f"{prefix}-{name}", + description=f"{name} numbers", + inputSchema={"type": "object", "properties": {"a": {"type": "integer"}}, "required": ["a"]}, + ) + + +def _auth(**object_permission: object) -> UserAPIKeyAuth: + return UserAPIKeyAuth( + api_key="sk-scope", + object_permission=LiteLLM_ObjectPermissionTable(object_permission_id="scope", **object_permission), + ) + + +def _ids(result: CallToolResult) -> dict[str, str]: + return {item["name"]: item["tool_id"] for item in _text(result)} + + +class TestMcpProxyAuthorizationScope: + """The real catalog resolver runs (server grants, tool grants, scope header, sentinel); only the + upstream tools/list fetch and the final upstream dispatch are faked.""" + + ALPHA = _server("srv-alpha", "alpha", tool_name_to_display_name={"add": "Add Numbers"}) + BETA = _server("srv-beta", "beta") + ALPHA_ADD = with_mcp_proxy_identity(_upstream_tool("alpha", "add"), "srv-alpha") + ALPHA_MULTIPLY = with_mcp_proxy_identity(_upstream_tool("alpha", "multiply"), "srv-alpha") + BETA_ADD = with_mcp_proxy_identity(_upstream_tool("beta", "add"), "srv-beta") + + @pytest.fixture + def rig(self) -> Iterator[AsyncMock]: + upstream = { + "srv-alpha": [_upstream_tool("alpha", "add"), _upstream_tool("alpha", "multiply")], + "srv-beta": [_upstream_tool("beta", "add")], + } + + async def fetch(server: MCPServer, **_: object) -> list[Tool]: + return list(upstream[server.server_id]) + + dispatched = AsyncMock( + return_value=CallToolResult(content=[TextContent(type="text", text="ok")], isError=False) + ) + global_mcp_server_manager.registry.update({"srv-alpha": self.ALPHA, "srv-beta": self.BETA}) + with ( + patch.object( # test-quality-ok: the upstream MCP server is the only faked collaborator + global_mcp_server_manager, "_get_tools_from_server", new=AsyncMock(side_effect=fetch) + ), + patch.object(global_mcp_server_manager, "call_tool", new=dispatched), # test-quality-ok: dispatch seam + ): + yield dispatched + + async def _proxy( + self, name: str, arguments: dict[str, object], auth: UserAPIKeyAuth, **kwargs: object + ) -> CallToolResult: + return await handle_mcp_proxy_tool(name, arguments, auth, **kwargs) + + @pytest.mark.asyncio + async def test_search_and_schema_are_bounded_by_the_key_server_grant(self, rig: AsyncMock) -> None: + granted = _auth(mcp_servers=["srv-alpha"]) + + assert _ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, granted)) == { + "alpha-add": mcp_proxy_tool_id(self.ALPHA_ADD), + "alpha-multiply": mcp_proxy_tool_id(self.ALPHA_MULTIPLY), + } + denied_schema = await self._proxy( + MCP_PROXY_SCHEMA_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.BETA_ADD)}, granted + ) + denied_call = await self._proxy( + MCP_PROXY_CALL_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.BETA_ADD), "arguments": {"a": 1}}, granted + ) + assert denied_schema.isError is True and denied_call.isError is True + rig.assert_not_awaited() + + @pytest.mark.asyncio + async def test_no_mcp_servers_sentinel_hides_every_tool(self, rig: AsyncMock) -> None: + result = await self._proxy( + MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, _auth(mcp_servers=["no-mcp-servers"]) + ) + assert _text(result) == [] + rig.assert_not_awaited() + + @pytest.mark.asyncio + async def test_tool_grant_hides_ungranted_tools_and_blocks_their_ids(self, rig: AsyncMock) -> None: + scoped = _auth(mcp_servers=["srv-alpha"], mcp_tool_permissions={"srv-alpha": ["add"]}) + + assert set(_ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, scoped))) == {"alpha-add"} + blocked = await self._proxy( + MCP_PROXY_CALL_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.ALPHA_MULTIPLY), "arguments": {"a": 1}}, scoped + ) + assert blocked.isError is True + rig.assert_not_awaited() + + @pytest.mark.asyncio + async def test_same_named_tools_keep_distinct_ids_and_dispatch_to_their_own_server(self, rig: AsyncMock) -> None: + both = _auth(mcp_servers=["srv-alpha", "srv-beta"]) + + ids = _ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, both)) + assert set(ids) == {"alpha-add", "beta-add"}, "display-name overrides must not rename proxy identities" + assert ids["alpha-add"] != ids["beta-add"] + + result = await self._proxy(MCP_PROXY_CALL_TOOL_NAME, {"tool_id": ids["beta-add"], "arguments": {"a": 1}}, both) + assert result.isError is False + rig.assert_awaited_once() + assert rig.await_args.kwargs["server_name"] == "beta" + assert rig.await_args.kwargs["name"] == "add" + + @pytest.mark.asyncio + async def test_server_scope_header_narrows_search_within_the_grant(self, rig: AsyncMock) -> None: + both = _auth(mcp_servers=["srv-alpha", "srv-beta"]) + scoped = await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, both, mcp_servers=["beta"]) + assert set(_ids(scoped)) == {"beta-add"} + rig.assert_not_awaited() diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 56a57f4f7ff..5c7accd9120 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -8542,6 +8542,50 @@ export interface paths { patch?: never; trace?: never; }; + "/mcp/proxy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + get: operations["proxy_mcp_route_mcp_proxy_get"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + put: operations["proxy_mcp_route_mcp_proxy_put"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + post: operations["proxy_mcp_route_mcp_proxy_post"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + delete: operations["proxy_mcp_route_mcp_proxy_delete"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + options: operations["proxy_mcp_route_mcp_proxy_options"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + head: operations["proxy_mcp_route_mcp_proxy_head"]; + /** + * Proxy Mcp Route + * @description Serve the fixed three-tool MCP proxy surface. + */ + patch: operations["proxy_mcp_route_mcp_proxy_patch"]; + trace?: never; + }; "/memory-usage-in-mem-cache": { parameters: { query?: never; @@ -50983,6 +51027,146 @@ export interface operations { }; }; }; + proxy_mcp_route_mcp_proxy_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_delete: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_options: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_head: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + proxy_mcp_route_mcp_proxy_patch: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; memory_usage_in_mem_cache_memory_usage_in_mem_cache_get: { parameters: { query?: never; From 5a7919f3f5ecab734946c1a2e0ea8e81b859c046 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 17:35:43 -0700 Subject: [PATCH 060/105] test(proxy): isolate reaper state and reap Prisma fixture children --- tests/test_litellm/proxy/db/conftest.py | 15 +++++++++++--- .../proxy/db/test_query_engine_reaper.py | 20 +++++++++++++------ tests/test_litellm/proxy/test_proxy_cli.py | 10 +++++++++- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/tests/test_litellm/proxy/db/conftest.py b/tests/test_litellm/proxy/db/conftest.py index 03d7ea81257..d3226b0ec50 100644 --- a/tests/test_litellm/proxy/db/conftest.py +++ b/tests/test_litellm/proxy/db/conftest.py @@ -6,7 +6,7 @@ import time from collections.abc import Generator from dataclasses import dataclass from pathlib import Path -from typing import Optional +from typing import Final, Optional import pytest @@ -122,10 +122,18 @@ class FakePrismaCli: return [json.loads(line) for line in self.calls_file.read_text().splitlines()] def grandchild_is_gone(self, within_seconds: float) -> bool: - deadline = time.monotonic() + within_seconds + pid: Final = int(self.grandchild_pidfile.read_text()) + deadline: Final = time.monotonic() + within_seconds while time.monotonic() < deadline: + if os.name != "nt": + try: + reaped_pid, _ = os.waitpid(pid, os.WNOHANG) + if reaped_pid == pid: + return True + except ChildProcessError: + pass try: - os.kill(int(self.grandchild_pidfile.read_text()), 0) + os.kill(pid, 0) except ProcessLookupError: return True time.sleep(0.05) @@ -154,3 +162,4 @@ def fake_prisma_cli(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Generato os.kill(int(cli.grandchild_pidfile.read_text()), signal.SIGKILL) except ProcessLookupError: pass + assert cli.grandchild_is_gone(within_seconds=5) diff --git a/tests/test_litellm/proxy/db/test_query_engine_reaper.py b/tests/test_litellm/proxy/db/test_query_engine_reaper.py index efcecb4bc08..5018176854e 100644 --- a/tests/test_litellm/proxy/db/test_query_engine_reaper.py +++ b/tests/test_litellm/proxy/db/test_query_engine_reaper.py @@ -3,6 +3,7 @@ import signal import subprocess import sys import time +from typing import Final from unittest.mock import MagicMock, patch import pytest @@ -15,7 +16,6 @@ from litellm.proxy.db.query_engine_reaper import ( _try_reap, list_orphaned_engine_pids, reap_orphaned_engines, - set_child_subreaper, start_query_engine_reaper, terminate_and_reap, terminate_and_reap_all, @@ -79,11 +79,19 @@ class TestListOrphanedEnginePids: class TestSetChildSubreaper: def test_matches_platform_capability(self): - result = set_child_subreaper() - if sys.platform.startswith("linux"): - assert result is True - else: - assert result is False + result: Final = subprocess.run( + [ + sys.executable, + "-c", + "import sys; " + "from litellm.proxy.db.query_engine_reaper import set_child_subreaper; " + "assert set_child_subreaper() is sys.platform.startswith('linux')", + ], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0, result.stderr @pytest.mark.skipif(sys.platform == "win32", reason="POSIX signals and waitpid") diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index 0c20d5e0ff0..c76ff189a8a 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -1525,7 +1525,12 @@ class TestProxyInitializationHelpers: def capture_run(self): captured["options"] = dict(self.options) - with patch("gunicorn.app.base.BaseApplication.run", capture_run): + with ( + patch("gunicorn.app.base.BaseApplication.run", capture_run), + patch( # test-quality-ok: option tests must not start a thread or change the pytest worker's child ownership + "litellm.proxy.proxy_cli.start_query_engine_reaper" + ), + ): ProxyInitializationHelpers._run_gunicorn_server( host="127.0.0.1", port=4010, @@ -1553,6 +1558,9 @@ class TestProxyInitializationHelpers: with ( patch("gunicorn.app.base.BaseApplication.run", capture_run), patch("builtins.print") as mock_print, + patch( # test-quality-ok: option tests must not start a thread or change the pytest worker's child ownership + "litellm.proxy.proxy_cli.start_query_engine_reaper" + ), ): ProxyInitializationHelpers._run_gunicorn_server( host="127.0.0.1", From 94a81f003e71cba637197f4951ae005c8a201e9b Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 17:40:27 -0700 Subject: [PATCH 061/105] bump: litellm-enterprise 0.1.65 -> 0.1.66 --- enterprise/pyproject.toml | 4 ++-- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml index 3699087dbfa..903c5155a12 100644 --- a/enterprise/pyproject.toml +++ b/enterprise/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "litellm-enterprise" -version = "0.1.65" +version = "0.1.66" 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.65" +version = "0.1.66" version_files = [ "pyproject.toml:^version", "../pyproject.toml:litellm-enterprise==", diff --git a/pyproject.toml b/pyproject.toml index dec54f50ceb..04f2f3fd1dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ proxy = [ "azure-storage-blob>=12.28.0,<13.0", "mcp>=1.28.1,<2.0", "litellm-proxy-extras==0.4.95", - "litellm-enterprise==0.1.65", + "litellm-enterprise==0.1.66", "RestrictedPython>=8.5,<9.0", "rich>=13.9.4,<14.0", "InquirerPy>=0.3.4,<1.0", diff --git a/uv.lock b/uv.lock index 23a44e7dd86..0fe787645a2 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-09-05T20:24:07.535116Z" +exclude-newer = "2026-09-06T00:40:30.433549Z" exclude-newer-span = "P3D" [manifest] @@ -4767,7 +4767,7 @@ proxy-dev = [ [[package]] name = "litellm-enterprise" -version = "0.1.65" +version = "0.1.66" source = { editable = "enterprise" } [[package]] From 810d48f28fcdd0e2b29a64b5a238aa9697fae8e7 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 17:40:12 -0700 Subject: [PATCH 062/105] chore(ci): extend diskcache scan exception to October 1 --- osv-scanner.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osv-scanner.toml b/osv-scanner.toml index 5b0339bdcd0..3e070fc8cf7 100644 --- a/osv-scanner.toml +++ b/osv-scanner.toml @@ -1,6 +1,6 @@ [[IgnoredVulns]] id = "GHSA-w8v5-vhqr-4h9v" -ignoreUntil = 2026-09-09 +ignoreUntil = 2026-10-01 reason = "diskcache has no fixed release published; remove this entry once one exists" [[IgnoredVulns]] From 529b8706ee4a35ca479301242cb07f74e45d6775 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:51:13 -0700 Subject: [PATCH 063/105] test(spend-tracking): mock the spend-log scan through the transaction its caller now opens --- .../test_common_daily_activity.py | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py index d214ab6b5ac..6cd900cb041 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py +++ b/tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -518,6 +518,26 @@ async def test_get_api_key_metadata_permanent_miss_never_pages_tokens_or_reads_s assert all("take" not in call.kwargs and "skip" not in call.kwargs for call in token_lookups) +def _spend_log_transaction(mock_prisma: MagicMock, rows: list[dict[str, str | None]]) -> AsyncMock: + transaction = MagicMock() + transaction.execute_raw = AsyncMock(return_value=0) + transaction.query_raw = AsyncMock(return_value=rows) + mock_prisma.db.tx.return_value.__aenter__.return_value = transaction + return transaction.query_raw + + +def _spend_log_row(digest: str, key_alias: str, user_id: str) -> dict[str, str | None]: + return { + "digest": digest, + "first_alias": key_alias, + "last_alias": key_alias, + "first_team": None, + "last_team": None, + "first_owner": user_id, + "last_owner": user_id, + } + + @pytest.mark.asyncio async def test_get_api_key_metadata_permanent_miss_with_a_window_reads_spend_logs_once_within_it(): from litellm.proxy.utils import hash_token @@ -529,14 +549,13 @@ async def test_get_api_key_metadata_permanent_miss_with_a_window_reads_spend_log mock_prisma.db.litellm_deletedverificationtoken.find_many = AsyncMock(return_value=[]) mock_prisma.db.litellm_usertable.find_many = AsyncMock(return_value=[]) mock_prisma.db.query_raw = AsyncMock(return_value=[]) + spend_log_query_raw = _spend_log_transaction(mock_prisma, []) result = await get_api_key_metadata(prisma_client=mock_prisma, api_keys={double_hashed}, spend_logs_window=window) assert double_hashed not in result - assert mock_prisma.db.query_raw.await_count == 3 - ((_, digests, start, end),) = [ - call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0] - ] + assert mock_prisma.db.query_raw.await_count == 2 + ((_, digests, start, end),) = [call.args for call in spend_log_query_raw.call_args_list] assert digests == [double_hashed] assert (start, end) == window @@ -559,12 +578,10 @@ async def test_get_daily_activity_recovers_a_session_key_alias_from_spend_logs_a return_value=[SimpleNamespace(user_id="session-user", user_email="session@example.com")] ) - async def query_raw(sql, *params): - if "LiteLLM_SpendLogs" in sql: - return [{"digest": session_digest, "key_alias": "cli-session-alias", "team_id": None, "user_id": "session-user"}] - return [] - - mock_prisma.db.query_raw = AsyncMock(side_effect=query_raw) + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + spend_log_query_raw = _spend_log_transaction( + mock_prisma, [_spend_log_row(session_digest, "cli-session-alias", "session-user")] + ) result = await get_daily_activity( prisma_client=mock_prisma, @@ -583,9 +600,7 @@ async def test_get_daily_activity_recovers_a_session_key_alias_from_spend_logs_a key_metadata = result.results[0].breakdown.api_keys[session_digest].metadata assert key_metadata.key_alias == "cli-session-alias" assert key_metadata.user_email == "session@example.com" - ((_, digests, start, end),) = [ - call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0] - ] + ((_, digests, start, end),) = [call.args for call in spend_log_query_raw.call_args_list] assert digests == [session_digest] assert (start, end) == (datetime(2023, 12, 31), datetime(2024, 1, 3)) @@ -2191,12 +2206,10 @@ async def test_get_api_key_metadata_resolves_session_key_via_spend_log_window(): return_value=[SimpleNamespace(user_id="user-42", user_email="user42@example.com")] ) - async def query_raw(sql, *params): - if "LiteLLM_SpendLogs" in sql: - return [{"digest": session_digest, "key_alias": "cli-session-user-42", "team_id": None, "user_id": "user-42"}] - return [] - - mock_prisma.db.query_raw = AsyncMock(side_effect=query_raw) + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + spend_log_query_raw = _spend_log_transaction( + mock_prisma, [_spend_log_row(session_digest, "cli-session-user-42", "user-42")] + ) result = await get_api_key_metadata( prisma_client=mock_prisma, @@ -2207,8 +2220,7 @@ async def test_get_api_key_metadata_resolves_session_key_via_spend_log_window(): assert result[session_digest]["key_alias"] == "cli-session-user-42" assert result[session_digest]["user_id"] == "user-42" assert result[session_digest]["user_email"] == "user42@example.com" - spend_log_calls = [call.args for call in mock_prisma.db.query_raw.call_args_list if "LiteLLM_SpendLogs" in call.args[0]] - ((_, digests, start, end),) = spend_log_calls + ((_, digests, start, end),) = [call.args for call in spend_log_query_raw.call_args_list] assert digests == [session_digest] assert (start, end) == (datetime(2026, 9, 7), datetime(2026, 9, 10)) From 24ee66328a7bf1a791dc0f7d81051165611877c9 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 17:55:21 -0700 Subject: [PATCH 064/105] test: bound Datadog read-back retries using reset headers --- tests/e2e/logging/datadog_reader.py | 114 ++++++++----- tests/e2e/logging/test_datadog_reader.py | 203 +++++++++++++++++++++++ 2 files changed, 278 insertions(+), 39 deletions(-) diff --git a/tests/e2e/logging/datadog_reader.py b/tests/e2e/logging/datadog_reader.py index 7b4372ef9ba..368c20cb6aa 100644 --- a/tests/e2e/logging/datadog_reader.py +++ b/tests/e2e/logging/datadog_reader.py @@ -12,8 +12,12 @@ empty result. External reads go through ``e2e_http``. from __future__ import annotations +import math +import random import time +from collections.abc import Callable, Mapping from dataclasses import dataclass, field +from typing import Final import pytest from pydantic import BaseModel, ConfigDict, Field @@ -27,12 +31,28 @@ from e2e_config import ( DD_SITE, POLL_TIMEOUT, ) -from e2e_http import URL, Headers, RateLimitedError, Success, post +from e2e_http import URL, Headers, StreamingResponse, send -#: How many rate-limited responses in a row one search tolerates before the -#: hard fail; each retry sleeps a full search interval, so this rides out a -#: burst from a concurrent consumer of the org-wide search budget. -_RATE_LIMIT_RETRIES = 5 +type SearchCall = Callable[[str, float], StreamingResponse] + + +def _seconds(value: str | None) -> float | None: + if value is None: + return None + try: + seconds: Final = float(value) + except ValueError: + return None + return seconds if math.isfinite(seconds) and seconds >= 0 else None + + +def _rate_limit_delay(headers: Mapping[str, str]) -> float: + delays: Final = tuple( + delay + for name in ("x-ratelimit-reset", "retry-after") + if (delay := _seconds(headers.get(name))) is not None + ) + return max(1.0, max(delays, default=DD_SEARCH_INTERVAL)) class _DdAuthHeaders(Headers): @@ -90,6 +110,10 @@ class DdLogsReader: site: str api_key: str = field(repr=False) app_key: str = field(repr=False) + search: SearchCall | None = field(default=None, repr=False) + now: Callable[[], float] = field(default=time.monotonic, repr=False) + sleep: Callable[[float], None] = field(default=time.sleep, repr=False) + jitter: Callable[[], float] = field(default=random.random, repr=False) def events_for_marker(self, marker: str) -> list[DdLogEvent]: """Every ingested event whose attributes carry the marker. DataDog @@ -108,25 +132,28 @@ class DdLogsReader: a single event. A 429 backs off and retries - the search budget is org-wide, so another consumer can empty it under us - while any other failure stays a hard fail.""" - for _ in range(_RATE_LIMIT_RETRIES): - result = post( - URL(f"https://api.{self.site}/api/v2/logs/events/search"), - headers=_DdAuthHeaders(api_key=self.api_key, app_key=self.app_key), - json=_SearchRequest(filter=_SearchFilter(query=query)), - response_type=_SearchResponse, - timeout=30.0, - ) - match result: - case Success(data=page): - return [event.attributes for event in page.data] - case RateLimitedError(retry_after_seconds=retry_after): - time.sleep(retry_after if retry_after else DD_SEARCH_INTERVAL) - case failure: - pytest.fail(f"DataDog Logs Search API at api.{self.site} failed: {failure}") + return self._events_for_query(query, self.now() + POLL_TIMEOUT) + + def _events_for_query(self, query: str, deadline: float) -> list[DdLogEvent]: + search: Final = self.search or self._search_page + while (remaining := deadline - self.now()) > 0: + if (result := search(query, min(30.0, remaining))).ok: + return [event.attributes for event in _SearchResponse.model_validate_json(result.body).data] + if result.status_code != 429: + pytest.fail(f"DataDog Logs Search API at api.{self.site} failed with HTTP {result.status_code}") + if (delay := min(_rate_limit_delay(result.headers) + self.jitter(), deadline - self.now())) > 0: + self.sleep(delay) pytest.fail( - f"DataDog Logs Search API at api.{self.site} still rate-limited after " - f"{_RATE_LIMIT_RETRIES} retries {DD_SEARCH_INTERVAL}s apart - the org-wide " - "logs_public_search_api budget (2 requests per 10s) is exhausted by another consumer" + f"DataDog Logs Search API at api.{self.site} remained rate-limited for {POLL_TIMEOUT}s; " + "the org-wide logs_public_search_api budget is exhausted" + ) + + def _search_page(self, query: str, timeout: float) -> StreamingResponse: + return send( + URL(f"https://api.{self.site}/api/v2/logs/events/search"), + headers=_DdAuthHeaders(api_key=self.api_key, app_key=self.app_key), + json=_SearchRequest(filter=_SearchFilter(query=query)), + timeout=timeout, ) def poll_events_for_marker(self, marker: str) -> list[DdLogEvent]: @@ -140,33 +167,42 @@ class DdLogsReader: hide from the exactly-one assertion - real-DataDog jitter can surface one call's two events tens of seconds apart. Searches pace at DD_SEARCH_INTERVAL, not POLL_INTERVAL, to respect the search API's - request budget. At the deadline the last result is returned as-is.""" - deadline = time.monotonic() + POLL_TIMEOUT - while time.monotonic() < deadline: - events = self.events_for_query(query) + request budget. Discovery, quota retries, and duplicate detection share + one POLL_TIMEOUT deadline; an incomplete settle window fails closed.""" + deadline: Final = self.now() + POLL_TIMEOUT + while (remaining := deadline - self.now()) > 0: + events = self._events_for_query(query, deadline) if events: - return self._settled_events_for_query(query, events) - time.sleep(DD_SEARCH_INTERVAL) - return self.events_for_query(query) + return self._settled_events_for_query(query, events, deadline) + if (remaining := deadline - self.now()) > 0: + self.sleep(min(DD_SEARCH_INTERVAL, remaining)) + return [] - def _settled_events_for_query(self, query: str, events: list[DdLogEvent]) -> list[DdLogEvent]: + def _settled_events_for_query(self, query: str, events: list[DdLogEvent], deadline: float) -> list[DdLogEvent]: """Re-read at every search interval until the settle window closes; a duplicate ends the watch early because more waiting cannot clear it. Keep the last non-empty result: a transient empty search (index lag) must not erase events already confirmed earlier in the settle window. + A successful final search must reach the full settle window before the + shared read-back deadline; otherwise duplicate detection is incomplete. """ - settle_deadline = time.monotonic() + DD_SETTLE_SECONDS + settle_deadline: Final = self.now() + DD_SETTLE_SECONDS last_nonempty = events - while time.monotonic() < settle_deadline: - time.sleep(DD_SEARCH_INTERVAL) - latest = self.events_for_query(query) - if not latest: - continue + if len(events) > 1: + return events + while (remaining := deadline - self.now()) > 0: + self.sleep(min(DD_SEARCH_INTERVAL, remaining)) + if self.now() >= deadline: + break + latest = self._events_for_query(query, deadline) if len(latest) > 1: return latest - last_nonempty = latest - return last_nonempty + if latest: + last_nonempty = latest + if self.now() >= settle_deadline: + return last_nonempty + pytest.fail(f"DataDog log delivery could not complete its duplicate-detection window within {POLL_TIMEOUT}s") def build_dd_logs_reader() -> DdLogsReader: diff --git a/tests/e2e/logging/test_datadog_reader.py b/tests/e2e/logging/test_datadog_reader.py index 00624bd3c84..910a1cefd42 100644 --- a/tests/e2e/logging/test_datadog_reader.py +++ b/tests/e2e/logging/test_datadog_reader.py @@ -1,7 +1,14 @@ +import json +from collections.abc import Iterator, Sequence +from dataclasses import dataclass from typing import Final +import pytest + from datadog_reader import DdLogsReader from datadog_reader import _DdAuthHeaders # pyright: ignore[reportPrivateUsage] # verifies private auth-header serialization +from e2e_config import DD_SEARCH_INTERVAL, POLL_TIMEOUT +from e2e_http import StreamingResponse def test_failure_diagnostics_hide_credentials_without_changing_auth_headers() -> None: @@ -18,3 +25,199 @@ def test_failure_diagnostics_hide_credentials_without_changing_auth_headers() -> "DD-API-KEY": api_key, "DD-APPLICATION-KEY": app_key, } + + +@dataclass +class Clock: + elapsed: float = 0.0 + + def now(self) -> float: + return self.elapsed + + def sleep(self, seconds: float) -> None: + self.elapsed += seconds + + +@dataclass +class Search: + responses: Iterator[StreamingResponse] + calls: tuple[tuple[str, float], ...] = () + + def __call__(self, query: str, timeout: float) -> StreamingResponse: + self.calls += ((query, timeout),) + return next(self.responses) + + +def _page(*event_ids: str) -> StreamingResponse: + return StreamingResponse( + status_code=200, + body=json.dumps({"data": [{"attributes": {"attributes": {"id": event_id}}} for event_id in event_ids]}), + ) + + +def _reader(responses: Sequence[StreamingResponse], clock: Clock) -> tuple[DdLogsReader, Search]: + search: Final = Search(iter(responses)) + return DdLogsReader( + site="us5.datadoghq.com", + api_key="test-api-secret", + app_key="test-app-secret", + search=search, + now=clock.now, + sleep=clock.sleep, + jitter=lambda: 0.25, + ), search + + +def test_429_honors_server_reset_and_preserves_duplicate_events() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "6"}), _page("first", "duplicate")), + clock, + ) + + events: Final = reader.events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first", "duplicate") + assert clock.elapsed == 6.25 + assert search.calls == (("test-marker", 30.0), ("test-marker", 30.0)) + + +@pytest.mark.parametrize("reset", ("", "invalid", "nan", "inf", "-1")) +def test_invalid_reset_uses_search_interval(reset: str) -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": reset}), _page()), clock + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == DD_SEARCH_INTERVAL + 0.25 + + +def test_zero_reset_cannot_create_a_busy_retry_loop() -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "0"}), _page()), clock + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == 1.25 + + +def test_retry_after_is_not_shortened_by_an_earlier_reset() -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "2", "retry-after": "8"}), _page()), + clock, + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == 8.25 + + +def test_rate_limit_wait_stops_at_deadline_without_issuing_another_request() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT * 10)}),), clock + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert search.calls == (("test-marker", 30.0),) + + +def test_late_retry_cannot_receive_a_fresh_request_timeout() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT - 5)}), _page()), + clock, + ) + + assert reader.events_for_query("test-marker") == [] + assert search.calls == (("test-marker", 30.0), ("test-marker", 4.75)) + + +@pytest.mark.parametrize("status", (-1, 401, 403, 500)) +def test_non_quota_failures_are_not_retried_or_treated_as_empty_results(status: int) -> None: + clock: Final = Clock() + reader, search = _reader((StreamingResponse(status_code=status, body=""), _page()), clock) + + with pytest.raises(pytest.fail.Exception, match=f"failed with HTTP {status}"): + reader.events_for_query("test-marker") + + assert search.calls == (("test-marker", 30.0),) + assert clock.elapsed == 0 + + +def test_polling_quota_retries_share_the_original_deadline() -> None: + clock: Final = Clock() + reader, search = _reader( + (_page(), StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT)})), + clock, + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == 2 + + +def test_empty_polling_does_not_start_a_final_search_after_its_deadline() -> None: + clock: Final = Clock() + attempts: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) + reader, search = _reader((_page(),) * attempts, clock) + + assert reader.poll_events_for_query("test-marker") == [] + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == attempts + + +def test_settlement_quota_retries_keep_the_remaining_readback_budget() -> None: + clock: Final = Clock() + empty_reads: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) - 2 + reader, search = _reader( + (_page(),) * empty_reads + + (_page("first"), StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT)})), + clock, + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert search.calls[-1] == ("test-marker", DD_SEARCH_INTERVAL) + assert len(search.calls) == empty_reads + 2 + + +def test_settlement_detects_a_duplicate_on_the_final_search() -> None: + clock: Final = Clock() + reader, _ = _reader((_page("first"), _page("first"), _page(), _page("first", "duplicate")), clock) + + events: Final = reader.poll_events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first", "duplicate") + assert clock.elapsed == 30 + + +def test_settlement_keeps_confirmed_events_through_empty_searches() -> None: + clock: Final = Clock() + reader, _ = _reader((_page("first"), _page(), _page(), _page()), clock) + + events: Final = reader.poll_events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first",) + assert clock.elapsed == 30 + + +def test_late_delivery_cannot_pass_without_a_complete_settle_window() -> None: + clock: Final = Clock() + empty_reads: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) - 2 + reader, search = _reader((_page(),) * empty_reads + (_page("first"), _page("first")), clock) + + with pytest.raises(pytest.fail.Exception, match="duplicate-detection window"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == empty_reads + 2 From 43a1b2992adbb70e8fcef9c0ae431247d05743b3 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 01:00:26 +0000 Subject: [PATCH 065/105] fix(otel v2): restore the Datadog auth span and the last-wins callback merge (#40335) * fix(otel v2): restore the Datadog auth span and the last-wins callback merge Move @tracer.wrap() back onto user_api_key_auth so USE_DDTRACE=true emits the auth span again, and let a failure entry's callback_vars take part in the destination merge so the resolver picks the same account the runtime parser does Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(otel v2): drop docstrings from the two regression tests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * ci: rerun proxy-infra after the flaky test_check_migration process-tree test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yucheng Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/auth/user_api_key_auth.py | 2 +- litellm/proxy/litellm_pre_call_utils.py | 9 +- .../otel/test_otel_v2_destinations.py | 29 +++- .../proxy/auth/test_user_api_key_auth.py | 128 ++++++++++++++++++ 4 files changed, 162 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 5a2a20f8f59..6b000489d5a 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -2836,7 +2836,6 @@ async def _authorize_authenticated_request( return None -@tracer.wrap() def _seed_request_destinations(user_api_key_dict: UserAPIKeyAuth, request: Request | None = None) -> None: """Anchor the OTLP destinations this key or team overrides its traces to. @@ -2874,6 +2873,7 @@ def _seed_request_destinations(user_api_key_dict: UserAPIKeyAuth, request: Reque verbose_proxy_logger.debug("OTel V2: tenant destination resolution failed: %s", exc) +@tracer.wrap() async def user_api_key_auth( request: Request, api_key: str = fastapi.Security(api_key_header), diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index d0eb75bc29a..924f84be5f4 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -1041,7 +1041,9 @@ def resolve_tenant_otel_destinations( the request has an outcome, so honouring the filter would mean holding every span back until the call finishes. Those entries keep today's behaviour instead, where the tenant's credentials reach the backend through per-request tracer routing and - the operator's exporter is left alone. + the operator's exporter is left alone. Its ``callback_vars`` still take part in the + merge for a backend another entry made eligible, so the destination carries the + same credentials the runtime parser resolves for that request. A backend the request disabled dynamically, through the key's ``litellm_disabled_callbacks`` or the ``x-litellm-disable-callbacks`` header in @@ -1069,12 +1071,13 @@ def resolve_tenant_otel_destinations( callback for item in entries if (callback := _get_validated_callback_metadata(item=item, source="otel-destination")) is not None - if callback.callback_type != "failure" if callback.callback_name.lower() not in disabled ) return tuple( destination - for name in dict.fromkeys(callback.callback_name for callback in callbacks) + for name in dict.fromkeys( + callback.callback_name for callback in callbacks if callback.callback_type != "failure" + ) if ( destination := destination_for( name, diff --git a/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py b/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py index 1799381bada..67695d5aed8 100644 --- a/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py +++ b/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py @@ -2,7 +2,9 @@ import contextvars import time +from base64 import b64encode from collections.abc import Mapping +from functools import reduce from types import MappingProxyType import pytest @@ -49,8 +51,11 @@ from litellm.integrations.otel.presets.destinations import ( destination_for, ) from litellm.integrations.otel.presets.langfuse import langfuse_preset -from litellm.proxy._types import UserAPIKeyAuth -from litellm.proxy.litellm_pre_call_utils import resolve_tenant_otel_destinations +from litellm.proxy._types import AddTeamCallback, UserAPIKeyAuth +from litellm.proxy.litellm_pre_call_utils import ( + convert_key_logging_metadata_to_callback, + resolve_tenant_otel_destinations, +) from litellm.types.utils import StandardCallbackDynamicParams LANGFUSE_DEST = OtelDestination( @@ -1822,6 +1827,26 @@ class TestTenantConfigAgreement: assert [d.endpoint for d in destinations] == ["http://key.local/api/public/otel"] + def test_a_failure_entry_still_wins_the_merge_next_to_a_success_entry(self): + entries = [ + {**self._entry("http://team.local"), "callback_type": "success"}, + { + **self._entry("http://key.local", langfuse_public_key="pk-failure", langfuse_secret_key="sk-failure"), + "callback_type": "failure", + }, + ] + runtime = reduce( + lambda merged, entry: convert_key_logging_metadata_to_callback(AddTeamCallback(**entry), merged), + entries, + None, + ) + + destinations = resolve_tenant_otel_destinations(UserAPIKeyAuth(team_metadata={"logging": entries})) + + assert runtime.callback_vars["langfuse_host"] == "http://key.local" + assert [d.endpoint for d in destinations] == ["http://key.local/api/public/otel"] + assert destinations[0].headers["Authorization"] == f"Basic {b64encode(b'pk-failure:sk-failure').decode()}" + @pytest.fixture def premium(self, monkeypatch): from litellm.proxy import proxy_server diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index f1a269cc00a..fd289e33ea6 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -1,8 +1,13 @@ import asyncio import json import logging +import os +import subprocess +import sys from contextlib import contextmanager from datetime import datetime, timedelta +from pathlib import Path +from textwrap import dedent from types import SimpleNamespace from unittest.mock import ANY, AsyncMock, MagicMock, patch @@ -7005,3 +7010,126 @@ class TestLitellmReceivedAtStamping: assert result == earlier assert request.state.litellm_received_at == earlier + + +_RECORDING_DDTRACE = dedent( + ''' + import functools + import inspect + + + class _Span: + def __enter__(self): + return self + + def __exit__(self, *exc): + return None + + + class _Tracer: + def __init__(self): + self.spans = [] + + def wrap(self, name=None, **kwargs): + def decorator(f): + span_name = name or f"{f.__module__}.{f.__name__}" + if inspect.iscoroutinefunction(f): + + @functools.wraps(f) + async def async_wrapped(*args, **kw): + self.spans.append(span_name) + return await f(*args, **kw) + + return async_wrapped + + @functools.wraps(f) + def wrapped(*args, **kw): + self.spans.append(span_name) + return f(*args, **kw) + + return wrapped + + return decorator + + def trace(self, name, **kwargs): + return _Span() + + def current_span(self): + return None + + def current_root_span(self): + return None + + + tracer = _Tracer() + ''' +) + +_DDTRACE_AUTH_PROBE = dedent( + ''' + import asyncio + import json + + import ddtrace + from fastapi import Request + from starlette.datastructures import URL + + import litellm.proxy.proxy_server as proxy_server + from litellm.proxy._types import ProxyException + from litellm.proxy.auth.user_api_key_auth import user_api_key_auth + + proxy_server.master_key = "sk-probe" + + + async def auth(api_key): + request = Request(scope={"type": "http", "headers": [], "method": "POST", "path": "/chat/completions"}) + request._url = URL(url="/chat/completions") + try: + await user_api_key_auth( + request=request, + api_key=api_key, + azure_api_key_header="", + anthropic_api_key_header=None, + google_ai_studio_api_key_header=None, + azure_apim_header=None, + custom_litellm_key_header=None, + ) + return "accepted" + except ProxyException: + return "rejected" + + + async def main(): + outcomes = [await auth("Bearer sk-probe"), await auth("Bearer sk-wrong")] + print(json.dumps({"outcomes": outcomes, "spans": ddtrace.tracer.spans})) + + + asyncio.run(main()) + ''' +) + + +def test_user_api_key_auth_opens_a_datadog_span_for_accepted_and_rejected_keys(tmp_path: Path): + stub_root = tmp_path / "site" + (stub_root / "ddtrace").mkdir(parents=True) + (stub_root / "ddtrace" / "__init__.py").write_text(_RECORDING_DDTRACE) + probe = tmp_path / "probe.py" + probe.write_text(_DDTRACE_AUTH_PROBE) + repo_root = Path(litellm.__file__).resolve().parent.parent + env = { + **os.environ, + "USE_DDTRACE": "true", + "PYTHONPATH": os.pathsep.join( + [str(stub_root), str(repo_root)] + [p for p in (os.environ.get("PYTHONPATH"),) if p] + ), + } + + result = subprocess.run( + [sys.executable, str(probe)], env=env, cwd=repo_root, capture_output=True, text=True, check=False + ) + + assert result.returncode == 0, result.stderr[-4000:] + report = json.loads(result.stdout.strip().splitlines()[-1]) + assert report["outcomes"] == ["accepted", "rejected"] + auth_span = "litellm.proxy.auth.user_api_key_auth.user_api_key_auth" + assert [span for span in report["spans"] if span == auth_span] == [auth_span, auth_span] From 634852a18309a97f8cb24f18fabd48bb0b80c6a1 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Tue, 8 Sep 2026 18:00:01 -0700 Subject: [PATCH 066/105] fix(anthropic): key the /v1/messages prompt cache on Claude Code's session_id only The bridges derived prompt_cache_key as the first 64 chars of metadata.user_id. Claude Code packs a JSON object into that field whose prefix is the per-install device_id, so every session and subagent on one machine shared a single key, and a plain end-user id pinned all of that user's conversations to one slot. Parse the JSON and use session_id; send no key otherwise so the provider falls back to its own prompt-prefix hashing. An explicit prompt_cache_key still wins. Fixes #39145 --- .../experimental_pass_through/utils.py | 23 +++++- ...al_pass_through_adapters_transformation.py | 72 ++++++++++++++----- .../adapters/test_handler_prompt_cache_key.py | 12 ++-- .../test_responses_adapters_handler.py | 21 ++++-- .../test_responses_adapters_transformation.py | 33 ++++++--- 5 files changed, 123 insertions(+), 38 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/utils.py b/litellm/llms/anthropic/experimental_pass_through/utils.py index 55fe9c47faf..335a0e5641d 100644 --- a/litellm/llms/anthropic/experimental_pass_through/utils.py +++ b/litellm/llms/anthropic/experimental_pass_through/utils.py @@ -3,6 +3,8 @@ from collections.abc import Mapping from types import MappingProxyType from typing import TYPE_CHECKING, Final +from pydantic import BaseModel, ConfigDict, ValidationError + import litellm from litellm.types.utils import ModelInfo @@ -21,10 +23,27 @@ _EFFORT_DEGRADATION_CHAIN: Final[Mapping[str, tuple[str, ...]]] = MappingProxyTy _THINKING_OFF: Final = "none" +class _ClaudeCodeUserId(BaseModel): + """The JSON Claude Code packs into ``metadata.user_id``; only ``session_id`` is per conversation.""" + + model_config = ConfigDict(frozen=True) + + session_id: str + + def prompt_cache_key_from_user_id(user_id: object) -> str | None: - if user_id is None: + """The per-session key Claude Code carries inside ``metadata.user_id``, or nothing. + + Anthropic defines ``user_id`` as an opaque end-user id, so a plain string names a person, not + a conversation. Keying the provider cache on it pins every parallel session and subagent of that + person to one slot, which caches worse than the provider's own prompt-prefix hashing does. + """ + if not isinstance(user_id, str): + return None + try: + return _ClaudeCodeUserId.model_validate_json(user_id).session_id[:OPENAI_MAX_PROMPT_CACHE_KEY_LENGTH] or None + except ValidationError: return None - return str(user_id)[:OPENAI_MAX_PROMPT_CACHE_KEY_LENGTH] or None def litellm_logging_obj_from_kwargs(kwargs: Mapping[str, object]) -> "LiteLLMLoggingObject | None": diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 30465ca25ba..c59ec70b015 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -1,4 +1,5 @@ import base64 +import json from typing import Any, Final, cast import pytest @@ -724,9 +725,14 @@ def test_translate_anthropic_to_openai_orders_top_level_and_midturn_system(): ] -def _translate_with_metadata( - model: str, metadata: dict[str, str], custom_llm_provider: str | None -) -> dict[str, Any]: +def _claude_code_user_id(session_id: str) -> str: + return json.dumps({"device_id": "d" * 64, "account_uuid": "", "session_id": session_id}) + + +CLAUDE_CODE_USER_ID: Final = _claude_code_user_id("session-abc") + + +def _translate_with_metadata(model: str, metadata: dict[str, str], custom_llm_provider: str | None) -> dict[str, Any]: openai_request, _ = LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai( anthropic_message_request={ "model": model, @@ -739,23 +745,51 @@ def _translate_with_metadata( return cast(dict[str, Any], openai_request) -def test_translate_anthropic_to_openai_maps_user_id_to_prompt_cache_key_for_openai(): - openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": "session-abc"}, "openai") - assert openai_request["user"] == "session-abc" +def test_translate_anthropic_to_openai_maps_claude_code_session_id_to_prompt_cache_key_for_openai(): + openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": CLAUDE_CODE_USER_ID}, "openai") + assert openai_request["user"] == CLAUDE_CODE_USER_ID assert openai_request["prompt_cache_key"] == "session-abc" -def test_translate_anthropic_to_openai_truncates_prompt_cache_key_but_keeps_full_user(): - long_id = "".join(str(i % 10) for i in range(100)) - openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": long_id}, "openai") - assert openai_request["user"] == long_id - assert openai_request["prompt_cache_key"] == long_id[:64] - assert len(openai_request["prompt_cache_key"]) == 64 +def test_translate_anthropic_to_openai_gives_each_claude_code_session_its_own_prompt_cache_key(): + """BerriAI/litellm#39145: the first 64 chars of Claude Code's user_id are the per-install device_id.""" + keys = tuple( + _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": _claude_code_user_id(session_id)}, "openai")[ + "prompt_cache_key" + ] + for session_id in ("11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222") + ) + assert keys == ("11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222") + + +def test_translate_anthropic_to_openai_truncates_long_session_id_to_openai_limit(): + long_session_id = "".join(str(i % 10) for i in range(100)) + openai_request = _translate_with_metadata( + "openai/gpt-5.6-luna", {"user_id": _claude_code_user_id(long_session_id)}, "openai" + ) + assert openai_request["prompt_cache_key"] == long_session_id[:64] + + +@pytest.mark.parametrize( + "user_id", + [ + "alice", + "".join(str(i % 10) for i in range(100)), + json.dumps({"device_id": "d" * 64, "account_uuid": ""}), + json.dumps({"session_id": ""}), + json.dumps({"session_id": 123}), + "{not json", + ], +) +def test_translate_anthropic_to_openai_keeps_plain_user_id_off_prompt_cache_key(user_id: str): + openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": user_id}, "openai") + assert openai_request["user"] == user_id + assert "prompt_cache_key" not in openai_request @pytest.mark.parametrize("model", ["azure/my-gpt-5-deployment", "my-gpt-5-deployment"]) def test_translate_anthropic_to_openai_sets_prompt_cache_key_for_azure(model: str): - openai_request = _translate_with_metadata(model, {"user_id": "session-abc"}, "azure") + openai_request = _translate_with_metadata(model, {"user_id": CLAUDE_CODE_USER_ID}, "azure") assert openai_request["prompt_cache_key"] == "session-abc" @@ -772,8 +806,8 @@ def test_translate_anthropic_to_openai_sets_prompt_cache_key_for_azure(model: st def test_translate_anthropic_to_openai_skips_prompt_cache_key_when_provider_lacks_it( model: str, custom_llm_provider: str ): - openai_request = _translate_with_metadata(model, {"user_id": "session-abc"}, custom_llm_provider) - assert openai_request["user"] == "session-abc" + openai_request = _translate_with_metadata(model, {"user_id": CLAUDE_CODE_USER_ID}, custom_llm_provider) + assert openai_request["user"] == CLAUDE_CODE_USER_ID assert "prompt_cache_key" not in openai_request @@ -781,14 +815,14 @@ def test_translate_anthropic_to_openai_skips_prompt_cache_key_for_chained_litell assert "prompt_cache_key" in litellm.get_supported_openai_params( model="xai", custom_llm_provider="litellm_proxy" ) - openai_request = _translate_with_metadata("litellm_proxy/xai", {"user_id": "session-abc"}, "litellm_proxy") - assert openai_request["user"] == "session-abc" + openai_request = _translate_with_metadata("litellm_proxy/xai", {"user_id": CLAUDE_CODE_USER_ID}, "litellm_proxy") + assert openai_request["user"] == CLAUDE_CODE_USER_ID assert "prompt_cache_key" not in openai_request def test_translate_anthropic_to_openai_skips_prompt_cache_key_without_provider(): - openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": "session-abc"}, None) - assert openai_request["user"] == "session-abc" + openai_request = _translate_with_metadata("openai/gpt-5.6-luna", {"user_id": CLAUDE_CODE_USER_ID}, None) + assert openai_request["user"] == CLAUDE_CODE_USER_ID assert "prompt_cache_key" not in openai_request diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_prompt_cache_key.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_prompt_cache_key.py index f48d51dbe1e..7dc7507120f 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_prompt_cache_key.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_prompt_cache_key.py @@ -1,3 +1,4 @@ +import json import os import sys @@ -10,6 +11,7 @@ from litellm.llms.anthropic.experimental_pass_through.adapters.handler import ( ) MESSAGES = [{"role": "user", "content": "hello"}] +CLAUDE_CODE_USER_ID = json.dumps({"device_id": "d" * 64, "account_uuid": "", "session_id": "session-abc"}) def _prepare(model: str, extra_kwargs: dict[str, object], thinking: dict[str, object] | None = None): @@ -17,7 +19,7 @@ def _prepare(model: str, extra_kwargs: dict[str, object], thinking: dict[str, ob max_tokens=1024, messages=MESSAGES, model=model, - metadata={"user_id": "session-abc"}, + metadata={"user_id": CLAUDE_CODE_USER_ID}, thinking=thinking, extra_kwargs=extra_kwargs, ) @@ -26,7 +28,7 @@ def _prepare(model: str, extra_kwargs: dict[str, object], thinking: dict[str, ob def test_prepare_completion_kwargs_derives_prompt_cache_key_for_openai_provider(): completion_kwargs = _prepare("openai/gpt-5.6-luna", {"custom_llm_provider": "openai"}) - assert completion_kwargs["user"] == "session-abc" + assert completion_kwargs["user"] == CLAUDE_CODE_USER_ID assert completion_kwargs["prompt_cache_key"] == "session-abc" @@ -35,7 +37,7 @@ def test_prepare_completion_kwargs_prefers_explicit_prompt_cache_key_over_derive "openai/gpt-5.6-luna", {"custom_llm_provider": "openai", "prompt_cache_key": "explicit-key"}, ) - assert completion_kwargs["user"] == "session-abc" + assert completion_kwargs["user"] == CLAUDE_CODE_USER_ID assert completion_kwargs["prompt_cache_key"] == "explicit-key" @@ -50,13 +52,13 @@ def test_prepare_completion_kwargs_skips_prompt_cache_key_without_provider_suppo model: str, extra_kwargs: dict[str, object] ): completion_kwargs = _prepare(model, extra_kwargs) - assert completion_kwargs["user"] == "session-abc" + assert completion_kwargs["user"] == CLAUDE_CODE_USER_ID assert "prompt_cache_key" not in completion_kwargs def test_prepare_completion_kwargs_skips_prompt_cache_key_for_chained_litellm_proxy(): completion_kwargs = _prepare("litellm_proxy/xai", {"custom_llm_provider": "litellm_proxy"}) - assert completion_kwargs["user"] == "session-abc" + assert completion_kwargs["user"] == CLAUDE_CODE_USER_ID assert "prompt_cache_key" not in completion_kwargs diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_handler.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_handler.py index 3383813245a..16e8cf0e90e 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_handler.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_handler.py @@ -16,6 +16,7 @@ from litellm.llms.anthropic.experimental_pass_through.responses_adapters.handler ) MESSAGES = [{"role": "user", "content": "hello"}] +CLAUDE_CODE_USER_ID = json.dumps({"device_id": "d" * 64, "account_uuid": "", "session_id": "session-abc"}) RESPONSES_SSE_BODY = ( b"event: response.created\n" @@ -30,7 +31,19 @@ RESPONSES_SSE_BODY = ( ) -def test_build_responses_kwargs_derives_prompt_cache_key_from_user_id(): +def test_build_responses_kwargs_derives_prompt_cache_key_from_claude_code_session_id(): + responses_kwargs = _build_responses_kwargs( + max_tokens=1024, + messages=MESSAGES, + model="openai/gpt-5.6-luna", + metadata={"user_id": CLAUDE_CODE_USER_ID}, + extra_kwargs={"custom_llm_provider": "openai"}, + ) + assert responses_kwargs["user"] == CLAUDE_CODE_USER_ID[:64] + assert responses_kwargs["prompt_cache_key"] == "session-abc" + + +def test_build_responses_kwargs_sets_no_prompt_cache_key_for_plain_user_id(): responses_kwargs = _build_responses_kwargs( max_tokens=1024, messages=MESSAGES, @@ -39,7 +52,7 @@ def test_build_responses_kwargs_derives_prompt_cache_key_from_user_id(): extra_kwargs={"custom_llm_provider": "openai"}, ) assert responses_kwargs["user"] == "session-abc" - assert responses_kwargs["prompt_cache_key"] == "session-abc" + assert "prompt_cache_key" not in responses_kwargs def test_build_responses_kwargs_prefers_explicit_prompt_cache_key_over_derived(): @@ -47,10 +60,10 @@ def test_build_responses_kwargs_prefers_explicit_prompt_cache_key_over_derived() max_tokens=1024, messages=MESSAGES, model="openai/gpt-5.6-luna", - metadata={"user_id": "session-abc"}, + metadata={"user_id": CLAUDE_CODE_USER_ID}, extra_kwargs={"custom_llm_provider": "openai", "prompt_cache_key": "explicit-key"}, ) - assert responses_kwargs["user"] == "session-abc" + assert responses_kwargs["user"] == CLAUDE_CODE_USER_ID[:64] assert responses_kwargs["prompt_cache_key"] == "explicit-key" diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py index 9f8414afa38..edcc7adddb7 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py @@ -1113,17 +1113,34 @@ class TestTranslateRequestBroaderCoverage: kwargs = _ADAPTER.translate_request(req) assert len(kwargs["user"]) == 64 - def test_metadata_user_id_mapped_to_prompt_cache_key(self): - req = _make_request(metadata={"user_id": "user-42"}) + def test_metadata_claude_code_session_id_mapped_to_prompt_cache_key(self): + user_id = json.dumps({"device_id": "d" * 64, "account_uuid": "", "session_id": "session-42"}) + req = _make_request(metadata={"user_id": user_id}) kwargs = _ADAPTER.translate_request(req) - assert kwargs["prompt_cache_key"] == "user-42" + assert kwargs["user"] == user_id[:64] + assert kwargs["prompt_cache_key"] == "session-42" - def test_metadata_user_id_prompt_cache_key_truncated_to_first_64_chars(self): - long_id = "".join(str(i % 10) for i in range(100)) - req = _make_request(metadata={"user_id": long_id}) + def test_metadata_claude_code_sessions_get_distinct_prompt_cache_keys(self): + """BerriAI/litellm#39145: the first 64 chars of Claude Code's user_id are the per-install device_id.""" + keys = tuple( + _ADAPTER.translate_request( + _make_request( + metadata={"user_id": json.dumps({"device_id": "d" * 64, "account_uuid": "", "session_id": sid})} + ) + )["prompt_cache_key"] + for sid in ("11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222") + ) + assert keys == ("11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222") + + @pytest.mark.parametrize( + "user_id", + ["user-42", "".join(str(i % 10) for i in range(100)), json.dumps({"device_id": "d" * 64}), "{not json"], + ) + def test_metadata_plain_user_id_sets_no_prompt_cache_key(self, user_id: str): + req = _make_request(metadata={"user_id": user_id}) kwargs = _ADAPTER.translate_request(req) - assert kwargs["prompt_cache_key"] == long_id[:64] - assert len(kwargs["prompt_cache_key"]) == 64 + assert kwargs["user"] == user_id[:64] + assert "prompt_cache_key" not in kwargs def test_metadata_empty_user_id_sets_no_prompt_cache_key(self): req = _make_request(metadata={"user_id": ""}) From 314e573529897752c882be9aac4985931bcc26bc Mon Sep 17 00:00:00 2001 From: tin-berri Date: Tue, 8 Sep 2026 18:28:46 -0700 Subject: [PATCH 067/105] feat(auto-router): refresh family reasoning presets (#40341) --- .../public_endpoints/autorouter_presets.json | 43 +++++++++++++++---- .../src/lib/autorouter_presets.test.ts | 17 ++++---- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/litellm/proxy/public_endpoints/autorouter_presets.json b/litellm/proxy/public_endpoints/autorouter_presets.json index 7d09db31127..7a251afc076 100644 --- a/litellm/proxy/public_endpoints/autorouter_presets.json +++ b/litellm/proxy/public_endpoints/autorouter_presets.json @@ -10,7 +10,12 @@ "REASONING": ["claude-opus-5"] }, "tier_model_configs": { - "REASONING": [{ "model_name": "claude-opus-5", "litellm_params": { "reasoning_effort": "high" } }] + "REASONING": [ + { + "model_name": "claude-opus-5", + "litellm_params": { "reasoning_effort": "high" } + } + ] }, "classifier_type": "heuristic_v2", "escalation_keywords": ["LITELLM ESCALATE"], @@ -23,16 +28,21 @@ }, "anthropic_family": { "label": "Anthropic Family", - "description": "Routes across the Claude model family: Haiku for simple queries, Sonnet for medium, Opus for complex, Opus at high thinking for reasoning.", + "description": "Routes across the Claude model family: Haiku for simple queries, Sonnet for medium, Opus for complex, Fable 5.1 at high thinking for reasoning.", "complexity_router_config": { "tiers": { "SIMPLE": ["claude-haiku-4-5"], "MEDIUM": ["claude-sonnet-5"], "COMPLEX": ["claude-opus-5"], - "REASONING": ["claude-opus-5"] + "REASONING": ["claude-fable-5-1"] }, "tier_model_configs": { - "REASONING": [{ "model_name": "claude-opus-5", "litellm_params": { "reasoning_effort": "high" } }] + "REASONING": [ + { + "model_name": "claude-fable-5-1", + "litellm_params": { "reasoning_effort": "high" } + } + ] }, "classifier_type": "heuristic", "escalation_keywords": ["LITELLM ESCALATE"], @@ -73,8 +83,18 @@ "REASONING": ["claude-opus-5"] }, "tier_model_configs": { - "MEDIUM": [{ "model_name": "muse-spark-1.2", "litellm_params": { "reasoning_effort": "xhigh" } }], - "COMPLEX": [{ "model_name": "kimi-k3", "litellm_params": { "reasoning_effort": "max" } }] + "MEDIUM": [ + { + "model_name": "muse-spark-1.2", + "litellm_params": { "reasoning_effort": "xhigh" } + } + ], + "COMPLEX": [ + { + "model_name": "kimi-k3", + "litellm_params": { "reasoning_effort": "max" } + } + ] }, "classifier_type": "llm", "classifier_llm_config": { @@ -93,16 +113,21 @@ }, "openai_family": { "label": "OpenAI Family", - "description": "Routes across the GPT model family: Luna for simple queries, Terra for medium, Sol for complex, Sol at xhigh thinking for reasoning.", + "description": "Routes across the GPT model family: Luna for simple queries, Terra for medium, Sol for complex, Astra at xhigh thinking for reasoning.", "complexity_router_config": { "tiers": { "SIMPLE": ["gpt-5.6-luna"], "MEDIUM": ["gpt-5.6-terra"], "COMPLEX": ["gpt-5.6-sol"], - "REASONING": ["gpt-5.6-sol"] + "REASONING": ["gpt-6-astra"] }, "tier_model_configs": { - "REASONING": [{ "model_name": "gpt-5.6-sol", "litellm_params": { "reasoning_effort": "xhigh" } }] + "REASONING": [ + { + "model_name": "gpt-6-astra", + "litellm_params": { "reasoning_effort": "xhigh" } + } + ] }, "classifier_type": "heuristic", "escalation_keywords": ["LITELLM ESCALATE"], diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts index 5d1c7f73c71..8a5f83adbdf 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts @@ -149,13 +149,12 @@ describe("autorouter_presets", () => { ); }); - // Opus serves both tiers, so the effort is all that separates them and losing it fails silently. - it("pins the anthropic preset's reasoning tier to Opus at high thinking", () => { + it("pins the anthropic preset's reasoning tier to Fable 5.1 at high thinking", () => { const config = getPresetByKey("anthropic_family")!.complexity_router_config; expect(config.tiers.COMPLEX).toEqual(["claude-opus-5"]); - expect(config.tiers.REASONING).toEqual(["claude-opus-5"]); + expect(config.tiers.REASONING).toEqual(["claude-fable-5-1"]); expect(config.tier_model_configs).toEqual({ - REASONING: [{ model_name: "claude-opus-5", litellm_params: { reasoning_effort: "high" } }], + REASONING: [{ model_name: "claude-fable-5-1", litellm_params: { reasoning_effort: "high" } }], }); }); @@ -214,7 +213,7 @@ describe("autorouter_presets", () => { const preset = getPresetByKey("anthropic_family")!; const prefill = buildPresetPrefill(preset.complexity_router_config, groupsOnly(getRequiredModelsInPreset(preset))); expect(prefill.complexityRouterConfig.tier_model_params).toEqual({ - REASONING: { "claude-opus-5": { reasoning_effort: "high" } }, + REASONING: { "claude-fable-5-1": { reasoning_effort: "high" } }, }); }); @@ -227,21 +226,21 @@ describe("autorouter_presets", () => { }); }); - it("pins the OpenAI preset to the Luna, Terra, and Sol progression", () => { + it("pins the OpenAI preset to the Luna, Terra, Sol, and Astra progression", () => { const preset = getPresetByKey("openai_family")!; const expectedTiers = { SIMPLE: ["gpt-5.6-luna"], MEDIUM: ["gpt-5.6-terra"], COMPLEX: ["gpt-5.6-sol"], - REASONING: ["gpt-5.6-sol"], + REASONING: ["gpt-6-astra"], }; expect(preset.complexity_router_config.tiers).toEqual(expectedTiers); expect(preset.complexity_router_config.tier_model_configs).toEqual({ - REASONING: [{ model_name: "gpt-5.6-sol", litellm_params: { reasoning_effort: "xhigh" } }], + REASONING: [{ model_name: "gpt-6-astra", litellm_params: { reasoning_effort: "xhigh" } }], }); const prefill = buildPresetPrefill(preset.complexity_router_config, groupsOnly(getRequiredModelsInPreset(preset))); expect(prefill.complexityRouterConfig.tier_model_params).toEqual({ - REASONING: { "gpt-5.6-sol": { reasoning_effort: "xhigh" } }, + REASONING: { "gpt-6-astra": { reasoning_effort: "xhigh" } }, }); }); From fb21852f7bc5ba2a1d79a89b5d46a1ec6fc1e56d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 18:35:25 -0700 Subject: [PATCH 068/105] test(mcp): resolve current manager in proxy fixtures --- .../proxy/_experimental/mcp_server/conftest.py | 8 ++++---- .../proxy/_experimental/mcp_server/test_mcp_proxy_mode.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/conftest.py b/tests/test_litellm/proxy/_experimental/mcp_server/conftest.py index 2ccba2b2055..9a66f130d24 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/conftest.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/conftest.py @@ -2,15 +2,15 @@ import os import pytest -from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( - global_mcp_server_manager, -) - @pytest.fixture(autouse=True) def _hermetic_mcp_server_registry(): """Restore the singleton ``global_mcp_server_manager``'s registry state around every test, so entries seeded by one test never leak into another on a shared shard.""" + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + saved_registry = dict(global_mcp_server_manager.registry) saved_config_servers = dict(global_mcp_server_manager.config_mcp_servers) saved_tool_mapping = dict(global_mcp_server_manager.tool_name_to_mcp_server_name_mapping) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py index e86fc27fed7..413785529d5 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py @@ -7,7 +7,6 @@ from mcp.types import CallToolResult, TextContent, Tool from litellm.proxy._experimental.mcp_server import server from litellm.proxy._experimental.mcp_server.faults.list_outcomes import AggregateToolListing -from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager from litellm.proxy._experimental.mcp_server.tool_search import ( MCP_PROXY_CALL_TOOL_NAME, MCP_PROXY_SCHEMA_TOOL_NAME, @@ -271,6 +270,8 @@ class TestMcpProxyAuthorizationScope: @pytest.fixture def rig(self) -> Iterator[AsyncMock]: + from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager + upstream = { "srv-alpha": [_upstream_tool("alpha", "add"), _upstream_tool("alpha", "multiply")], "srv-beta": [_upstream_tool("beta", "add")], From 1a9c6ce390aeaf6835351cbd2169eca5c972e4d3 Mon Sep 17 00:00:00 2001 From: tin-berri Date: Tue, 8 Sep 2026 19:01:01 -0700 Subject: [PATCH 069/105] fix(mcp): preserve proxy logging and authorization coverage (#40337) * test(mcp): exercise /mcp/proxy authorization against the real registry instead of patched manager methods * fix(mcp): preserve proxy logging and authorization coverage * test(mcp): respect the proxy FastAPI import boundary --- .../proxy/_experimental/mcp_server/server.py | 25 +- tests/mcp_tests/mcp_server.py | 15 +- .../test_configs/test_config_mcp_e2e.yaml | 3 + tests/mcp_tests/test_proxy_mcp_e2e.py | 388 +++++++++++++++--- .../mcp_server/test_mcp_proxy_mode.py | 353 ++-------------- 5 files changed, 384 insertions(+), 400 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 44c00df996c..30793ef246d 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -767,12 +767,12 @@ if MCP_AVAILABLE: _stateful_auth_context_cleanup_task.cancel() with contextlib.suppress(asyncio.CancelledError): await _stateful_auth_context_cleanup_task - if _session_manager_cm: - await _session_manager_cm.__aexit__(None, None, None) - if _session_manager_stateful_cm: - await _session_manager_stateful_cm.__aexit__(None, None, None) if _sse_session_manager_cm: await _sse_session_manager_cm.__aexit__(None, None, None) + if _session_manager_stateful_cm: + await _session_manager_stateful_cm.__aexit__(None, None, None) + if _session_manager_cm: + await _session_manager_cm.__aexit__(None, None, None) except Exception as e: verbose_logger.exception("Error during session manager shutdown: %s", e) @@ -1005,6 +1005,7 @@ if MCP_AVAILABLE: if _mcp_proxy_mode.get() and name in MCP_PROXY_TOOL_NAMES: assert user_api_key_auth is not None + proxy_call_start: Final = datetime.now() # noqa: DTZ005 # logging pipeline uses naive datetimes proxy_logging_obj: Final = ( await _build_virtual_call_logging_obj( name=name, @@ -1016,7 +1017,7 @@ if MCP_AVAILABLE: if name == MCP_PROXY_CALL_TOOL_NAME else None ) - return await handle_mcp_proxy_tool( + proxy_result: Final = await handle_mcp_proxy_tool( name=name, arguments=arguments or {}, # mutable-ok: proxy handler payload user_api_key_dict=user_api_key_auth, @@ -1028,6 +1029,16 @@ if MCP_AVAILABLE: raw_headers=raw_headers, litellm_logging_obj=proxy_logging_obj, ) + if proxy_logging_obj is not None: + return await _fire_mcp_tool_call_logging( + logging_obj=proxy_logging_obj, + result=proxy_result, + start_time=proxy_call_start, + end_time=datetime.now(), # noqa: DTZ005 # matches the logging pipeline start time + user_api_key_auth=user_api_key_auth, + request_data=types.MappingProxyType({"name": name, "arguments": arguments}), + ) + return proxy_result if name not in VIRTUAL_TOOL_NAMES: return None @@ -3493,7 +3504,9 @@ if MCP_AVAILABLE: server_name: str | None, session_id: str | None = None, ) -> StandardLoggingMCPToolCall: - mcp_server: Final = global_mcp_server_manager._get_mcp_server_from_tool_name(name) + mcp_server: Final = global_mcp_server_manager._get_mcp_server_from_tool_name( + add_server_prefix_to_name(name, server_name) if server_name else name + ) namespaced_tool_name: Final = f"{server_name}/{name}" if server_name else name if mcp_server: mcp_info: Final = mcp_server.mcp_info or {} diff --git a/tests/mcp_tests/mcp_server.py b/tests/mcp_tests/mcp_server.py index bc6accbb721..eba7cae1bca 100644 --- a/tests/mcp_tests/mcp_server.py +++ b/tests/mcp_tests/mcp_server.py @@ -1,10 +1,12 @@ # math_server.py import argparse import os +from typing import Final -from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp import Context, FastMCP mcp = FastMCP("Math") +ADD_OFFSET: Final = int(os.getenv("MCP_ADD_OFFSET", "0")) def _parse_args() -> argparse.Namespace: @@ -31,7 +33,7 @@ def _parse_args() -> argparse.Namespace: @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" - return a + b + return a + b + ADD_OFFSET @mcp.tool() @@ -40,6 +42,15 @@ def multiply(a: int, b: int) -> int: return a * b +@mcp.tool() +def request_headers(ctx: Context) -> dict[str, str]: + request: Final = ctx.request_context.request + return { + "authorization": request.headers.get("authorization", "") if request is not None else "", + "x-request-tag": request.headers.get("x-request-tag", "") if request is not None else "", + } + + def main() -> None: args = _parse_args() transport = (args.transport or "stdio").lower() diff --git a/tests/mcp_tests/test_configs/test_config_mcp_e2e.yaml b/tests/mcp_tests/test_configs/test_config_mcp_e2e.yaml index ad68a03781d..19fad3d1393 100644 --- a/tests/mcp_tests/test_configs/test_config_mcp_e2e.yaml +++ b/tests/mcp_tests/test_configs/test_config_mcp_e2e.yaml @@ -23,3 +23,6 @@ mcp_servers: transport: http url: http://127.0.0.1:0/mcp allow_all_keys: true + math_restricted: + transport: http + url: http://127.0.0.1:0/mcp diff --git a/tests/mcp_tests/test_proxy_mcp_e2e.py b/tests/mcp_tests/test_proxy_mcp_e2e.py index a97eed82e18..5e5b9db2dc6 100644 --- a/tests/mcp_tests/test_proxy_mcp_e2e.py +++ b/tests/mcp_tests/test_proxy_mcp_e2e.py @@ -1,27 +1,39 @@ import asyncio import json import os +import queue import socket import subprocess import sys +import tempfile import threading import time import typing +from contextlib import asynccontextmanager, contextmanager +from dataclasses import dataclass +from datetime import datetime from pathlib import Path +import httpx import pytest import uvicorn import yaml from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client +from mcp.types import CallToolResult +from starlette.requests import Request +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy._experimental.mcp_server.tool_search import handle_mcp_proxy_tool +from litellm.proxy._types import LiteLLM_ObjectPermissionTable, ProxyException, UserAPIKeyAuth from litellm.proxy.proxy_server import ( app as proxy_app, +) +from litellm.proxy.proxy_server import ( cleanup_router_config_variables, initialize, ) - CONFIG_TEMPLATE_PATH = Path("tests/mcp_tests/test_configs/test_config_mcp_e2e.yaml") MCP_SERVER_SCRIPT = Path("tests/mcp_tests/mcp_server.py") PROJECT_ROOT = Path(__file__).resolve().parents[2] @@ -46,28 +58,49 @@ def _clear_proxy_database_env() -> typing.Iterator[None]: mp.undo() -def _initialize_proxy(config_path: str) -> None: +async def _initialize_proxy(config_path: str) -> None: + from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager + cleanup_router_config_variables() - asyncio.run(initialize(config=config_path, debug=True)) + await initialize(config=config_path, debug=True) + for server_id, upstream in tuple(global_mcp_server_manager.registry.items()): + if upstream.server_name != "math_restricted": + continue + global_mcp_server_manager.registry[server_id] = upstream.model_copy( + update={"tool_name_to_display_name": {"add": "Add Numbers"}} + ) + + +@dataclass(frozen=True) +class ProxyRig: + url: str + config_path: str + loop: asyncio.AbstractEventLoop def _start_proxy_server( config_path: str, -) -> tuple[str, uvicorn.Server, threading.Thread, socket.socket]: - _initialize_proxy(config_path) - +) -> tuple[ProxyRig, uvicorn.Server, threading.Thread, socket.socket]: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 0)) host, port = sock.getsockname() - config = uvicorn.Config(proxy_app, host=host, port=port, log_level="warning") + config = uvicorn.Config(proxy_app, host=host, port=port, log_level="warning", lifespan="off") server = uvicorn.Server(config) + loop = asyncio.new_event_loop() + + async def _serve() -> None: + from litellm.proxy._experimental.mcp_server import server as mcp_server + + await _initialize_proxy(config_path) + async with proxy_app.router.lifespan_context(proxy_app), mcp_server.lifespan(proxy_app): + await server.serve(sockets=[sock]) + def _run() -> None: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(server.serve(sockets=[sock])) + with asyncio.Runner(loop_factory=lambda: loop) as runner: + runner.run(_serve()) thread = threading.Thread(target=_run, daemon=True) thread.start() @@ -80,75 +113,93 @@ def _start_proxy_server( raise TimeoutError("Proxy server did not start in time") time.sleep(0.05) - return f"http://{host}:{port}", server, thread, sock + return ProxyRig(f"http://{host}:{port}", config_path, loop), server, thread, sock -@pytest.fixture(scope="session") -def math_streamable_http_server() -> str: +@contextmanager +def _math_http_server(offset: int) -> typing.Iterator[str]: host = "127.0.0.1" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind((host, 0)) _, port = sock.getsockname() - cmd = [ - sys.executable, - str(MCP_SERVER_SCRIPT), - "--transport", - "http", - "--host", - host, - "--port", - str(port), - ] - - env = os.environ.copy() - server_process = subprocess.Popen( - cmd, - cwd=str(PROJECT_ROOT), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - start_time = time.time() - while True: - if server_process.poll() is not None: - stdout, stderr = server_process.communicate() - raise RuntimeError( - f"Streamable HTTP MCP server exited early.\nSTDOUT: {stdout.decode()}\nSTDERR: {stderr.decode()}" - ) + with tempfile.TemporaryFile() as server_log: + process = subprocess.Popen( + [sys.executable, str(MCP_SERVER_SCRIPT), "--transport", "http", "--host", host, "--port", str(port)], + cwd=str(PROJECT_ROOT), + stdout=server_log, + stderr=subprocess.STDOUT, + env={**os.environ, "MCP_ADD_OFFSET": str(offset)}, + ) try: - with socket.create_connection((host, port), timeout=0.1): - break - except OSError: - if time.time() - start_time > PROXY_START_TIMEOUT: - server_process.terminate() - raise TimeoutError("Streamable HTTP MCP server did not start in time") - time.sleep(0.05) - - yield f"http://{host}:{port}" - - server_process.terminate() - try: - server_process.wait(timeout=5) - except subprocess.TimeoutExpired: - server_process.kill() + start_time = time.monotonic() + while True: + if process.poll() is not None: + server_log.seek(0) + raise RuntimeError(f"MCP upstream exited early: {server_log.read().decode()}") + try: + with socket.create_connection((host, port), timeout=0.1): + break + except OSError: + if time.monotonic() - start_time > PROXY_START_TIMEOUT: + raise TimeoutError("Streamable HTTP MCP server did not start in time") + time.sleep(0.05) + yield f"http://{host}:{port}" + finally: + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=5) @pytest.fixture(scope="session") -def proxy_server_url(tmp_path_factory: pytest.TempPathFactory, math_streamable_http_server: str): +def math_streamable_http_server() -> typing.Iterator[str]: + with _math_http_server(100) as url: + yield url + + +@pytest.fixture(scope="session") +def math_restricted_server() -> typing.Iterator[str]: + with _math_http_server(200) as url: + yield url + + +@pytest.fixture(scope="session") +def _proxy_server( + tmp_path_factory: pytest.TempPathFactory, + math_streamable_http_server: str, + math_restricted_server: str, +): config_dir = tmp_path_factory.mktemp("mcp_e2e") config_path = config_dir / "config.yaml" config = yaml.safe_load(CONFIG_TEMPLATE_PATH.read_text()) + config["mcp_servers"]["math_stdio"]["command"] = sys.executable config["mcp_servers"]["math_streamable_http"]["url"] = f"{math_streamable_http_server}/mcp" + config["mcp_servers"]["math_restricted"]["url"] = f"{math_restricted_server}/mcp" + config["general_settings"]["custom_auth"] = f"{__name__}.authorize_proxy_key" + config["litellm_settings"]["callbacks"] = [f"{__name__}.proxy_call_recorder"] + config["mcp_servers"]["math_restricted"]["mcp_info"] = {"mcp_server_cost_info": {"default_cost_per_query": 0.25}} config_path.write_text(yaml.safe_dump(config)) - server_url, server, thread, sock = _start_proxy_server(str(config_path)) + rig, server, thread, sock = _start_proxy_server(str(config_path)) - yield server_url + try: + yield rig + finally: + server.should_exit = True + thread.join(timeout=10) + sock.close() + assert not thread.is_alive(), "Proxy did not shut down" - server.should_exit = True - thread.join(timeout=10) - sock.close() + +@pytest.fixture +def proxy_server_url(_proxy_server: ProxyRig, setup_and_teardown: None) -> str: + asyncio.run_coroutine_threadsafe(_initialize_proxy(_proxy_server.config_path), _proxy_server.loop).result( + timeout=30 + ) + return _proxy_server.url class TestProxyMcpSimpleConnections: @@ -192,7 +243,7 @@ class TestProxyMcpSimpleConnections: assert result.content first_content = result.content[0] text = getattr(first_content, "text", None) - assert text == "11" + assert text == "111" @pytest.mark.asyncio async def test_proxy_mcp_lists_all_servers_without_header(self, proxy_server_url: str) -> None: @@ -222,7 +273,7 @@ class TestProxyMcpSimpleConnections: stdio_result = await _call_and_get_text("math_stdio-add", a=2, b=3) streamable_result = await _call_and_get_text("math_streamable_http-add", a=4, b=5) assert stdio_result == "5" - assert streamable_result == "9" + assert streamable_result == "109" class TestProxyMcpStatelessBehavior: @@ -349,7 +400,7 @@ class TestProxyMcpSchemaDiscoveryMode: }, ) assert stdio.isError is False and stdio.content[0].text == "7" - assert http.isError is False and http.content[0].text == "11" + assert http.isError is False and http.content[0].text == "111" @pytest.mark.asyncio async def test_server_scope_header_narrows_discovery(self, proxy_server_url: str) -> None: @@ -384,6 +435,12 @@ class TestProxyMcpSchemaDiscoveryMode: stale = await session.call_tool("get_tool_schema", arguments={"tool_id": "0" * 32}) assert stale.isError is True and "unauthorized tool_id" in stale.content[0].text + for not_an_object in ("wrong", False): + refused_args = await session.call_tool( + "call_tool", arguments={"tool_id": tool_id, "arguments": not_an_object} + ) + assert refused_args.isError is True and "object" in refused_args.content[0].text + direct = await session.call_tool("math_stdio-add", arguments={"a": 1, "b": 2}) assert direct.isError is True and "unavailable on /mcp/proxy" in direct.content[0].text @@ -391,3 +448,208 @@ class TestProxyMcpSchemaDiscoveryMode: with pytest.raises(McpError) as refused: await operation() assert refused.value.error.code == METHOD_NOT_FOUND + + +async def authorize_proxy_key(request: Request, api_key: str) -> UserAPIKeyAuth: + permissions = { + "sk-1234": LiteLLM_ObjectPermissionTable(object_permission_id="open", mcp_servers=["math_stdio"]), + "sk-restricted": LiteLLM_ObjectPermissionTable( + object_permission_id="restricted", mcp_servers=["math_restricted"] + ), + "sk-none": LiteLLM_ObjectPermissionTable(object_permission_id="none", mcp_servers=["no-mcp-servers"]), + "sk-add-only": LiteLLM_ObjectPermissionTable( + object_permission_id="add-only", mcp_servers=["math_stdio"], mcp_tool_permissions={"math_stdio": ["add"]} + ), + } + permission = permissions.get(api_key) + if permission is None: + raise ProxyException(message="Unknown test key", type="authentication_error", param=None, code=401) + return UserAPIKeyAuth(api_key=api_key, user_id=api_key, object_permission=permission) + + +class ProxyCallRecorder(CustomLogger): + def __init__(self) -> None: + super().__init__() + self.events: queue.Queue[str] = queue.Queue() + + async def async_log_success_event( + self, kwargs: dict[str, object], response_obj: object, start_time: datetime, end_time: datetime + ) -> None: + payload = kwargs.get("standard_logging_object") + if isinstance(payload, dict) and payload.get("call_type") == "call_mcp_tool": + self.events.put(json.dumps(payload, default=str)) + + +proxy_call_recorder = ProxyCallRecorder() + + +@asynccontextmanager +async def _scoped_session(url: str, key: str = "sk-1234", **headers: str) -> typing.AsyncIterator[ClientSession]: + async with asyncio.timeout(30): + async with _proxy_session(url, Authorization=f"Bearer {key}", **headers) as (read, write, _sid): + async with ClientSession(read, write) as session: + await session.initialize() + yield session + + +async def _search(session: ClientSession, query: str) -> dict[str, str]: + result = await session.call_tool("search_tools", arguments={"query": query}) + assert result.isError is False, result + return {hit["name"]: hit["tool_id"] for hit in _payload(result)} + + +async def _call(session: ClientSession, tool_id: str, a: int = 3, b: int = 4) -> CallToolResult: + return await session.call_tool("call_tool", arguments={"tool_id": tool_id, "arguments": {"a": a, "b": b}}) + + +def _assert_unauthorized(result: CallToolResult) -> None: + assert result.isError is True + assert result.content[0].text == "Unknown or unauthorized tool_id" + + +class TestProxyMcpAuthorizationScope: + @pytest.mark.asyncio + async def test_server_grant_bounds_search_and_blocks_foreign_ids(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url, "sk-restricted") as granted: + restricted_id = (await _search(granted, "add"))["math_restricted-add"] + assert (await _call(granted, restricted_id)).content[0].text == "207" + async with _scoped_session(proxy_server_url) as ungranted: + assert set(await _search(ungranted, "add")) == {"math_stdio-add", "math_streamable_http-add"} + _assert_unauthorized(await ungranted.call_tool("get_tool_schema", {"tool_id": restricted_id})) + _assert_unauthorized(await _call(ungranted, restricted_id)) + + @pytest.mark.asyncio + async def test_no_mcp_servers_sentinel_hides_every_tool(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url) as granted: + tool_id = (await _search(granted, "add"))["math_stdio-add"] + async with _scoped_session(proxy_server_url, "sk-none") as session: + assert await _search(session, "add") == {} + _assert_unauthorized(await session.call_tool("get_tool_schema", {"tool_id": tool_id})) + _assert_unauthorized(await _call(session, tool_id)) + + @pytest.mark.asyncio + async def test_tool_grant_hides_ungranted_tools_and_blocks_their_ids(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url) as granted: + multiply_id = (await _search(granted, "multiply"))["math_stdio-multiply"] + async with _scoped_session(proxy_server_url, "sk-add-only", **{"x-mcp-servers": "math_stdio"}) as session: + ids = await _search(session, "add multiply request_headers") + assert set(ids) == {"math_stdio-add"} + assert (await _call(session, ids["math_stdio-add"])).content[0].text == "7" + _assert_unauthorized(await session.call_tool("get_tool_schema", {"tool_id": multiply_id})) + _assert_unauthorized(await _call(session, multiply_id)) + + @pytest.mark.asyncio + async def test_same_named_tools_keep_distinct_ids_and_reach_their_own_upstream(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url, "sk-restricted") as session: + ids = await _search(session, "add") + assert set(ids) == {"math_stdio-add", "math_streamable_http-add", "math_restricted-add"} + assert len(set(ids.values())) == 3 + assert all(len(tool_id) == 32 for tool_id in ids.values()) + for name, expected in ( + ("math_stdio-add", "7"), + ("math_streamable_http-add", "107"), + ("math_restricted-add", "207"), + ): + schema = _payload(await session.call_tool("get_tool_schema", {"tool_id": ids[name]})) + assert schema["name"] == name + assert schema["tool_id"] == ids[name] + result = await _call(session, ids[name]) + assert result.isError is False + assert result.content[0].text == expected + + @pytest.mark.asyncio + async def test_server_scope_header_narrows_grants_and_blocks_out_of_scope_ids(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url, "sk-restricted") as unscoped: + other_id = (await _search(unscoped, "add"))["math_stdio-add"] + async with _scoped_session( + proxy_server_url, "sk-restricted", **{"x-mcp-servers": "math_restricted"} + ) as session: + ids = await _search(session, "add") + assert set(ids) == {"math_restricted-add"} + _assert_unauthorized(await session.call_tool("get_tool_schema", {"tool_id": other_id})) + _assert_unauthorized(await _call(session, other_id)) + assert (await _call(session, ids["math_restricted-add"])).content[0].text == "207" + + @pytest.mark.asyncio + @pytest.mark.parametrize("key", [None, "sk-invalid"]) + async def test_missing_or_invalid_key_cannot_initialize(self, proxy_server_url: str, key: str | None) -> None: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{proxy_server_url}/mcp/proxy", + headers={ + "Accept": "application/json, text/event-stream", + **({"Authorization": f"Bearer {key}"} if key else {}), + }, + json={ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "auth-test", "version": "1"}, + }, + }, + ) + assert response.status_code == 401, response.text + + @pytest.mark.asyncio + async def test_server_headers_are_forwarded_only_to_the_named_upstream(self, proxy_server_url: str) -> None: + for tag in ("first-request", "second-request"): + async with _scoped_session( + proxy_server_url, + "sk-restricted", + **{ + "x-mcp-math_restricted-authorization": f"Bearer {tag}", + "x-mcp-math_restricted-x-request-tag": tag, + }, + ) as session: + ids = await _search(session, "request_headers") + for name, expected in ( + ("math_restricted", {"authorization": f"Bearer {tag}", "x-request-tag": tag}), + ("math_streamable_http", {"authorization": "", "x-request-tag": ""}), + ): + result = await session.call_tool( + "call_tool", {"tool_id": ids[f"{name}-request_headers"], "arguments": {}} + ) + assert result.isError is False + assert _payload(result) == expected + + @pytest.mark.asyncio + async def test_proxy_call_emits_spend_log(self, proxy_server_url: str) -> None: + async with _scoped_session(proxy_server_url, "sk-restricted") as session: + tool_id = (await _search(session, "add"))["math_restricted-add"] + result = await _call(session, tool_id, 123, 456) + assert result.isError is False and result.content[0].text == "779" + async with asyncio.timeout(10): + while True: + payload = json.loads(await asyncio.to_thread(proxy_call_recorder.events.get, True, 5)) + if payload.get("metadata", {}).get("mcp_tool_call_metadata", {}).get("arguments") == { + "a": 123, + "b": 456, + }: + break + assert payload["call_type"] == "call_mcp_tool" + assert payload["response_cost"] == 0.25 + assert payload["status"] == "success" + assert payload["metadata"]["mcp_tool_call_metadata"]["mcp_server_name"] == "math_restricted" + assert payload["metadata"]["mcp_tool_call_metadata"]["name"] == "add" + assert payload["metadata"]["mcp_tool_call_metadata"]["namespaced_tool_name"] == "math_restricted/add" + + @pytest.mark.parametrize("arguments", ["wrong", False, None, [], 0]) + def test_handler_rejects_non_object_arguments( + self, proxy_server_url: str, _proxy_server: ProxyRig, arguments: object + ) -> None: + async def check() -> None: + auth = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="validation", mcp_servers=["math_stdio"] + ) + ) + hits = _payload(await handle_mcp_proxy_tool("search_tools", {"query": "add"}, auth)) + tool_id = next(hit["tool_id"] for hit in hits if hit["name"] == "math_stdio-add") + result = await handle_mcp_proxy_tool("call_tool", {"tool_id": tool_id, "arguments": arguments}, auth) + assert result.isError is True + assert result.content[0].text == "arguments must be an object" + + asyncio.run_coroutine_threadsafe(check(), _proxy_server.loop).result(timeout=30) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py index 413785529d5..29711f80deb 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py @@ -1,355 +1,50 @@ -import json -from collections.abc import Iterator -from unittest.mock import AsyncMock, patch - import pytest -from mcp.types import CallToolResult, TextContent, Tool +from mcp.shared.exceptions import McpError +from pydantic import AnyUrl from litellm.proxy._experimental.mcp_server import server -from litellm.proxy._experimental.mcp_server.faults.list_outcomes import AggregateToolListing -from litellm.proxy._experimental.mcp_server.tool_search import ( - MCP_PROXY_CALL_TOOL_NAME, - MCP_PROXY_SCHEMA_TOOL_NAME, - MCP_PROXY_SEARCH_TOOL_NAME, - handle_mcp_proxy_tool, - mcp_proxy_tool_id, - with_mcp_proxy_identity, -) -from litellm.proxy._types import LiteLLM_ObjectPermissionTable, UserAPIKeyAuth -from litellm.types.mcp import MCPTransport -from litellm.types.mcp_server.mcp_server_manager import MCPServer +from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode +from litellm.proxy._types import UserAPIKeyAuth -TOOL = Tool.model_validate( - { - "name": "math_stdio-add", - "description": "Add two numbers", - "inputSchema": { - "type": "object", - "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}, - "required": ["a", "b"], - }, - "outputSchema": {"type": "object"}, - "_meta": {"litellm.ai/proxy_tool_identity": {"server_id": "server-1", "tool_name": "math_stdio-add"}}, - } -) AUTH = UserAPIKeyAuth(api_key="key") -def _text(result: CallToolResult) -> object: - return json.loads(result.content[0].text) - - -@pytest.mark.asyncio -async def test_proxy_search_returns_opaque_id_and_schema() -> None: - with ( - patch( # test-quality-ok: isolate authorized catalog owner - "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", - new_callable=AsyncMock, - return_value=AggregateToolListing(tools=[TOOL], outcomes={}), - ), - ): - result = await handle_mcp_proxy_tool(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, AUTH) - - item = _text(result)[0] - assert item["tool_id"] == mcp_proxy_tool_id(TOOL) - assert item["name"] == TOOL.name - assert "inputSchema" not in item - assert "outputSchema" not in item - assert len(item["tool_id"]) == 32 - - -@pytest.mark.asyncio -async def test_proxy_schema_and_call_resolve_current_authorized_catalog() -> None: - executed = CallToolResult(content=[TextContent(type="text", text="3")], isError=False) - with ( - patch( # test-quality-ok: isolate authorized catalog owner - "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", - new_callable=AsyncMock, - return_value=AggregateToolListing(tools=[TOOL], outcomes={}), - ), - patch( # test-quality-ok: isolate execution delegate seam - "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_tool_call", - new_callable=AsyncMock, - return_value=executed, - ) as call, - ): - schema = await handle_mcp_proxy_tool( - MCP_PROXY_SCHEMA_TOOL_NAME, - {"tool_id": mcp_proxy_tool_id(TOOL)}, - AUTH, - ) - result = await handle_mcp_proxy_tool( - MCP_PROXY_CALL_TOOL_NAME, - {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": {"a": 1, "b": 2}}, - AUTH, - ) - - assert _text(schema)["inputSchema"] == TOOL.inputSchema - assert result is executed - assert call.await_args.kwargs["tool_name"] == TOOL.name - assert call.await_args.kwargs["arguments"] == {"a": 1, "b": 2} - assert call.await_args.kwargs["requested_server_id"] == "server-1" - - -@pytest.mark.asyncio -async def test_proxy_rejects_stale_id_and_invalid_arguments_before_dispatch() -> None: - with ( - patch( # test-quality-ok: isolate authorized catalog owner - "litellm.proxy._experimental.mcp_server.server._list_mcp_tools", - new_callable=AsyncMock, - return_value=AggregateToolListing(tools=[TOOL], outcomes={}), - ), - patch( # test-quality-ok: isolate execution delegate seam - "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_tool_call", new_callable=AsyncMock - ) as call, - ): - stale = await handle_mcp_proxy_tool(MCP_PROXY_SCHEMA_TOOL_NAME, {"tool_id": "stale"}, AUTH) - invalid = await handle_mcp_proxy_tool( - MCP_PROXY_CALL_TOOL_NAME, - {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": "wrong"}, - AUTH, - ) - falsy = await handle_mcp_proxy_tool( - MCP_PROXY_CALL_TOOL_NAME, - {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": False}, - AUTH, - ) - invalid_schema = await handle_mcp_proxy_tool( - MCP_PROXY_CALL_TOOL_NAME, - {"tool_id": mcp_proxy_tool_id(TOOL), "arguments": {"a": "wrong"}}, - AUTH, - ) - - assert stale.isError is True - assert invalid.isError is True - assert falsy.isError is True - assert invalid_schema.isError is True - call.assert_not_awaited() - - -@pytest.mark.asyncio -async def test_proxy_call_builds_logging_object() -> None: - from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode - - sentinel = object() - result = CallToolResult(content=[TextContent(type="text", text="ok")], isError=False) +@pytest.fixture +def proxy_mode(): token = _mcp_proxy_mode.set(True) try: - with ( - patch.object( # test-quality-ok: isolate logging pipeline seam - server, "_build_virtual_call_logging_obj", new_callable=AsyncMock, return_value=sentinel - ) as build, - patch( # test-quality-ok: isolate proxy dispatch seam - "litellm.proxy._experimental.mcp_server.tool_search.handle_mcp_proxy_tool", - new_callable=AsyncMock, - return_value=result, - ) as handle, - ): - actual = await server._dispatch_virtual_mcp_tool( - name=MCP_PROXY_CALL_TOOL_NAME, - arguments={"tool_id": "id", "arguments": {}}, - user_api_key_auth=AUTH, - client_ip=None, - ) + yield finally: _mcp_proxy_mode.reset(token) - assert actual is result - build.assert_awaited_once() - assert handle.await_args.kwargs["litellm_logging_obj"] is sentinel - @pytest.mark.asyncio +@pytest.mark.usefixtures("proxy_mode") async def test_proxy_call_rejects_non_proxy_tool_names() -> None: - from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode - - token = _mcp_proxy_mode.set(True) - try: - result = await server._dispatch_virtual_mcp_tool( - name="math_stdio-add", - arguments={"a": 1, "b": 2}, - user_api_key_auth=AUTH, - client_ip=None, - ) - finally: - _mcp_proxy_mode.reset(token) + result = await server._dispatch_virtual_mcp_tool( + name="math_stdio-add", arguments={"a": 1, "b": 2}, user_api_key_auth=AUTH, client_ip=None + ) assert result is not None assert result.isError is True - assert "unavailable" in result.content[0].text + assert "unavailable on /mcp/proxy" in result.content[0].text @pytest.mark.asyncio +@pytest.mark.usefixtures("proxy_mode") async def test_proxy_rejects_non_tool_protocol_operations() -> None: - from mcp.shared.exceptions import McpError - from pydantic import AnyUrl - - from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode - - token = _mcp_proxy_mode.set(True) - try: - with pytest.raises(McpError): - await server.list_prompts() - with pytest.raises(McpError): - await server.get_prompt("prompt", {}) - with pytest.raises(McpError): - await server.list_resources() - with pytest.raises(McpError): - await server.list_resource_templates() - with pytest.raises(McpError): - await server.read_resource(AnyUrl("https://example.com/resource")) - finally: - _mcp_proxy_mode.reset(token) - - -@pytest.mark.asyncio -async def test_proxy_list_mode_has_fixed_definitions_without_search_flag() -> None: - from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode - - token = _mcp_proxy_mode.set(True) - try: - with patch( # test-quality-ok: isolate authenticated MCP context seam - "litellm.proxy._experimental.mcp_server.server.get_or_extract_auth_context", - new_callable=AsyncMock, - return_value=(AUTH, None, None, None, None, None, None), - ): - tools = await server.handle_list_tools() - options = server.server.create_initialization_options() - finally: - _mcp_proxy_mode.reset(token) - - assert {tool.name for tool in tools} == { - MCP_PROXY_SEARCH_TOOL_NAME, - MCP_PROXY_SCHEMA_TOOL_NAME, - MCP_PROXY_CALL_TOOL_NAME, - } + options = server.server.create_initialization_options() assert options.capabilities.prompts is None assert options.capabilities.resources is None assert options.capabilities.tools is not None - -def _server(server_id: str, name: str, **overrides: object) -> MCPServer: - return MCPServer( - server_id=server_id, - name=name, - server_name=name, - url=f"http://{name}.test", - transport=MCPTransport.http, - **overrides, - ) - - -def _upstream_tool(prefix: str, name: str) -> Tool: - return Tool( - name=f"{prefix}-{name}", - description=f"{name} numbers", - inputSchema={"type": "object", "properties": {"a": {"type": "integer"}}, "required": ["a"]}, - ) - - -def _auth(**object_permission: object) -> UserAPIKeyAuth: - return UserAPIKeyAuth( - api_key="sk-scope", - object_permission=LiteLLM_ObjectPermissionTable(object_permission_id="scope", **object_permission), - ) - - -def _ids(result: CallToolResult) -> dict[str, str]: - return {item["name"]: item["tool_id"] for item in _text(result)} - - -class TestMcpProxyAuthorizationScope: - """The real catalog resolver runs (server grants, tool grants, scope header, sentinel); only the - upstream tools/list fetch and the final upstream dispatch are faked.""" - - ALPHA = _server("srv-alpha", "alpha", tool_name_to_display_name={"add": "Add Numbers"}) - BETA = _server("srv-beta", "beta") - ALPHA_ADD = with_mcp_proxy_identity(_upstream_tool("alpha", "add"), "srv-alpha") - ALPHA_MULTIPLY = with_mcp_proxy_identity(_upstream_tool("alpha", "multiply"), "srv-alpha") - BETA_ADD = with_mcp_proxy_identity(_upstream_tool("beta", "add"), "srv-beta") - - @pytest.fixture - def rig(self) -> Iterator[AsyncMock]: - from litellm.proxy._experimental.mcp_server.mcp_server_manager import global_mcp_server_manager - - upstream = { - "srv-alpha": [_upstream_tool("alpha", "add"), _upstream_tool("alpha", "multiply")], - "srv-beta": [_upstream_tool("beta", "add")], - } - - async def fetch(server: MCPServer, **_: object) -> list[Tool]: - return list(upstream[server.server_id]) - - dispatched = AsyncMock( - return_value=CallToolResult(content=[TextContent(type="text", text="ok")], isError=False) - ) - global_mcp_server_manager.registry.update({"srv-alpha": self.ALPHA, "srv-beta": self.BETA}) - with ( - patch.object( # test-quality-ok: the upstream MCP server is the only faked collaborator - global_mcp_server_manager, "_get_tools_from_server", new=AsyncMock(side_effect=fetch) - ), - patch.object(global_mcp_server_manager, "call_tool", new=dispatched), # test-quality-ok: dispatch seam - ): - yield dispatched - - async def _proxy( - self, name: str, arguments: dict[str, object], auth: UserAPIKeyAuth, **kwargs: object - ) -> CallToolResult: - return await handle_mcp_proxy_tool(name, arguments, auth, **kwargs) - - @pytest.mark.asyncio - async def test_search_and_schema_are_bounded_by_the_key_server_grant(self, rig: AsyncMock) -> None: - granted = _auth(mcp_servers=["srv-alpha"]) - - assert _ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, granted)) == { - "alpha-add": mcp_proxy_tool_id(self.ALPHA_ADD), - "alpha-multiply": mcp_proxy_tool_id(self.ALPHA_MULTIPLY), - } - denied_schema = await self._proxy( - MCP_PROXY_SCHEMA_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.BETA_ADD)}, granted - ) - denied_call = await self._proxy( - MCP_PROXY_CALL_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.BETA_ADD), "arguments": {"a": 1}}, granted - ) - assert denied_schema.isError is True and denied_call.isError is True - rig.assert_not_awaited() - - @pytest.mark.asyncio - async def test_no_mcp_servers_sentinel_hides_every_tool(self, rig: AsyncMock) -> None: - result = await self._proxy( - MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, _auth(mcp_servers=["no-mcp-servers"]) - ) - assert _text(result) == [] - rig.assert_not_awaited() - - @pytest.mark.asyncio - async def test_tool_grant_hides_ungranted_tools_and_blocks_their_ids(self, rig: AsyncMock) -> None: - scoped = _auth(mcp_servers=["srv-alpha"], mcp_tool_permissions={"srv-alpha": ["add"]}) - - assert set(_ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "numbers"}, scoped))) == {"alpha-add"} - blocked = await self._proxy( - MCP_PROXY_CALL_TOOL_NAME, {"tool_id": mcp_proxy_tool_id(self.ALPHA_MULTIPLY), "arguments": {"a": 1}}, scoped - ) - assert blocked.isError is True - rig.assert_not_awaited() - - @pytest.mark.asyncio - async def test_same_named_tools_keep_distinct_ids_and_dispatch_to_their_own_server(self, rig: AsyncMock) -> None: - both = _auth(mcp_servers=["srv-alpha", "srv-beta"]) - - ids = _ids(await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, both)) - assert set(ids) == {"alpha-add", "beta-add"}, "display-name overrides must not rename proxy identities" - assert ids["alpha-add"] != ids["beta-add"] - - result = await self._proxy(MCP_PROXY_CALL_TOOL_NAME, {"tool_id": ids["beta-add"], "arguments": {"a": 1}}, both) - assert result.isError is False - rig.assert_awaited_once() - assert rig.await_args.kwargs["server_name"] == "beta" - assert rig.await_args.kwargs["name"] == "add" - - @pytest.mark.asyncio - async def test_server_scope_header_narrows_search_within_the_grant(self, rig: AsyncMock) -> None: - both = _auth(mcp_servers=["srv-alpha", "srv-beta"]) - scoped = await self._proxy(MCP_PROXY_SEARCH_TOOL_NAME, {"query": "add"}, both, mcp_servers=["beta"]) - assert set(_ids(scoped)) == {"beta-add"} - rig.assert_not_awaited() + with pytest.raises(McpError): + await server.list_prompts() + with pytest.raises(McpError): + await server.get_prompt("prompt", {}) + with pytest.raises(McpError): + await server.list_resources() + with pytest.raises(McpError): + await server.list_resource_templates() + with pytest.raises(McpError): + await server.read_resource(AnyUrl("https://example.com/resource")) From 163847333242b7e75b1a15c27519beef2b1365ed Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 8 Sep 2026 19:02:08 -0700 Subject: [PATCH 070/105] fix(bedrock): keep deletion response IDs in request context --- litellm/llms/bedrock/files/transformation.py | 18 ++++++------- .../test_bedrock_files_transformation.py | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index 90b539ff37c..9875ac2b9c3 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -13,7 +13,7 @@ from urllib.parse import unquote import httpx from httpx import Headers, Response from openai.types.file_deleted import FileDeleted -from pydantic import BaseModel, ConfigDict, TypeAdapter +from pydantic import BaseModel, ConfigDict, Field, TypeAdapter from typing_extensions import ReadOnly from litellm._logging import verbose_logger @@ -61,7 +61,11 @@ from ..base_aws_llm import BaseAWSLLM from ..common_utils import BedrockError, merge_bedrock_aws_request_params, resolve_s3_encryption_key_id S3_SIGNED_REQUEST_HEADERS_PARAM: Final = "_s3_signed_request_headers" -S3_DELETE_FILE_ID_PARAM: Final = "_s3_delete_file_id" + + +class _S3DeleteContext(BaseModel): + file_id: str = Field(min_length=1) + # litellm_params key carrying the size of the body uploaded to S3, handed from # `transform_create_file_request` to `transform_create_file_response`. @@ -1187,11 +1191,9 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): optional_params: Mapping[str, object], litellm_params: MutableMapping[str, object], ) -> tuple[str, dict[str, str]]: - request: Final = self._transform_s3_file_request( + return self._transform_s3_file_request( file_id=file_id, method="DELETE", optional_params=optional_params, litellm_params=litellm_params ) - litellm_params[S3_DELETE_FILE_ID_PARAM] = file_id - return request def transform_delete_file_response( self, @@ -1205,10 +1207,8 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): message=raw_response.text or f"S3 file deletion returned HTTP {raw_response.status_code}", headers=raw_response.headers, ) - file_id: Final = litellm_params.get(S3_DELETE_FILE_ID_PARAM) - if not isinstance(file_id, str) or not file_id: - raise ValueError("Missing file id for Bedrock file deletion response") - return FileDeleted(id=file_id, deleted=True, object="file") + context: Final = _S3DeleteContext.model_validate(logging_obj.model_call_details.get("additional_args")) + return FileDeleted(id=context.file_id, deleted=True, object="file") def transform_list_files_request( self, diff --git a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py index 2c02a58663e..3b01a4f2054 100644 --- a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py +++ b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py @@ -1861,6 +1861,33 @@ class TestBedrockFileDeletion: S3_URI: Final = "s3://my-bucket/litellm-bedrock-files-model-abc.jsonl" URL: Final = "https://s3.us-west-2.amazonaws.com/my-bucket/litellm-bedrock-files-model-abc.jsonl" + def test_interleaved_deletions_keep_their_own_file_ids(self, monkeypatch: pytest.MonkeyPatch) -> None: + import httpx + + from litellm.llms.bedrock.files.transformation import BedrockFilesConfig + + monkeypatch.setenv("AWS_S3_BUCKET_NAME", "my-bucket") + config: Final = BedrockFilesConfig() + params: Final = { + "aws_access_key_id": "AKIAEXAMPLE", + "aws_secret_access_key": "test-secret", + "aws_region_name": "us-west-2", + } + file_ids: Final = (self.S3_URI, "s3://my-bucket/litellm-bedrock-files-model-second.jsonl") + for file_id in file_ids: + config.transform_delete_file_request(file_id=file_id, optional_params={}, litellm_params=params) + + deleted: Final = tuple( + config.transform_delete_file_response( + raw_response=httpx.Response(204), + logging_obj=MagicMock(model_call_details={"additional_args": {"file_id": file_id}}), + litellm_params=params, + ).id + for file_id in file_ids + ) + + assert deleted == file_ids + def test_delete_file_sends_signed_delete_and_returns_matching_id(self, monkeypatch: pytest.MonkeyPatch) -> None: import httpx import respx From 0086b62b4575fb5bb69653c2ad3d196822bd3f13 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 02:05:08 +0000 Subject: [PATCH 071/105] fix(cost-map): keep first fetch blocking, run retries in background Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/__init__.py | 4 +- .../litellm_core_utils/get_model_cost_map.py | 132 +++++++++- litellm/proxy/proxy_server.py | 23 +- .../test_get_model_cost_map.py | 235 +++++++++++++----- 4 files changed, 296 insertions(+), 98 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 42c0ea881fd..4e3754399da 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -541,7 +541,7 @@ _key_management_system: Optional["KeyManagementSystem"] = None #### PII MASKING #### output_parse_pii: bool = False ############################################# -from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map +from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map, mark_litellm_import_complete model_cost = get_model_cost_map(url=model_cost_map_url) cost_discount_config: Dict[str, float] = {} # Provider-specific cost discounts {"vertex_ai": 0.05} = 5% discount @@ -2397,3 +2397,5 @@ def __getattr__(name: str) -> Any: # ALL_LITELLM_RESPONSE_TYPES is lazy-loaded via __getattr__ to avoid loading utils at import time + +mark_litellm_import_complete() diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 9cba5db8ab7..4118cd3420e 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -12,6 +12,7 @@ import asyncio import json import os import random +import threading import time from collections.abc import Awaitable, Callable from dataclasses import dataclass @@ -159,6 +160,15 @@ class GetModelCostMap: RETRYABLE_FETCH_STATUS_CODES: Final = frozenset({429, 500, 502, 503, 504}) MODEL_COST_MAP_FETCH_MAX_ATTEMPTS: Final = 3 MODEL_COST_MAP_FETCH_MAX_WAIT_SECONDS: Final = 30.0 +_litellm_import_complete = threading.Event() + + +def mark_litellm_import_complete() -> None: + _litellm_import_complete.set() + + +def _start_daemon_thread(fn: Callable[[], None]) -> None: + threading.Thread(target=fn, name="litellm-model-cost-map-retry", daemon=True).start() @dataclass(frozen=True, slots=True) @@ -297,9 +307,15 @@ def _fetch_remote_model_cost_map_with_retry_sync( sleep: Callable[[float], None], rng: random.Random, client: _SyncGetClient, + starting_attempt: int = 1, + initial_outcome: _FetchAttemptRetryable | None = None, ) -> ModelCostMapReloadResult: - for attempt in range(1, max_attempts + 1): - outcome = _attempt_fetch_sync(client=client, url=url, timeout=timeout) + for attempt in range(starting_attempt, max_attempts + 1): + outcome = ( + initial_outcome + if initial_outcome is not None and attempt == starting_attempt + else _attempt_fetch_sync(client=client, url=url, timeout=timeout) + ) if not isinstance(outcome, _FetchAttemptRetryable): return outcome wait_seconds = _next_retry_wait(outcome=outcome, attempt=attempt, max_attempts=max_attempts, rng=rng) @@ -464,6 +480,70 @@ def _finalize_model_cost_map(model_cost: dict) -> dict: return _expand_model_aliases(model_cost) +def adopt_model_cost_map( + new_model_cost_map: dict, # mutable-ok: public API preserves the mutable cost-map contract +) -> int: + import litellm + from litellm import utils + + litellm.model_cost = new_model_cost_map + utils._invalidate_model_cost_lowercase_map() # pyright: ignore[reportPrivateUsage] # required cache invalidation + litellm.add_known_models(model_cost_map=new_model_cost_map) + fetched_model_count: Final = len(new_model_cost_map) if new_model_cost_map else 0 + utils.reapply_runtime_model_cost_registrations() + return fetched_model_count + + +def _continue_remote_fetch_in_background( + url: str, + timeout: int, + max_attempts: int, + sleep: Callable[[float], None], + rng: random.Random, + client: _SyncGetClient, + first_outcome: _FetchAttemptRetryable, + apply: Callable[[dict], object], # mutable-ok: injected callback receives the mutable cost-map dict +) -> None: + try: + result: Final = _fetch_remote_model_cost_map_with_retry_sync( + url=url, + timeout=timeout, + max_attempts=max_attempts, + sleep=sleep, + rng=rng, + client=client, + initial_outcome=first_outcome, + ) + if isinstance(result, ModelCostMapReloadUnavailable): + verbose_logger.warning( + "LiteLLM: Failed to fetch remote model cost map from %s after %d attempts; keeping local backup", + url, + max_attempts, + ) + return + backup_model_count: Final = GetModelCostMap._get_backup_model_count() # pyright: ignore[reportPrivateUsage] # integrity cache + if not GetModelCostMap.validate_model_cost_map( + fetched_map=result.model_cost_map, + backup_model_count=backup_model_count, + ): + verbose_logger.warning( + "LiteLLM: Fetched model cost map failed integrity check. Keeping local backup. url=%s", + url, + ) + _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" + return + finalized_map: Final = _finalize_model_cost_map(result.model_cost_map) + _litellm_import_complete.wait() + apply(finalized_map) + _cost_map_source_info.source = "remote" + _cost_map_source_info.url = url + _cost_map_source_info.is_env_forced = False + _cost_map_source_info.fallback_reason = None + _cost_map_source_info.loaded_at = datetime.now(timezone.utc) + except Exception as e: + verbose_logger.warning("LiteLLM: Background model cost map retry failed: %s", e) + + def get_model_cost_map( url: str, timeout: int = 5, @@ -471,14 +551,19 @@ def get_model_cost_map( sleep: Callable[[float], None] = time.sleep, rng: random.Random | None = None, client: "_SyncGetClient | None" = None, + start_background: Callable[[Callable[[], None]], None] = _start_daemon_thread, + apply: Callable[ # mutable-ok: injected callback receives the mutable cost-map dict + [dict], + object, + ] = adopt_model_cost_map, ) -> dict: """ Public entry point — returns the model cost map dict. 1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set, uses the local backup only. - 2. Otherwise fetches from ``url``, retrying transient HTTP errors - (429/5xx/transport) with Retry-After-aware backoff, validates - integrity, and falls back to the local backup on any failure. + 2. Otherwise fetches from ``url``, validates the first response, and falls + back to the local backup while retrying transient HTTP errors in the + background. Only the backup model count is cached (a single int) for validation. The full backup dict is only parsed when it must be *returned* as a @@ -497,14 +582,35 @@ def get_model_cost_map( _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False - result: Final = _fetch_remote_model_cost_map_with_retry_sync( - url=url, - timeout=timeout, - max_attempts=max_attempts, - sleep=sleep, - rng=rng if rng is not None else random.Random(), - client=client if client is not None else httpx, - ) + fetch_client: Final = client if client is not None else httpx + fetch_rng: Final = rng if rng is not None else random.Random() + first_outcome: Final = _attempt_fetch_sync(client=fetch_client, url=url, timeout=timeout) + if isinstance(first_outcome, _FetchAttemptRetryable): + verbose_logger.warning( + "LiteLLM: model cost map fetch attempt 1/%d failed: %s; " + "using local backup while retrying in the background", + max_attempts, + first_outcome.reason, + ) + _cost_map_source_info.source = "local" + _cost_map_source_info.fallback_reason = f"Remote fetch failed: {first_outcome.reason}" + local_map: Final = _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()) + if max_attempts > 1: + start_background( + lambda: _continue_remote_fetch_in_background( + url=url, + timeout=timeout, + max_attempts=max_attempts, + sleep=sleep, + rng=fetch_rng, + client=fetch_client, + first_outcome=first_outcome, + apply=apply, + ) + ) + return local_map + + result: Final = first_outcome if isinstance(result, ModelCostMapReloadUnavailable): verbose_logger.warning( "LiteLLM: Failed to fetch remote model cost map from %s: %s. Falling back to local backup.", diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 1cd08fe27c0..efe38376353 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -132,11 +132,7 @@ from litellm.types.utils import ( TextCompletionResponse, TokenCountResponse, ) -from litellm.utils import ( - _invalidate_model_cost_lowercase_map, - load_credentials_from_list, - reapply_runtime_model_cost_registrations, -) +from litellm.utils import load_credentials_from_list if TYPE_CHECKING: from aiohttp import ClientSession @@ -4411,20 +4407,9 @@ def resolve_classifier_plugin( def _swap_in_model_cost_map(new_model_cost_map: dict) -> int: - """Adopt a freshly fetched cost map into this process's litellm state, return the model count""" - litellm.model_cost = new_model_cost_map - # Invalidate case-insensitive lookup map since model_cost was replaced - _invalidate_model_cost_lowercase_map() - # Repopulate provider model sets (e.g. litellm.anthropic_models) so that - # wildcard patterns like "anthropic/*" include any newly added models. - litellm.add_known_models(model_cost_map=new_model_cost_map) - # Counted before the re-apply below, which writes into this same dict, so the - # number reported describes the fetched price data alone. - fetched_model_count: Final = len(new_model_cost_map) if new_model_cost_map else 0 - # The swap discards everything registered at runtime (deployment model_info, - # register_model overrides), so put it back on top of the fresh catalog. - reapply_runtime_model_cost_registrations() - return fetched_model_count + from litellm.litellm_core_utils.get_model_cost_map import adopt_model_cost_map + + return adopt_model_cost_map(new_model_cost_map) def should_load_db_object(object_type: str | SupportedDBObjectType) -> bool: diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 18185126775..dce1a431f13 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -20,21 +20,18 @@ from litellm.litellm_core_utils.get_model_cost_map import ( GetModelCostMap, _count_model_entries, _finalize_model_cost_map, + adopt_model_cost_map, ) def _load_root_cost_map() -> dict: - path = os.path.join( - os.path.dirname(__file__), "../../../model_prices_and_context_window.json" - ) + path = os.path.join(os.path.dirname(__file__), "../../../model_prices_and_context_window.json") with open(path) as f: return json.load(f) def _make_models(n: int) -> dict: - return { - f"model-{i}": {"litellm_provider": "openai", "mode": "chat"} for i in range(n) - } + return {f"model-{i}": {"litellm_provider": "openai", "mode": "chat"} for i in range(n)} def test_count_model_entries_excludes_reserved_keys(): @@ -117,9 +114,7 @@ def test_finalize_pops_key_and_installs_rules(): def test_finalize_with_no_block_clears_rules(): previous = list(get_fallback_generalization_rules()) try: - set_fallback_generalizations( - [{"name": "stale", "pattern": r"^x", "model_info": {"a": 1}}] - ) + set_fallback_generalizations([{"name": "stale", "pattern": r"^x", "model_info": {"a": 1}}]) _finalize_model_cost_map(_make_models(2)) assert match_capability_generalizations("x-1") is None finally: @@ -306,9 +301,7 @@ def test_get_model_cost_map_stamps_loaded_at(monkeypatch): from litellm.litellm_core_utils import get_model_cost_map as module monkeypatch.setattr(module._cost_map_source_info, "loaded_at", None) - client, _calls = _mock_client( - [httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client - ) + client, _calls = _mock_client([httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client) before = datetime.now(timezone.utc) module.get_model_cost_map(url="https://example.invalid/cost_map.json", client=client) @@ -317,6 +310,7 @@ def test_get_model_cost_map_stamps_loaded_at(monkeypatch): assert loaded_at is not None assert before <= loaded_at <= datetime.now(timezone.utc) + # --------------------------------------------------------------------------- # refetch_model_cost_map: retry/backoff behavior for runtime reloads # --------------------------------------------------------------------------- @@ -382,9 +376,7 @@ async def test_refetch_retries_429_honoring_retry_after(): ] ) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloaded) assert len(result.model_cost_map) > 100 assert calls["count"] == 3 @@ -396,9 +388,7 @@ async def test_refetch_gives_up_after_max_attempts_with_exponential_backoff(): """All 429 without Retry-After: exponential backoff waits, then a failure value.""" client, calls = _mock_client([httpx.Response(429)]) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloadUnavailable) assert "429" in result.reason assert "after 3 attempts" in result.reason @@ -418,9 +408,7 @@ async def test_refetch_caps_retry_after_wait(): ] ) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloaded) assert sleeper.waits == [30.0] @@ -435,9 +423,7 @@ async def test_refetch_retries_transport_errors(): ] ) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloaded) assert calls["count"] == 2 assert len(sleeper.waits) == 1 @@ -448,9 +434,7 @@ async def test_refetch_non_retryable_status_fails_immediately(): """A 404 is permanent: one attempt, no sleeps, failure value.""" client, calls = _mock_client([httpx.Response(404)]) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloadUnavailable) assert "404" in result.reason assert calls["count"] == 1 @@ -461,9 +445,7 @@ async def test_refetch_non_retryable_status_fails_immediately(): async def test_refetch_invalid_json_fails_immediately(): client, calls = _mock_client([httpx.Response(200, content=b"not json")]) sleeper = _SleepRecorder() - result = await refetch_model_cost_map( - url=_URL, sleep=sleeper, rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloadUnavailable) assert "invalid JSON" in result.reason assert calls["count"] == 1 @@ -475,9 +457,7 @@ async def test_refetch_shrunk_map_fails_integrity_not_swapped_in(): """A drastically shrunk upstream file is rejected instead of being adopted.""" tiny = json.dumps(_make_models(60)).encode() client, _calls = _mock_client([httpx.Response(200, content=tiny)]) - result = await refetch_model_cost_map( - url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client - ) + result = await refetch_model_cost_map(url=_URL, sleep=_SleepRecorder(), rng=random.Random(0), client=client) assert isinstance(result, ModelCostMapReloadUnavailable) assert "integrity validation" in result.reason @@ -520,62 +500,187 @@ class _SyncSleepRecorder: self.waits.append(seconds) -def test_boot_load_retries_transient_failures_instead_of_falling_back(): - """A refused connection then a 503 at pod boot used to pin the process to the bundled - backup for its lifetime; both are transient and must be retried before giving up.""" +class _BackgroundRecorder: + def __init__(self): + self.callbacks = [] + + def __call__(self, callback): + self.callbacks.append(callback) + + +def test_boot_load_returns_local_map_and_schedules_transient_retry(): + client, calls = _mock_client( + [ + httpx.ConnectError("connection refused"), + ], + client_cls=httpx.Client, + ) + sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + + cost_map = get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, + ) + assert calls["count"] == 1 + assert sleeper.waits == [] + assert len(background.callbacks) == 1 + assert len(cost_map) > 100 + source = get_model_cost_map_source_info() + assert source["source"] == "local" + assert source["fallback_reason"] is not None + + +def test_background_retry_adopts_valid_remote_map(): client, calls = _mock_client( [ httpx.ConnectError("connection refused"), - httpx.Response(503), httpx.Response(200, content=_real_map_bytes()), ], client_cls=httpx.Client, ) sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + applied = [] - cost_map = get_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) + get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, + apply=applied.append, + ) + assert calls["count"] == 1 + assert sleeper.waits == [] + assert len(background.callbacks) == 1 + + background.callbacks[0]() + + assert calls["count"] == 2 + assert len(sleeper.waits) == 1 + assert 2.0 <= sleeper.waits[0] < 3.0 + assert len(applied) == 1 + assert applied[0].keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} + source = get_model_cost_map_source_info() + assert source["source"] == "remote" + assert source["fallback_reason"] is None + + +def test_background_retry_keeps_local_map_after_remaining_failures(): + client, calls = _mock_client( + [httpx.ConnectError("connection refused"), httpx.Response(503)], + client_cls=httpx.Client, + ) + sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + applied = [] + + get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, + apply=applied.append, + ) + background.callbacks[0]() assert calls["count"] == 3 assert len(sleeper.waits) == 2 assert 2.0 <= sleeper.waits[0] < 3.0 assert 4.0 <= sleeper.waits[1] < 5.0 + assert applied == [] + assert get_model_cost_map_source_info()["source"] == "local" + + +def test_boot_load_does_not_schedule_non_retryable_failure(): + client, calls = _mock_client([httpx.Response(404)], client_cls=httpx.Client) + sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + + get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, + ) + + assert calls["count"] == 1 + assert sleeper.waits == [] + assert background.callbacks == [] source = get_model_cost_map_source_info() - assert source["source"] == "remote" - assert source["fallback_reason"] is None + assert source["source"] == "local" + assert source["fallback_reason"] is not None + + +def test_boot_load_success_does_not_schedule_background_retry(): + client, calls = _mock_client([httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client) + sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + + cost_map = get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, + ) + assert calls["count"] == 1 + assert sleeper.waits == [] + assert background.callbacks == [] + assert get_model_cost_map_source_info()["source"] == "remote" assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} -def test_boot_load_honors_retry_after_then_falls_back_after_max_attempts(): - """An outage longer than the retry budget still ends on the bundled backup, and the - recorded fallback reason says how many attempts were spent so operators can tell.""" - client, calls = _mock_client( - [httpx.Response(429, headers={"Retry-After": "7"})], client_cls=httpx.Client +def test_boot_load_with_one_attempt_does_not_schedule_background_retry(): + client, calls = _mock_client([httpx.ConnectError("connection refused")], client_cls=httpx.Client) + sleeper = _SyncSleepRecorder() + background = _BackgroundRecorder() + + get_model_cost_map( + url=_URL, + max_attempts=1, + sleep=sleeper, + rng=random.Random(0), + client=client, + start_background=background, ) - sleeper = _SyncSleepRecorder() - cost_map = get_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) - - assert calls["count"] == 3 - assert sleeper.waits == [7.0, 7.0] - source = get_model_cost_map_source_info() - assert source["source"] == "local" - assert "after 3 attempts" in source["fallback_reason"] - assert len(cost_map) > 100 - - -def test_boot_load_does_not_retry_permanent_failures(): - """A 404 or a malformed URL cannot heal by waiting: one attempt, no sleeps, backup.""" - client, calls = _mock_client([httpx.Response(404)], client_cls=httpx.Client) - sleeper = _SyncSleepRecorder() - - get_model_cost_map(url=_URL, sleep=sleeper, rng=random.Random(0), client=client) assert calls["count"] == 1 assert sleeper.waits == [] + assert background.callbacks == [] assert get_model_cost_map_source_info()["source"] == "local" - get_model_cost_map(url="not a url", sleep=sleeper, rng=random.Random(0)) - assert sleeper.waits == [] - assert get_model_cost_map_source_info()["source"] == "local" + +def test_adopt_model_cost_map_replays_runtime_registration_and_provider_models(): + import litellm + from litellm import utils as litellm_utils + + original_model_cost = litellm.model_cost + original_registry = dict(litellm_utils._runtime_registered_model_cost) + original_anthropic_models = set(litellm.anthropic_models) + try: + litellm.register_model( + model_cost={"custom/deployment-model": {"litellm_provider": "custom", "max_input_tokens": 4321}} + ) + + models_count = adopt_model_cost_map({"anthropic/new-model": {"litellm_provider": "anthropic", "mode": "chat"}}) + + assert models_count == 1 + assert "anthropic/new-model" in litellm.anthropic_models + assert litellm.model_cost["custom/deployment-model"]["max_input_tokens"] == 4321 + finally: + litellm.model_cost = original_model_cost # test-quality-ok: restore the module state changed by adoption + litellm_utils._runtime_registered_model_cost.clear() + litellm_utils._runtime_registered_model_cost.update(original_registry) + litellm.anthropic_models.clear() + litellm.anthropic_models.update(original_anthropic_models) + litellm_utils._invalidate_model_cost_lowercase_map() def test_boot_load_respects_local_env_override(monkeypatch): From aa0a9ab3ea4dff53a22cbc60fbc0195c4ab6098c Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 02:29:56 +0000 Subject: [PATCH 072/105] refactor(cost-map): drop initial_outcome flag from retry loop Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/get_model_cost_map.py | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 90ff696f926..d9b5a492539 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -324,19 +324,14 @@ async def _fetch_remote_model_cost_map_with_retry( def _fetch_remote_model_cost_map_with_retry_sync( url: str, timeout: int, - max_attempts: int, + attempts: range, sleep: Callable[[float], None], rng: random.Random, client: _SyncGetClient, - starting_attempt: int = 1, - initial_outcome: _FetchAttemptRetryable | None = None, ) -> ModelCostMapReloadResult: - for attempt in range(starting_attempt, max_attempts + 1): - outcome = ( - initial_outcome - if initial_outcome is not None and attempt == starting_attempt - else _attempt_fetch_sync(client=client, url=url, timeout=timeout) - ) + max_attempts: Final = attempts.stop - 1 + for attempt in attempts: + outcome = _attempt_fetch_sync(client=client, url=url, timeout=timeout) if not isinstance(outcome, _FetchAttemptRetryable): return outcome wait_seconds = _next_retry_wait(outcome=outcome, attempt=attempt, max_attempts=max_attempts, rng=rng) @@ -557,18 +552,18 @@ def _continue_remote_fetch_in_background( sleep: Callable[[float], None], rng: random.Random, client: _SyncGetClient, - first_outcome: _FetchAttemptRetryable, + first_wait: float, apply: Callable[[dict], object], # mutable-ok: injected callback receives the mutable cost-map dict ) -> None: try: + sleep(first_wait) result: Final = _fetch_remote_model_cost_map_with_retry_sync( url=url, timeout=timeout, - max_attempts=max_attempts, + attempts=range(2, max_attempts + 1), sleep=sleep, rng=rng, client=client, - initial_outcome=first_outcome, ) if isinstance(result, ModelCostMapReloadUnavailable): verbose_logger.warning( @@ -652,19 +647,26 @@ def get_model_cost_map( local_map: Final = _finalize_loaded_model_cost_map( GetModelCostMap.load_local_model_cost_map_with_revision() ).model_cost_map - if max_attempts > 1: - start_background( - lambda: _continue_remote_fetch_in_background( - url=url, - timeout=timeout, - max_attempts=max_attempts, - sleep=sleep, - rng=fetch_rng, - client=fetch_client, - first_outcome=first_outcome, - apply=apply, - ) + first_wait: Final = _next_retry_wait( + outcome=first_outcome, + attempt=1, + max_attempts=max_attempts, + rng=fetch_rng, + ) + if isinstance(first_wait, ModelCostMapReloadUnavailable): + return local_map + start_background( + lambda: _continue_remote_fetch_in_background( + url=url, + timeout=timeout, + max_attempts=max_attempts, + sleep=sleep, + rng=fetch_rng, + client=fetch_client, + first_wait=first_wait, + apply=apply, ) + ) return local_map result: Final = first_outcome From 536a85b42967fd0c9c6b49d2df9298232034427a Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 02:48:17 +0000 Subject: [PATCH 073/105] fix(cost-map): keep register_model url fetch to a single attempt Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/utils.py | 2 +- tests/test_litellm/test_utils.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index 36b48d3b8d8..8df28870544 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3079,7 +3079,7 @@ def register_model( # Convert stringified numbers to appropriate numeric types loaded_model_cost = model_cost elif isinstance(model_cost, str): - loaded_model_cost = litellm.get_model_cost_map(url=model_cost) + loaded_model_cost = litellm.get_model_cost_map(url=model_cost, max_attempts=1) if persist_across_reloads: _registrations: Final[Mapping[str, Mapping[str, object]]] = loaded_model_cost diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index fee5e3a2e4c..e90372141bd 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -2,10 +2,12 @@ import asyncio import json import logging import os +import threading from datetime import datetime, timedelta, timezone from typing import Final from unittest.mock import AsyncMock, MagicMock, patch +import httpx import pytest import respx from jsonschema import validate @@ -2382,6 +2384,27 @@ def test_register_model_with_scientific_notation(): _invalidate_model_cost_lowercase_map() +@respx.mock +def test_register_model_url_fetch_uses_single_attempt(monkeypatch): + monkeypatch.setattr(litellm, "model_cost", dict(litellm.model_cost)) + before = dict(litellm.model_cost) + threads_before = {thread.name for thread in threading.enumerate()} + route = respx.get("https://example.invalid/custom_pricing.json").mock( + return_value=httpx.Response(503) + ) + + litellm.register_model(model_cost="https://example.invalid/custom_pricing.json") + + threads_after = {thread.name for thread in threading.enumerate()} + assert route.call_count == 1 + assert not (threads_after - threads_before) & {"litellm-model-cost-map-retry"} + assert not any( + thread.name == "litellm-model-cost-map-retry" and thread.is_alive() + for thread in threading.enumerate() + ) + assert litellm.model_cost.keys() >= before.keys() + + def test_register_model_openrouter_without_slash(): """ Test that register_model handles openrouter models without '/' in the name. From 902dd7b2b6c61b282d3dafb6aee92607f53ecc81 Mon Sep 17 00:00:00 2001 From: tin-berri Date: Tue, 8 Sep 2026 19:56:21 -0700 Subject: [PATCH 074/105] fix(mcp): log proxy tool dispatch exceptions (#40351) --- .../proxy/_experimental/mcp_server/server.py | 51 ++++++++++---- tests/mcp_tests/test_proxy_mcp_e2e.py | 30 ++++++++ .../mcp_server/test_mcp_proxy_mode.py | 70 ++++++++++++++++++- 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 30793ef246d..9a52da0cab1 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -1017,18 +1017,45 @@ if MCP_AVAILABLE: if name == MCP_PROXY_CALL_TOOL_NAME else None ) - proxy_result: Final = await handle_mcp_proxy_tool( - name=name, - arguments=arguments or {}, # mutable-ok: proxy handler payload - user_api_key_dict=user_api_key_auth, - client_ip=client_ip, - mcp_servers=mcp_servers, - mcp_auth_header=mcp_auth_header, - mcp_server_auth_headers=mcp_server_auth_headers, - oauth2_headers=oauth2_headers, - raw_headers=raw_headers, - litellm_logging_obj=proxy_logging_obj, - ) + try: + proxy_result: Final = await handle_mcp_proxy_tool( + name=name, + arguments=arguments or {}, # mutable-ok: proxy handler payload + user_api_key_dict=user_api_key_auth, + client_ip=client_ip, + mcp_servers=mcp_servers, + mcp_auth_header=mcp_auth_header, + mcp_server_auth_headers=mcp_server_auth_headers, + oauth2_headers=oauth2_headers, + raw_headers=raw_headers, + litellm_logging_obj=proxy_logging_obj, + ) + except Exception as exc: + if proxy_logging_obj is not None: + from litellm.proxy.proxy_server import proxy_logging_obj as request_logging_obj + + failure_end: Final = datetime.now() # noqa: DTZ005 # matches the logging pipeline start time + failure_traceback: Final = traceback.format_exc(limit=MAXIMUM_TRACEBACK_LINES_TO_LOG) + try: + proxy_logging_obj.failure_handler(exc, failure_traceback, proxy_call_start, failure_end) + await proxy_logging_obj.async_failure_handler( + exc, failure_traceback, proxy_call_start, failure_end + ) + if not isinstance(exc, MCPUpstreamAuthError): + await request_logging_obj.post_call_failure_hook( + request_data={ # mutable-ok: failure hook mutates its request payload + "name": name, + "arguments": arguments, + "litellm_logging_obj": proxy_logging_obj, + }, + original_exception=exc, + user_api_key_dict=user_api_key_auth, + route="/mcp/call_tool", + traceback_str=failure_traceback, + ) + except Exception: + verbose_logger.exception("Error logging failed MCP proxy tool call") + raise if proxy_logging_obj is not None: return await _fire_mcp_tool_call_logging( logging_obj=proxy_logging_obj, diff --git a/tests/mcp_tests/test_proxy_mcp_e2e.py b/tests/mcp_tests/test_proxy_mcp_e2e.py index 5e5b9db2dc6..f0fc3d892e2 100644 --- a/tests/mcp_tests/test_proxy_mcp_e2e.py +++ b/tests/mcp_tests/test_proxy_mcp_e2e.py @@ -471,6 +471,7 @@ class ProxyCallRecorder(CustomLogger): def __init__(self) -> None: super().__init__() self.events: queue.Queue[str] = queue.Queue() + self.failures: queue.Queue[str] = queue.Queue() async def async_log_success_event( self, kwargs: dict[str, object], response_obj: object, start_time: datetime, end_time: datetime @@ -479,6 +480,13 @@ class ProxyCallRecorder(CustomLogger): if isinstance(payload, dict) and payload.get("call_type") == "call_mcp_tool": self.events.put(json.dumps(payload, default=str)) + async def async_log_failure_event( + self, kwargs: dict[str, object], response_obj: object, start_time: datetime, end_time: datetime + ) -> None: + payload = kwargs.get("standard_logging_object") + if isinstance(payload, dict) and payload.get("call_type") == "call_mcp_tool": + self.failures.put(json.dumps(payload, default=str)) + proxy_call_recorder = ProxyCallRecorder() @@ -636,6 +644,28 @@ class TestProxyMcpAuthorizationScope: assert payload["metadata"]["mcp_tool_call_metadata"]["name"] == "add" assert payload["metadata"]["mcp_tool_call_metadata"]["namespaced_tool_name"] == "math_restricted/add" + @pytest.mark.asyncio + async def test_proxy_scope_exception_returns_iserror_and_emits_failure_log(self, proxy_server_url: str) -> None: + async with _scoped_session( + proxy_server_url, + "sk-none", + **{"x-mcp-servers": "math_restricted", "x-litellm-call-id": "proxy-scope-denial"}, + ) as session: + result = await session.call_tool("call_tool", {"tool_id": "denied-scope", "arguments": {}}) + assert result.isError is True + assert result.content[0].text == ( + "Error: The key is not allowed to access the requested MCP servers: math_restricted" + ) + async with asyncio.timeout(10): + while True: + payload = json.loads(await asyncio.to_thread(proxy_call_recorder.failures.get, True, 5)) + if payload["id"] == "proxy-scope-denial": + break + assert payload["call_type"] == "call_mcp_tool" + assert payload["status"] == "failure" + assert payload["response_cost"] == 0 + assert "math_restricted" in payload["error_str"] + @pytest.mark.parametrize("arguments", ["wrong", False, None, [], 0]) def test_handler_rejects_non_object_arguments( self, proxy_server_url: str, _proxy_server: ProxyRig, arguments: object diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py index 29711f80deb..67b7c5a3414 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_proxy_mode.py @@ -1,10 +1,16 @@ +import json +from datetime import datetime + import pytest +from fastapi import HTTPException from mcp.shared.exceptions import McpError from pydantic import AnyUrl +import litellm +from litellm.integrations.custom_logger import CustomLogger from litellm.proxy._experimental.mcp_server import server from litellm.proxy._experimental.mcp_server.mcp_context import _mcp_proxy_mode -from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy._types import LiteLLM_ObjectPermissionTable, UserAPIKeyAuth AUTH = UserAPIKeyAuth(api_key="key") @@ -48,3 +54,65 @@ async def test_proxy_rejects_non_tool_protocol_operations() -> None: await server.list_resource_templates() with pytest.raises(McpError): await server.read_resource(AnyUrl("https://example.com/resource")) + + +class FailureRecorder(CustomLogger): + def __init__(self) -> None: + super().__init__() + self.events: list[tuple[str, str]] = [] + + async def async_log_failure_event( + self, kwargs: dict[str, object], response_obj: object, start_time: datetime, end_time: datetime + ) -> None: + self.events.append(("failure", json.dumps(kwargs.get("standard_logging_object"), default=str))) + + async def async_log_success_event( + self, kwargs: dict[str, object], response_obj: object, start_time: datetime, end_time: datetime + ) -> None: + self.events.append(("success", json.dumps(kwargs.get("standard_logging_object"), default=str))) + + async def async_post_call_failure_hook( + self, + request_data: dict[str, object], + original_exception: Exception, + user_api_key_dict: UserAPIKeyAuth, + traceback_str: str | None = None, + ) -> None: + self.events.append(("post_failure", json.dumps(request_data, default=str))) + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("proxy_mode") +async def test_proxy_scope_exception_emits_failure_log(monkeypatch: pytest.MonkeyPatch) -> None: + recorder = FailureRecorder() + monkeypatch.setattr(litellm, "callbacks", [recorder]) + auth = UserAPIKeyAuth( + api_key="scope-denial-key-hash", + object_permission=LiteLLM_ObjectPermissionTable(object_permission_id="denied", mcp_servers=["no-mcp-servers"]), + ) + arguments = {"tool_id": "denied-scope", "arguments": {}} + + with pytest.raises(HTTPException) as denied: + await server._dispatch_virtual_mcp_tool( + name="call_tool", + arguments=arguments, + user_api_key_auth=auth, + client_ip=None, + mcp_servers=["ungranted"], + raw_headers={"authorization": "Bearer raw-scope-secret", "x-litellm-call-id": "scope-denial"}, + ) + + assert denied.value.status_code == 403 + assert denied.value.detail == {"error": "The key is not allowed to access the requested MCP servers: ungranted"} + assert [kind for kind, _ in recorder.events] == ["failure", "post_failure"] + payload = json.loads(recorder.events[0][1]) + assert payload["id"] == "scope-denial" + assert payload["call_type"] == "call_mcp_tool" + assert payload["status"] == "failure" + assert payload["response_cost"] == 0 + assert "ungranted" in payload["error_str"] + hook_payload = json.loads(recorder.events[1][1]) + assert hook_payload["standard_logging_object"] == payload + assert hook_payload["arguments"] == arguments + assert "raw_headers" not in hook_payload + assert "raw-scope-secret" not in recorder.events[1][1] From 4179f086e0196107dfc83da2a4e0bb1d15568edd Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 02:58:51 +0000 Subject: [PATCH 075/105] refactor(cost-map): share local-fallback and remote-accept paths Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/get_model_cost_map.py | 111 ++++++++---------- 1 file changed, 47 insertions(+), 64 deletions(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index d9b5a492539..67d06a0758e 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -531,6 +531,32 @@ def _finalize_loaded_model_cost_map(loaded: ModelCostMapReloaded) -> ModelCostMa return replace(loaded, model_cost_map=_finalize_model_cost_map(loaded.model_cost_map)) +def _use_local_backup(reason: str | None) -> dict: # mutable-ok: returns the mutable model-cost map contract + _cost_map_source_info.source = "local" + _cost_map_source_info.fallback_reason = reason + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map + + +def _accept_remote( + result: ModelCostMapReloaded, url: str +) -> dict | None: # mutable-ok: returns the mutable model-cost map contract + if not GetModelCostMap.validate_model_cost_map( + fetched_map=result.model_cost_map, + backup_model_count=GetModelCostMap._get_backup_model_count(), # pyright: ignore[reportPrivateUsage] # integrity cache + ): + verbose_logger.warning( + "LiteLLM: Fetched model cost map failed integrity check. Using local backup instead. url=%s", + url, + ) + return None + finalized: Final = _finalize_loaded_model_cost_map(result).model_cost_map + _cost_map_source_info.source = "remote" + _cost_map_source_info.url = url + _cost_map_source_info.is_env_forced = False + _cost_map_source_info.fallback_reason = None + return finalized + + def adopt_model_cost_map( new_model_cost_map: dict, # mutable-ok: public API preserves the mutable cost-map contract ) -> int: @@ -545,17 +571,20 @@ def adopt_model_cost_map( return fetched_model_count -def _continue_remote_fetch_in_background( +def _retry_remote_fetch_in_background( url: str, timeout: int, max_attempts: int, sleep: Callable[[float], None], rng: random.Random, client: _SyncGetClient, - first_wait: float, + first_outcome: _FetchAttemptRetryable, apply: Callable[[dict], object], # mutable-ok: injected callback receives the mutable cost-map dict ) -> None: try: + first_wait: Final = _next_retry_wait(outcome=first_outcome, attempt=1, max_attempts=max_attempts, rng=rng) + if isinstance(first_wait, ModelCostMapReloadUnavailable): + return sleep(first_wait) result: Final = _fetch_remote_model_cost_map_with_retry_sync( url=url, @@ -572,23 +601,12 @@ def _continue_remote_fetch_in_background( max_attempts, ) return - backup_model_count: Final = GetModelCostMap._get_backup_model_count() # pyright: ignore[reportPrivateUsage] # integrity cache - if not GetModelCostMap.validate_model_cost_map( - fetched_map=result.model_cost_map, - backup_model_count=backup_model_count, - ): - verbose_logger.warning( - "LiteLLM: Fetched model cost map failed integrity check. Keeping local backup. url=%s", - url, - ) + _litellm_import_complete.wait() + accepted: Final = _accept_remote(result, url) + if accepted is None: _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" return - _litellm_import_complete.wait() - apply(_finalize_loaded_model_cost_map(result).model_cost_map) - _cost_map_source_info.source = "remote" - _cost_map_source_info.url = url - _cost_map_source_info.is_env_forced = False - _cost_map_source_info.fallback_reason = None + apply(accepted) _cost_map_source_info.loaded_at = datetime.now(timezone.utc) except Exception as e: verbose_logger.warning("LiteLLM: Background model cost map retry failed: %s", e) @@ -623,77 +641,42 @@ def get_model_cost_map( # Note: can't use get_secret_bool here — this runs during litellm.__init__ # before litellm._key_management_settings is set. if os.getenv("LITELLM_LOCAL_MODEL_COST_MAP", "").lower() == "true": - _cost_map_source_info.source = "local" _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True - _cost_map_source_info.fallback_reason = None - return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map + return _use_local_backup(None) _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False fetch_client: Final = client if client is not None else httpx fetch_rng: Final = rng if rng is not None else random.Random() - first_outcome: Final = _attempt_fetch_sync(client=fetch_client, url=url, timeout=timeout) - if isinstance(first_outcome, _FetchAttemptRetryable): + outcome: Final = _attempt_fetch_sync(client=fetch_client, url=url, timeout=timeout) + if isinstance(outcome, ModelCostMapReloaded): + accepted: Final = _accept_remote(outcome, url) + return accepted if accepted is not None else _use_local_backup("Remote data failed integrity validation") + if isinstance(outcome, _FetchAttemptRetryable) and max_attempts > 1: verbose_logger.warning( "LiteLLM: model cost map fetch attempt 1/%d failed: %s; " "using local backup while retrying in the background", max_attempts, - first_outcome.reason, + outcome.reason, ) - _cost_map_source_info.source = "local" - _cost_map_source_info.fallback_reason = f"Remote fetch failed: {first_outcome.reason}" - local_map: Final = _finalize_loaded_model_cost_map( - GetModelCostMap.load_local_model_cost_map_with_revision() - ).model_cost_map - first_wait: Final = _next_retry_wait( - outcome=first_outcome, - attempt=1, - max_attempts=max_attempts, - rng=fetch_rng, - ) - if isinstance(first_wait, ModelCostMapReloadUnavailable): - return local_map start_background( - lambda: _continue_remote_fetch_in_background( + lambda: _retry_remote_fetch_in_background( url=url, timeout=timeout, max_attempts=max_attempts, sleep=sleep, rng=fetch_rng, client=fetch_client, - first_wait=first_wait, + first_outcome=outcome, apply=apply, ) ) - return local_map - - result: Final = first_outcome - if isinstance(result, ModelCostMapReloadUnavailable): + else: verbose_logger.warning( "LiteLLM: Failed to fetch remote model cost map from %s: %s. Falling back to local backup.", url, - result.reason, + outcome.reason, ) - _cost_map_source_info.source = "local" - _cost_map_source_info.fallback_reason = f"Remote fetch failed: {result.reason}" - return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map - content: Final = result.model_cost_map - - # Validate using cached count (cheap int comparison, no file I/O) - if not GetModelCostMap.validate_model_cost_map( - fetched_map=content, - backup_model_count=GetModelCostMap._get_backup_model_count(), - ): - verbose_logger.warning( - "LiteLLM: Fetched model cost map failed integrity check. Using local backup instead. url=%s", - url, - ) - _cost_map_source_info.source = "local" - _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" - return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map - - _cost_map_source_info.source = "remote" - _cost_map_source_info.fallback_reason = None - return _finalize_loaded_model_cost_map(result).model_cost_map + return _use_local_backup(f"Remote fetch failed: {outcome.reason}") From 9a721abf0d098f40caa6ba67d343e99de74f7fc2 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 03:50:49 +0000 Subject: [PATCH 076/105] test(cost-map): clear LITELLM_LOCAL_MODEL_COST_MAP in register_model url test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/test_litellm/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index e90372141bd..e42608c9904 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -2386,6 +2386,7 @@ def test_register_model_with_scientific_notation(): @respx.mock def test_register_model_url_fetch_uses_single_attempt(monkeypatch): + monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) monkeypatch.setattr(litellm, "model_cost", dict(litellm.model_cost)) before = dict(litellm.model_cost) threads_before = {thread.name for thread in threading.enumerate()} From 15392e7b3a298bec57f4aae31e863e39fb182cc6 Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:26:04 -0700 Subject: [PATCH 077/105] fix(mcp): surface connection test failures safely --- litellm/experimental_mcp_client/client.py | 21 +++ .../mcp_server/rest_endpoints.py | 59 ++++++- .../test_mcp_client.py | 124 ++++++++++++++- .../mcp_server/test_rest_endpoints.py | 148 +++++++++++++++++- 4 files changed, 334 insertions(+), 18 deletions(-) diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index 3503468c735..cabcc6c03ba 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -18,6 +18,7 @@ from mcp import ClientSession, McpError, ReadResourceResult, Resource, StdioServ from mcp.client.sse import sse_client from mcp.client.stdio import stdio_client from mcp.shared.message import SessionMessage +from mcp.shared.session import RequestResponder from typing_extensions import Unpack _TransportStreams: TypeAlias = tuple[ @@ -56,10 +57,13 @@ def missing_streamable_http_client_error() -> ImportError: from mcp.types import CallToolRequestParams as MCPCallToolRequestParams from mcp.types import CallToolResult as MCPCallToolResult from mcp.types import ( + ClientResult, GetPromptRequestParams, GetPromptResult, Prompt, ResourceTemplate, + ServerNotification, + ServerRequest, TextContent, ) from mcp.types import Tool as MCPTool @@ -442,6 +446,18 @@ class MCPClient: in_flight_error: BaseException | None = None try: read_stream, write_stream = transport[0], transport[1] + stream_error: Final[asyncio.Future[Exception]] = asyncio.get_running_loop().create_future() + + async def receive_message( + message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception, + ) -> None: + if not isinstance(message, ValueError): + return + if not stream_error.done(): + stream_error.set_result(message) + # The SDK closes pending requests when its message handler raises. + raise RuntimeError("MCP response stream failed") + # Build session kwargs with optional callbacks session_kwargs: Final[dict[str, Any]] = {} if self._sampling_callback is not None: @@ -456,6 +472,7 @@ class MCPClient: read_stream, write_stream, read_timeout_seconds=timedelta(seconds=self.timeout), + message_handler=receive_message if self.transport_type == MCPTransport.http else None, **session_kwargs, ) session: Final = await session_ctx.__aenter__() @@ -467,6 +484,10 @@ class MCPClient: if isinstance(ins, str) and ins.strip(): self._last_initialize_instructions = ins.strip() return await operation(session) + except McpError: + if stream_error.done(): + raise stream_error.result() + raise finally: try: await session_ctx.__aexit__(None, None, None) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 329dddbdf05..c0ffeaeac62 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -3,12 +3,15 @@ import importlib from collections.abc import Awaitable, Callable, Mapping, Sequence from dataclasses import dataclass from datetime import datetime +from traceback import walk_tb from types import MappingProxyType from typing import TYPE_CHECKING, Any, Final, Literal +from uuid import uuid4 import anyio import httpx from fastapi import APIRouter, Depends, HTTPException, Query, Request, status +from pydantic import ValidationError from starlette.datastructures import Headers from litellm._logging import verbose_logger @@ -30,6 +33,8 @@ from litellm.proxy._experimental.mcp_server.faults.list_outcomes import ( list_fault_http_status, outcome_wire_value, ) +from litellm.proxy._experimental.mcp_server.faults.traversal import iter_exception_tree +from litellm.proxy._experimental.mcp_server.oauth_utils import _redact_mcp_resource_url from litellm.proxy._experimental.mcp_server.ui_session_utils import ( acting_user_auth, build_effective_auth_contexts, @@ -78,11 +83,38 @@ _MCP_GUARDRAIL_REJECTIONS: Final = ( def _connection_error_message(exc: BaseException, url: str | None, timeout_seconds: float) -> str: + reference: Final = uuid4().hex + verbose_logger.error( + "MCP connection test failed (reference=%s): %s", + reference, + tuple( + ( + type(cause).__name__, + tuple( + (frame.f_code.co_filename, lineno, frame.f_code.co_name) + for frame, lineno in walk_tb(cause.__traceback__) + ), + ) + for cause in iter_exception_tree(exc) + ), + ) + return next( + ( + message + for cause in iter_exception_tree(exc) + if (message := _known_connection_error_message(cause, url, timeout_seconds)) is not None + ), + "An unexpected error occurred while testing the MCP connection. " + f"Retry; if it persists, share reference {reference} with your gateway administrator.", + ) + + +def _known_connection_error_message(exc: BaseException, url: str | None, timeout_seconds: float) -> str | None: if isinstance(exc, MCPServerURLCredentialsError): return str(exc.detail) if isinstance(exc, TimeoutError): return ( - f"Failed to connect to MCP server: no response from {url or 'the server'} " + f"Failed to connect to MCP server: no response from {_redact_mcp_resource_url(url) or 'the server'} " f"within {timeout_seconds:.0f}s. Check that the LiteLLM proxy can reach this URL " "from its network (DNS, egress rules, firewalls) and that the server answers MCP requests." ) @@ -99,10 +131,32 @@ def _connection_error_message(exc: BaseException, url: str | None, timeout_secon return "Failed to connect to MCP server: the connection timed out." if isinstance(exc, httpx.HTTPStatusError): return f"Failed to connect to MCP server: it returned HTTP {exc.response.status_code}." - return "Failed to connect to MCP server. Check proxy logs for details." + if isinstance(exc, ValueError) and str(exc).startswith("Unexpected content type:"): + return ( + "Failed to connect to MCP server: the endpoint returned an unsupported content type. " + "Check that the URL is an MCP endpoint, not a web page, and matches the selected transport." + ) + if isinstance(exc, ValidationError) and exc.title in ("JSONRPCMessage", "InitializeResult", "ListToolsResult"): + return ( + "Failed to connect to MCP server: the endpoint returned invalid JSON or an invalid MCP response. " + "Check the MCP endpoint URL and the server's protocol implementation." + ) + if MCP_AVAILABLE and isinstance(exc, McpError): + if exc.error.code == 32600 and exc.error.message == "Session terminated": + return ( + "Failed to connect to MCP server: the MCP session was terminated. " + "Check that the URL points to an MCP endpoint and matches the selected transport, " + "then retry to start a new session." + ) + return ( + f"Failed to connect to MCP server: the MCP request failed (JSON-RPC code {exc.error.code}). " + "Check that the endpoint supports MCP initialization and tool listing, and check the upstream server logs." + ) + return None if MCP_AVAILABLE: + from mcp.shared.exceptions import McpError from mcp.types import Tool as MCPTool from litellm.experimental_mcp_client.client import MCPClient @@ -1342,7 +1396,6 @@ if MCP_AVAILABLE: except (KeyboardInterrupt, SystemExit, asyncio.CancelledError): raise except BaseException as e: - verbose_logger.error("Error in MCP operation: %s", e, exc_info=True) return { "status": "error", "error": True, diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index 4db131da62c..49268b4d22d 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -1,9 +1,11 @@ import asyncio import base64 +import json import os import sys from importlib import metadata from pathlib import Path +from typing import Final from unittest.mock import AsyncMock, MagicMock, patch import anyio @@ -11,6 +13,8 @@ import httpx import pytest from litellm.proxy._experimental.mcp_server.outbound_credentials.httpx_auth import StaticHeaderAuth from mcp import McpError +from mcp.client.streamable_http import streamable_http_client +from pydantic import ValidationError from mcp.shared.message import SessionMessage from mcp.types import ( LATEST_PROTOCOL_VERSION, @@ -1224,14 +1228,14 @@ def test_without_a_configured_slot_the_existing_precedence_is_unchanged(): _REDIRECT_CASES = [ - ("https://upstream.example.com/mcp", "https://upstream.example.com/other"), # same origin + ("https://upstream.example.com/mcp", "https://upstream.example.com/other"), # same origin ("https://upstream.example.com/mcp", "https://upstream.example.com:443/other"), # explicit default port - ("https://upstream.example.com/mcp", "https://attacker.example.com/collect"), # different host - ("https://upstream.example.com/mcp", "http://upstream.example.com/collect"), # scheme downgrade - ("https://upstream.example.com/mcp", "https://upstream.example.com:8443/other"), # different port - ("https://upstream.example.com/mcp", "https://sub.upstream.example.com/x"), # different host - ("http://upstream.example.com/mcp", "https://upstream.example.com/other"), # http -> https upgrade - ("http://upstream.example.com/mcp", "http://upstream.example.com/other"), # same origin, plain http + ("https://upstream.example.com/mcp", "https://attacker.example.com/collect"), # different host + ("https://upstream.example.com/mcp", "http://upstream.example.com/collect"), # scheme downgrade + ("https://upstream.example.com/mcp", "https://upstream.example.com:8443/other"), # different port + ("https://upstream.example.com/mcp", "https://sub.upstream.example.com/x"), # different host + ("http://upstream.example.com/mcp", "https://upstream.example.com/other"), # http -> https upgrade + ("http://upstream.example.com/mcp", "http://upstream.example.com/other"), # same origin, plain http ] @@ -1283,3 +1287,109 @@ def test_a_differently_cased_injected_header_cannot_shadow_the_slot() -> None: headers = client._get_auth_headers() assert [v for k, v in headers.items() if k.lower() == "esb-oauth"] == ["Bearer minted-token"] assert headers["X-Trace"] == "keep" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("content_type", "body", "expected_type"), + [ + ("text/html", b"secret-page", ValueError), + ("application/json", b"secret-invalid-json", ValidationError), + ("application/json", b'{"secret":"invalid-rpc"}', ValidationError), + ("application/json", b'{"jsonrpc":"2.0","id":0,"result":{"secret":"invalid-schema"}}', ValidationError), + ], +) +async def test_invalid_http_response_surfaces_without_waiting_for_timeout( + content_type: str, body: bytes, expected_type: type[Exception] +) -> None: + from litellm.proxy._experimental.mcp_server.rest_endpoints import _connection_error_message + + def respond(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, headers={"Content-Type": content_type}, content=body) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=30) + with pytest.raises(expected_type) as caught: + await asyncio.wait_for( + client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), + lambda session: session.list_tools(), + ), + timeout=3, + ) + + message: Final = _connection_error_message(caught.value, client.server_url, 30) + assert "unsupported content type" in message or "invalid MCP response" in message + assert "secret" not in message + assert "timed out" not in message + + +@pytest.mark.asyncio +@pytest.mark.parametrize("status_code", [200, 401, 503]) +async def test_http_response_handler_preserves_success_and_http_errors(status_code: int) -> None: + def respond(request: httpx.Request) -> httpx.Response: + if request.method == "DELETE": + return httpx.Response(200) + payload: Final = json.loads(request.content) + if "id" not in payload: + return httpx.Response(202) + result: Final = ( + { + "protocolVersion": LATEST_PROTOCOL_VERSION, + "capabilities": {}, + "serverInfo": {"name": "test", "version": "1"}, + } + if payload["method"] == "initialize" + else {"tools": []} + ) + return httpx.Response(status_code, json={"jsonrpc": "2.0", "id": payload["id"], "result": result}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=30) + operation: Final = client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), lambda session: session.list_tools() + ) + if status_code == 200: + result: Final = await asyncio.wait_for(operation, timeout=3) + assert result.tools == [] + else: + with pytest.raises(httpx.HTTPStatusError) as caught: + await asyncio.wait_for(operation, timeout=3) + assert caught.value.response.status_code == status_code + + +@pytest.mark.asyncio +async def test_invalid_tool_list_schema_is_identified_as_an_upstream_response() -> None: + from litellm.proxy._experimental.mcp_server.rest_endpoints import _connection_error_message + + def respond(request: httpx.Request) -> httpx.Response: + if request.method == "DELETE": + return httpx.Response(200) + payload: Final = json.loads(request.content) + if "id" not in payload: + return httpx.Response(202) + result: Final = ( + { + "protocolVersion": LATEST_PROTOCOL_VERSION, + "capabilities": {}, + "serverInfo": {"name": "test", "version": "1"}, + } + if payload["method"] == "initialize" + else {"tools": "secret-invalid-tools"} + ) + return httpx.Response(200, json={"jsonrpc": "2.0", "id": payload["id"], "result": result}) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=30) + with pytest.raises(ValidationError) as caught: + await asyncio.wait_for( + client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), + lambda session: session.list_tools(), + ), + timeout=3, + ) + + message: Final = _connection_error_message(caught.value, client.server_url, 30) + assert "invalid MCP response" in message + assert "secret" not in message 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 bd692776c82..73734131130 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 @@ -3,7 +3,7 @@ import inspect import json import sys from datetime import datetime -from typing import Any, Dict, Optional +from typing import Any, Dict, Final, Optional from unittest.mock import AsyncMock, MagicMock if sys.version_info < (3, 11): # BaseExceptionGroup is a builtin only from 3.11 @@ -113,7 +113,7 @@ class TestExecuteWithMcpClient: assert "stack_trace" not in result @pytest.mark.asyncio - async def test_timeout_caps_hanging_operation_and_names_url(self, monkeypatch): + async def test_timeout_caps_hanging_operation_and_names_origin(self, monkeypatch): async def fake_create_client(*args, **kwargs): return object() @@ -138,7 +138,7 @@ class TestExecuteWithMcpClient: ) assert result["error"] is True - assert "https://mcp.example.com/mcp/" in result["message"] + assert "https://mcp.example.com" in result["message"] @pytest.mark.asyncio async def test_timeout_covers_client_creation(self, monkeypatch): @@ -166,15 +166,15 @@ class TestExecuteWithMcpClient: ) assert result["error"] is True - assert "https://mcp.example.com/mcp/" in result["message"] + assert "https://mcp.example.com" in result["message"] def test_timeout_defaults_to_tool_listing_timeout(self): default = inspect.signature(rest_endpoints._execute_with_mcp_client).parameters["timeout_seconds"].default assert default == MCP_TOOL_LISTING_TIMEOUT - def test_connection_error_message_timeout_names_url_and_budget(self): + def test_connection_error_message_timeout_names_origin_and_budget(self): message = rest_endpoints._connection_error_message(TimeoutError(), "https://api.example.com/mcp/", 30.0) - assert "https://api.example.com/mcp/" in message + assert "https://api.example.com" in message assert "30s" in message def test_connection_error_message_hides_arbitrary_http_exception_detail(self): @@ -592,7 +592,7 @@ class TestExecuteWithMcpClient: assert result["status"] == "error" assert result["error"] is True - assert "Failed to connect to MCP server" in result["message"] + assert "reference" in result["message"] # Error message must not leak raw exception details assert "cancel scope" not in result["message"] @@ -3430,7 +3430,139 @@ class TestConnectionErrorMessage: def test_unknown_error_falls_back_to_generic(self): message = rest_endpoints._connection_error_message(RuntimeError("weird"), "https://example.com", 30.0) assert "weird" not in message - assert "proxy logs" in message.lower() + assert "reference" in message.lower() + + def test_sdk_session_terminated_explains_endpoint_and_retry(self) -> None: + from mcp.shared.exceptions import McpError + from mcp.types import ErrorData + + message: Final = rest_endpoints._connection_error_message( + McpError(ErrorData(code=32600, message="Session terminated")), "https://example.com/mcp", 30.0 + ) + + assert "session was terminated" in message + assert "MCP endpoint" in message + assert "transport" in message + assert "retry" in message + assert "404" not in message + + @pytest.mark.parametrize("code", [-32700, -32601, -32602, -32603, -32000, 32600, 408]) + def test_rpc_errors_include_code_without_echoing_upstream_data(self, code: int) -> None: + from mcp.shared.exceptions import McpError + from mcp.types import ErrorData + + message: Final = rest_endpoints._connection_error_message( + McpError(ErrorData(code=code, message="secret-message", data={"token": "secret-data"})), + "https://example.com/secret-path?token=secret-query", + 30.0, + ) + + assert f"JSON-RPC code {code}" in message + assert "secret" not in message + assert "timed out" not in message + assert "session was terminated" not in message + + @pytest.mark.parametrize("status_code", [401, 403, 404, 405, 429, 503]) + def test_wrapped_http_failures_preserve_status(self, status_code: int) -> None: + response: Final = httpx.Response(status_code, text="secret-body") + upstream: Final = httpx.HTTPStatusError( + "secret-exception", + request=httpx.Request("POST", "https://example.com/?token=secret-query"), + response=response, + ) + wrapped: Final = BaseExceptionGroup( + "secret-group", [asyncio.CancelledError(), BaseExceptionGroup("nested", [upstream])] + ) + + message: Final = rest_endpoints._connection_error_message(wrapped, "https://example.com", 30.0) + + assert f"HTTP {status_code}" in message + assert "secret" not in message + + def test_explicit_cause_is_classified_before_incidental_context(self) -> None: + wrapped: Final = RuntimeError("secret-wrapper") + wrapped.__cause__ = httpx.ConnectError("secret-cause") + wrapped.__context__ = TimeoutError("secret-context") + + message: Final = rest_endpoints._connection_error_message(wrapped, "https://example.com", 30.0) + + assert "unreachable" in message + assert "secret" not in message + + def test_timeout_url_redacts_credentials_path_query_and_fragment(self) -> None: + message: Final = rest_endpoints._connection_error_message( + TimeoutError("secret-error"), + "https://secret-user:secret-pass@example.com:8443/secret-path?token=secret-query#secret-fragment", + 30.0, + ) + + assert "https://example.com:8443" in message + assert "30s" in message + assert "secret" not in message + + def test_unknown_failure_reference_matches_safe_diagnostics(self, caplog: pytest.LogCaptureFixture) -> None: + import re + + try: + raise RuntimeError("secret-exception-body") + except RuntimeError as exc: + message: Final = rest_endpoints._connection_error_message( + exc, "https://secret-user:secret-password@example.com/secret-path?token=secret-query", 30.0 + ) + + reference: Final = re.search(r"reference ([a-f0-9]{32})", message) + assert reference is not None + diagnostics: Final = tuple( + record for record in caplog.records if "MCP connection test failed" in record.message + ) + assert len(diagnostics) == 1 + assert reference.group(1) in diagnostics[0].message + assert "RuntimeError" in diagnostics[0].message + assert "test_unknown_failure_reference_matches_safe_diagnostics" in diagnostics[0].message + assert diagnostics[0].exc_info is None + assert "secret" not in message + diagnostics[0].message + + @pytest.mark.parametrize("exc", [ValueError("secret-config"), HTTPException(500, "secret-detail")]) + def test_unrelated_errors_are_not_misreported_as_invalid_mcp(self, exc: Exception) -> None: + message: Final = rest_endpoints._connection_error_message(exc, "https://example.com", 30.0) + + assert "reference" in message + assert "invalid MCP response" not in message + assert "secret" not in message + + def test_configuration_validation_error_uses_unknown_fallback(self) -> None: + from pydantic import ValidationError + + with pytest.raises(ValidationError) as caught: + NewMCPServerRequest.model_validate({"server_name": "example", "transport": "secret-invalid-transport"}) + + message: Final = rest_endpoints._connection_error_message(caught.value, "https://example.com", 30.0) + assert "reference" in message + assert "invalid MCP response" not in message + assert "secret" not in message + + @pytest.mark.asyncio + async def test_connection_test_preserves_cancellation(self) -> None: + async def cancelled_operation(client: rest_endpoints.MCPClient) -> dict[str, object]: + raise asyncio.CancelledError + + payload: Final = NewMCPServerRequest(server_name="cancelled", url="https://example.com", auth_type=MCPAuth.none) + with pytest.raises(asyncio.CancelledError): + await rest_endpoints._execute_with_mcp_client(payload, cancelled_operation) + + @pytest.mark.asyncio + async def test_unknown_failure_preserves_response_contract(self) -> None: + async def failing_operation(client: rest_endpoints.MCPClient) -> dict[str, object]: + raise RuntimeError("secret-operation") + + payload: Final = NewMCPServerRequest(server_name="unknown", url="https://example.com", auth_type=MCPAuth.none) + result: Final = await rest_endpoints._execute_with_mcp_client(payload, failing_operation) + + assert result["error"] is True + assert result["status"] == "error" + assert "reference" in result["message"] + assert "secret" not in result["message"] + assert "stack_trace" not in result class TestGetServerAuthHeaderGroupDefault: From 7b6ef9206e87c09adfc5844c8f8130da73d794fb Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:14:47 -0700 Subject: [PATCH 078/105] test(mcp): cover server notifications during tool listing --- .../test_mcp_client.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index 49268b4d22d..90292b9b162 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -24,6 +24,7 @@ from mcp.types import ( JSONRPCError, JSONRPCMessage, JSONRPCResponse, + LoggingMessageNotificationParams, ServerCapabilities, ) @@ -1358,6 +1359,58 @@ async def test_http_response_handler_preserves_success_and_http_errors(status_co assert caught.value.response.status_code == status_code +@pytest.mark.asyncio +async def test_http_response_handler_preserves_notifications_and_tool_listing() -> None: + notification: Final = { + "jsonrpc": "2.0", + "method": "notifications/message", + "params": {"level": "info", "data": "Listing tools"}, + } + logging_callback: Final = AsyncMock() + + def respond(request: httpx.Request) -> httpx.Response: + if request.method == "DELETE": + return httpx.Response(200) + payload: Final = json.loads(request.content) + if "id" not in payload: + return httpx.Response(202) + if payload["method"] == "initialize": + return httpx.Response( + 200, + json={ + "jsonrpc": "2.0", + "id": payload["id"], + "result": { + "protocolVersion": LATEST_PROTOCOL_VERSION, + "capabilities": {"logging": {}, "tools": {}}, + "serverInfo": {"name": "test", "version": "1"}, + }, + }, + ) + response: Final = { + "jsonrpc": "2.0", + "id": payload["id"], + "result": {"tools": [{"name": "search", "inputSchema": {"type": "object"}}]}, + } + return httpx.Response( + 200, + headers={"Content-Type": "text/event-stream"}, + content="".join(f"event: message\ndata: {json.dumps(message)}\n\n" for message in (notification, response)), + ) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=30, logging_callback=logging_callback) + result: Final = await asyncio.wait_for( + client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), lambda session: session.list_tools() + ), + timeout=3, + ) + + assert [tool.name for tool in result.tools] == ["search"] + logging_callback.assert_awaited_once_with(LoggingMessageNotificationParams(level="info", data="Listing tools")) + + @pytest.mark.asyncio async def test_invalid_tool_list_schema_is_identified_as_an_upstream_response() -> None: from litellm.proxy._experimental.mcp_server.rest_endpoints import _connection_error_message From 884f90c72715b0cfe0d5e2ea09f3744cb8376a78 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 05:15:53 +0000 Subject: [PATCH 079/105] refactor(cost-map): inline background retry, trim tests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/get_model_cost_map.py | 119 ++++----- .../test_get_model_cost_map.py | 243 +++++++----------- 2 files changed, 139 insertions(+), 223 deletions(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 67d06a0758e..f81ddbfee2e 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -184,10 +184,6 @@ def mark_litellm_import_complete() -> None: _litellm_import_complete.set() -def _start_daemon_thread(fn: Callable[[], None]) -> None: - threading.Thread(target=fn, name="litellm-model-cost-map-retry", daemon=True).start() - - @dataclass(frozen=True, slots=True) class ModelCostMapReloaded: model_cost_map: dict # mutable-ok: adopted as litellm.model_cost, whose consumer contract is a plain mutable dict @@ -531,32 +527,6 @@ def _finalize_loaded_model_cost_map(loaded: ModelCostMapReloaded) -> ModelCostMa return replace(loaded, model_cost_map=_finalize_model_cost_map(loaded.model_cost_map)) -def _use_local_backup(reason: str | None) -> dict: # mutable-ok: returns the mutable model-cost map contract - _cost_map_source_info.source = "local" - _cost_map_source_info.fallback_reason = reason - return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map - - -def _accept_remote( - result: ModelCostMapReloaded, url: str -) -> dict | None: # mutable-ok: returns the mutable model-cost map contract - if not GetModelCostMap.validate_model_cost_map( - fetched_map=result.model_cost_map, - backup_model_count=GetModelCostMap._get_backup_model_count(), # pyright: ignore[reportPrivateUsage] # integrity cache - ): - verbose_logger.warning( - "LiteLLM: Fetched model cost map failed integrity check. Using local backup instead. url=%s", - url, - ) - return None - finalized: Final = _finalize_loaded_model_cost_map(result).model_cost_map - _cost_map_source_info.source = "remote" - _cost_map_source_info.url = url - _cost_map_source_info.is_env_forced = False - _cost_map_source_info.fallback_reason = None - return finalized - - def adopt_model_cost_map( new_model_cost_map: dict, # mutable-ok: public API preserves the mutable cost-map contract ) -> int: @@ -579,7 +549,6 @@ def _retry_remote_fetch_in_background( rng: random.Random, client: _SyncGetClient, first_outcome: _FetchAttemptRetryable, - apply: Callable[[dict], object], # mutable-ok: injected callback receives the mutable cost-map dict ) -> None: try: first_wait: Final = _next_retry_wait(outcome=first_outcome, attempt=1, max_attempts=max_attempts, rng=rng) @@ -602,12 +571,20 @@ def _retry_remote_fetch_in_background( ) return _litellm_import_complete.wait() - accepted: Final = _accept_remote(result, url) - if accepted is None: - _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" + if not GetModelCostMap.validate_model_cost_map( + fetched_map=result.model_cost_map, + backup_model_count=GetModelCostMap._get_backup_model_count(), # pyright: ignore[reportPrivateUsage] # integrity cache + ): + verbose_logger.warning( + "LiteLLM: Fetched model cost map failed integrity check. Using local backup instead. url=%s", + url, + ) return - apply(accepted) + finalized: Final = _finalize_loaded_model_cost_map(result).model_cost_map + _cost_map_source_info.source = "remote" + _cost_map_source_info.fallback_reason = None _cost_map_source_info.loaded_at = datetime.now(timezone.utc) + adopt_model_cost_map(finalized) except Exception as e: verbose_logger.warning("LiteLLM: Background model cost map retry failed: %s", e) @@ -619,19 +596,12 @@ def get_model_cost_map( sleep: Callable[[float], None] = time.sleep, rng: random.Random | None = None, client: "_SyncGetClient | None" = None, - start_background: Callable[[Callable[[], None]], None] = _start_daemon_thread, - apply: Callable[ # mutable-ok: injected callback receives the mutable cost-map dict - [dict], - object, - ] = adopt_model_cost_map, ) -> dict: """ Public entry point — returns the model cost map dict. 1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set, uses the local backup only. - 2. Otherwise fetches from ``url``, validates the first response, and falls - back to the local backup while retrying transient HTTP errors in the - background. + 2. Otherwise fetches from ``url``, retrying transient errors in a background thread. Only the backup model count is cached (a single int) for validation. The full backup dict is only parsed when it must be *returned* as a @@ -641,9 +611,11 @@ def get_model_cost_map( # Note: can't use get_secret_bool here — this runs during litellm.__init__ # before litellm._key_management_settings is set. if os.getenv("LITELLM_LOCAL_MODEL_COST_MAP", "").lower() == "true": + _cost_map_source_info.source = "local" _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True - return _use_local_backup(None) + _cost_map_source_info.fallback_reason = None + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map _cost_map_source_info.url = url _cost_map_source_info.is_env_forced = False @@ -651,32 +623,45 @@ def get_model_cost_map( fetch_client: Final = client if client is not None else httpx fetch_rng: Final = rng if rng is not None else random.Random() outcome: Final = _attempt_fetch_sync(client=fetch_client, url=url, timeout=timeout) - if isinstance(outcome, ModelCostMapReloaded): - accepted: Final = _accept_remote(outcome, url) - return accepted if accepted is not None else _use_local_backup("Remote data failed integrity validation") if isinstance(outcome, _FetchAttemptRetryable) and max_attempts > 1: - verbose_logger.warning( - "LiteLLM: model cost map fetch attempt 1/%d failed: %s; " - "using local backup while retrying in the background", - max_attempts, - outcome.reason, - ) - start_background( - lambda: _retry_remote_fetch_in_background( - url=url, - timeout=timeout, - max_attempts=max_attempts, - sleep=sleep, - rng=fetch_rng, - client=fetch_client, - first_outcome=outcome, - apply=apply, - ) - ) - else: + threading.Thread( + target=_retry_remote_fetch_in_background, + kwargs={ # mutable-ok: threading requires a mutable keyword-arguments mapping + "url": url, + "timeout": timeout, + "max_attempts": max_attempts, + "sleep": sleep, + "rng": fetch_rng, + "client": fetch_client, + "first_outcome": outcome, + }, + name="litellm-model-cost-map-retry", + daemon=True, + ).start() + if not isinstance(outcome, ModelCostMapReloaded): verbose_logger.warning( "LiteLLM: Failed to fetch remote model cost map from %s: %s. Falling back to local backup.", url, outcome.reason, ) - return _use_local_backup(f"Remote fetch failed: {outcome.reason}") + _cost_map_source_info.source = "local" + _cost_map_source_info.fallback_reason = f"Remote fetch failed: {outcome.reason}" + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map + content: Final = outcome.model_cost_map + + # Validate using cached count (cheap int comparison, no file I/O) + if not GetModelCostMap.validate_model_cost_map( + fetched_map=content, + backup_model_count=GetModelCostMap._get_backup_model_count(), + ): + verbose_logger.warning( + "LiteLLM: Fetched model cost map failed integrity check. Using local backup instead. url=%s", + url, + ) + _cost_map_source_info.source = "local" + _cost_map_source_info.fallback_reason = "Remote data failed integrity validation" + return _finalize_loaded_model_cost_map(GetModelCostMap.load_local_model_cost_map_with_revision()).model_cost_map + + _cost_map_source_info.source = "remote" + _cost_map_source_info.fallback_reason = None + return _finalize_loaded_model_cost_map(outcome).model_cost_map diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index e39658a8f37..266c2ca1465 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -6,6 +6,7 @@ count actual model entries, not reserved meta keys) and the extraction of the import json import os +import threading import pytest @@ -20,7 +21,6 @@ from litellm.litellm_core_utils.get_model_cost_map import ( GetModelCostMap, _count_model_entries, _finalize_model_cost_map, - adopt_model_cost_map, get_model_cost_map_provenance, git_blob_id, ) @@ -566,194 +566,125 @@ from litellm.litellm_core_utils.get_model_cost_map import ( class _SyncSleepRecorder: """Injected in place of time.sleep so the boot path's waits are asserted without delay.""" - def __init__(self): + def __init__(self, block=False): self.waits = [] + self.block = block + self.started = threading.Event() + self.release = threading.Event() def __call__(self, seconds: float) -> None: + if self.block: + self.started.set() + self.release.wait(timeout=10) self.waits.append(seconds) -class _BackgroundRecorder: - def __init__(self): - self.callbacks = [] - - def __call__(self, callback): - self.callbacks.append(callback) +def _retry_threads(): + return [thread for thread in threading.enumerate() if thread.name == "litellm-model-cost-map-retry"] -def test_boot_load_returns_local_map_and_schedules_transient_retry(): - client, calls = _mock_client( - [ - httpx.ConnectError("connection refused"), - ], - client_cls=httpx.Client, - ) - sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() - - cost_map = get_model_cost_map( - url=_URL, - sleep=sleeper, - rng=random.Random(0), - client=client, - start_background=background, - ) - assert calls["count"] == 1 - assert sleeper.waits == [] - assert len(background.callbacks) == 1 - assert len(cost_map) > 100 - source = get_model_cost_map_source_info() - assert source["source"] == "local" - assert source["fallback_reason"] is not None - - -def test_background_retry_adopts_valid_remote_map(): - client, calls = _mock_client( - [ - httpx.ConnectError("connection refused"), - httpx.Response(200, content=_real_map_bytes()), - ], - client_cls=httpx.Client, - ) - sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() - applied = [] - - get_model_cost_map( - url=_URL, - sleep=sleeper, - rng=random.Random(0), - client=client, - start_background=background, - apply=applied.append, - ) - assert calls["count"] == 1 - assert sleeper.waits == [] - assert len(background.callbacks) == 1 - - background.callbacks[0]() - - assert calls["count"] == 2 - assert len(sleeper.waits) == 1 - assert 2.0 <= sleeper.waits[0] < 3.0 - assert len(applied) == 1 - assert applied[0].keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} - source = get_model_cost_map_source_info() - assert source["source"] == "remote" - assert source["fallback_reason"] is None - - -def test_background_retry_keeps_local_map_after_remaining_failures(): - client, calls = _mock_client( - [httpx.ConnectError("connection refused"), httpx.Response(503)], - client_cls=httpx.Client, - ) - sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() - applied = [] - - get_model_cost_map( - url=_URL, - sleep=sleeper, - rng=random.Random(0), - client=client, - start_background=background, - apply=applied.append, - ) - background.callbacks[0]() - - assert calls["count"] == 3 - assert len(sleeper.waits) == 2 - assert 2.0 <= sleeper.waits[0] < 3.0 - assert 4.0 <= sleeper.waits[1] < 5.0 - assert applied == [] - assert get_model_cost_map_source_info()["source"] == "local" - - -def test_boot_load_does_not_schedule_non_retryable_failure(): - client, calls = _mock_client([httpx.Response(404)], client_cls=httpx.Client) - sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() - - get_model_cost_map( - url=_URL, - sleep=sleeper, - rng=random.Random(0), - client=client, - start_background=background, - ) - - assert calls["count"] == 1 - assert sleeper.waits == [] - assert background.callbacks == [] - source = get_model_cost_map_source_info() - assert source["source"] == "local" - assert source["fallback_reason"] is not None - - -def test_boot_load_success_does_not_schedule_background_retry(): +def test_boot_load_success_does_not_start_background_retry(): client, calls = _mock_client([httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client) sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() cost_map = get_model_cost_map( url=_URL, sleep=sleeper, rng=random.Random(0), client=client, - start_background=background, ) assert calls["count"] == 1 assert sleeper.waits == [] - assert background.callbacks == [] - assert get_model_cost_map_source_info()["source"] == "remote" + assert _retry_threads() == [] assert cost_map.keys() >= _load_root_cost_map().keys() - {"sample_spec", FALLBACK_GENERALIZATIONS_KEY} + assert get_model_cost_map_source_info()["source"] == "remote" -def test_boot_load_with_one_attempt_does_not_schedule_background_retry(): - client, calls = _mock_client([httpx.ConnectError("connection refused")], client_cls=httpx.Client) - sleeper = _SyncSleepRecorder() - background = _BackgroundRecorder() +def test_boot_load_transient_failure_returns_local_then_background_retry_adopts_remote(monkeypatch): + import litellm + from litellm import utils as litellm_utils + from litellm.litellm_core_utils import get_model_cost_map as module - get_model_cost_map( + original_model_cost = litellm.model_cost + monkeypatch.setattr(litellm, "model_cost", dict(original_model_cost)) + for name, provider_models in tuple(vars(litellm).items()): + if name.endswith("_models") and isinstance(provider_models, set): + monkeypatch.setattr(litellm, name, set(provider_models)) + monkeypatch.setattr(litellm, "models_by_provider", dict(litellm.models_by_provider)) + monkeypatch.setattr( + litellm_utils, + "_runtime_registered_model_cost", + dict(litellm_utils._runtime_registered_model_cost), + ) + source_info = module._cost_map_source_info + for name in ("source", "url", "is_env_forced", "fallback_reason", "loaded_at", "source_revision", "etag"): + monkeypatch.setattr(source_info, name, getattr(source_info, name)) + + remote_map = _load_root_cost_map() + remote_map["claude-remote-only-test"] = {"litellm_provider": "anthropic", "mode": "chat"} + client, calls = _mock_client( + [ + httpx.ConnectError("connection refused"), + httpx.Response(200, content=json.dumps(remote_map).encode()), + ], + client_cls=httpx.Client, + ) + sleeper = _SyncSleepRecorder(block=True) + litellm.register_model({"my-runtime-model": {"litellm_provider": "custom", "max_input_tokens": 4321}}) + + cost_map = get_model_cost_map( url=_URL, - max_attempts=1, + max_attempts=3, sleep=sleeper, rng=random.Random(0), client=client, - start_background=background, ) assert calls["count"] == 1 assert sleeper.waits == [] - assert background.callbacks == [] - assert get_model_cost_map_source_info()["source"] == "local" - - -def test_adopt_model_cost_map_replays_runtime_registration_and_provider_models(): - import litellm - from litellm import utils as litellm_utils - - original_model_cost = litellm.model_cost - original_registry = dict(litellm_utils._runtime_registered_model_cost) - original_anthropic_models = set(litellm.anthropic_models) + assert sleeper.started.wait(timeout=10) + threads = _retry_threads() try: - litellm.register_model( - model_cost={"custom/deployment-model": {"litellm_provider": "custom", "max_input_tokens": 4321}} - ) - - models_count = adopt_model_cost_map({"anthropic/new-model": {"litellm_provider": "anthropic", "mode": "chat"}}) - - assert models_count == 1 - assert "anthropic/new-model" in litellm.anthropic_models - assert litellm.model_cost["custom/deployment-model"]["max_input_tokens"] == 4321 + assert len(threads) == 1 + assert "claude-remote-only-test" not in cost_map + assert cost_map.keys() == _finalize_model_cost_map(GetModelCostMap.load_local_model_cost_map()).keys() + source = get_model_cost_map_source_info() + assert source["source"] == "local" + assert source["fallback_reason"].startswith("Remote fetch failed:") + sleeper.release.set() + for thread in threads: + thread.join(timeout=10) + assert all(not thread.is_alive() for thread in threads) + assert sleeper.waits and 2.0 <= sleeper.waits[0] < 3.0 + assert calls["count"] == 2 + assert "claude-remote-only-test" in litellm.model_cost + assert "claude-remote-only-test" in litellm.anthropic_models + assert "my-runtime-model" in litellm.model_cost + source = get_model_cost_map_source_info() + assert source["source"] == "remote" + assert source["fallback_reason"] is None finally: - litellm.model_cost = original_model_cost # test-quality-ok: restore the module state changed by adoption - litellm_utils._runtime_registered_model_cost.clear() - litellm_utils._runtime_registered_model_cost.update(original_registry) - litellm.anthropic_models.clear() - litellm.anthropic_models.update(original_anthropic_models) - litellm_utils._invalidate_model_cost_lowercase_map() + sleeper.release.set() + for thread in _retry_threads(): + thread.join(timeout=10) + + +def test_boot_load_does_not_retry_non_retryable_failure(): + client, calls = _mock_client([httpx.Response(404)], client_cls=httpx.Client) + sleeper = _SyncSleepRecorder() + + get_model_cost_map( + url=_URL, + sleep=sleeper, + rng=random.Random(0), + client=client, + ) + assert calls["count"] == 1 + assert sleeper.waits == [] + assert _retry_threads() == [] + source = get_model_cost_map_source_info() + assert source["source"] == "local" + assert source["fallback_reason"] is not None def test_boot_load_respects_local_env_override(monkeypatch): From 0721163cacfbd9fbfeee5dd25205cb76840111dd Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 8 Sep 2026 22:49:40 -0700 Subject: [PATCH 080/105] test(e2e/ui): cover team-scoped model visibility, re-editing litellm params, and model health checks (#40039) * test(e2e/ui): cover team-scoped model visibility, re-editing litellm params, and model health checks Three Models and Endpoints flows had no end-to-end coverage, and all three keep coming back as bug reports. modelsByTeam walks an internal user through the Current team control and asserts the table lists exactly what each team grants. It creates one deployment that belongs to no team, proves that deployment is visible under Personal, then proves it is absent under both seeded teams, so an empty table cannot pass the same assertions. editLitellmParams adds a temperature and a custom pair to a deployment, saves, then re-edits the temperature and drops the custom pair. It checks both update request bodies, polls the stored deployment until the new temperature is there, reloads the page to confirm the second save is what renders, and sends one chat completion to prove the deployment still serves. modelHealthStatus runs the health check on a reachable deployment and on one pointed at a dead port, asserts the healthy and unhealthy cells and the two detail dialogs, and reloads to confirm both statuses are stored. Every deployment these specs create carries a unique name and is deleted in afterEach, including on the failure path. * test(e2e/ui): find health rows across every page of the health table The health table pages server-side at 50 rows with no search box, so on a proxy carrying more deployments than that the two deployments the spec creates can land on a later page and the lookup finds nothing. Row lookups now walk the pages, using the table's own page indicator to know when to advance and when to wrap back to the first page. * test(e2e/ui): build the created deployment ids without mutating the array * test(ui): scope model deployments to Playwright fixtures --- .../tests/internal-user/modelsByTeam.spec.ts | 196 ++++++++++++++ .../modelsPage/editLitellmParams.spec.ts | 252 ++++++++++++++++++ .../modelsPage/modelHealthStatus.spec.ts | 245 +++++++++++++++++ 3 files changed, 693 insertions(+) create mode 100644 tests/e2e/ui/tests/internal-user/modelsByTeam.spec.ts create mode 100644 tests/e2e/ui/tests/modelsPage/editLitellmParams.spec.ts create mode 100644 tests/e2e/ui/tests/modelsPage/modelHealthStatus.spec.ts diff --git a/tests/e2e/ui/tests/internal-user/modelsByTeam.spec.ts b/tests/e2e/ui/tests/internal-user/modelsByTeam.spec.ts new file mode 100644 index 00000000000..5e2c80b5845 --- /dev/null +++ b/tests/e2e/ui/tests/internal-user/modelsByTeam.spec.ts @@ -0,0 +1,196 @@ +import { + test as base, + expect, + type Locator, + type Page as PlaywrightPage, +} from "@playwright/test"; +import { + E2E_TEAM_CRUD_ALIAS, + E2E_TEAM_ORG_ALIAS, + INTERNAL_USER_STORAGE_PATH, +} from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { navigateToPage } from "../../helpers/navigation"; +import { readBack } from "../../helpers/roundTrip"; +import { CHAT_MODEL_A, CHAT_MODEL_B, masterKey } from "../../helpers/traffic"; + +const MOCK_LLM_BASE = `http://127.0.0.1:${process.env.MOCK_LLM_PORT ?? "8090"}/v1`; +const CURRENT_TEAM_VIEW = "Current Team Models"; +const ALL_MODELS_VIEW = "All Available Models"; +const PERSONAL_TEAM = "Personal"; + +const teamSelector = (page: PlaywrightPage): Locator => + page.getByRole("combobox", { name: "Current team", exact: true }); +const viewSelector = (page: PlaywrightPage): Locator => + page.getByRole("combobox", { name: "View", exact: true }); + +async function chooseOption( + page: PlaywrightPage, + selector: Locator, + optionName: string, +): Promise { + await selector.click(); + const option = page.getByRole("option", { name: optionName, exact: true }); + await expect(option, `option ${optionName} is offered`).toBeVisible({ + timeout: 10_000, + }); + await option.click(); + await expect( + selector, + `${optionName} is the selection the control now reports`, + ).toContainText(optionName, { + timeout: 10_000, + }); +} + +async function deleteDeployment( + page: PlaywrightPage, + id: string, +): Promise { + const post = () => + page.request.post("/model/delete", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { id }, + }); + const deleted = await post().catch(() => post()); + expect( + deleted.ok(), + `cleanup: /model/delete ${id} returned ${deleted.status()}`, + ).toBe(true); +} + +function modelRow(page: PlaywrightPage, modelName: string): Locator { + return page.getByRole("row").filter({ hasText: modelName }); +} + +async function isRegistered( + page: PlaywrightPage, + modelName: string, +): Promise { + const body = await readBack<{ data: { model_name?: string }[] }>( + page, + "/v2/model/info", + ); + return body.data.some((row) => row.model_name === modelName); +} + +const uniqueSuffix = (): string => + `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + +const test = base.extend<{ ungrantedModelName: string }>({ + ungrantedModelName: async ({ page }, use) => { + const ungrantedModelName = `e2e-ungranted-${uniqueSuffix()}`; + const created = await page.request.post("/model/new", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { + model_name: ungrantedModelName, + litellm_params: { + model: `openai/${ungrantedModelName}`, + api_base: MOCK_LLM_BASE, + api_key: "fake-key", + }, + model_info: {}, + }, + }); + expect( + created.ok(), + `/model/new failed: ${created.status()} ${await created.text()}`, + ).toBe(true); + const ungrantedModelId = (await created.json()).model_info?.id; + expect(ungrantedModelId, "model id from /model/new").toBeTruthy(); + + try { + await expect + .poll(async () => await isRegistered(page, ungrantedModelName), { + message: `deployment ${ungrantedModelName} never appeared in /v2/model/info after create`, + timeout: 60_000, + }) + .toBe(true); + await use(ungrantedModelName); + } finally { + await deleteDeployment(page, ungrantedModelId); + } + }, +}); + +test.describe("Models and Endpoints for an internal user", () => { + test.use({ storageState: INTERNAL_USER_STORAGE_PATH }); + + test("shows an internal user exactly the models of the team they select", async ({ + page, + ungrantedModelName, + }) => { + await navigateToPage(page, Page.Models); + + await expect( + page.getByRole("tab", { name: "Your Models" }), + "an internal user lands on their own models tab, not an admin-only view", + ).toBeVisible({ timeout: 15_000 }); + await expect( + viewSelector(page), + "the models table opens scoped to the selected team", + ).toContainText(CURRENT_TEAM_VIEW, { timeout: 15_000 }); + await expect( + modelRow(page, ungrantedModelName), + `the personal view lists ${ungrantedModelName}, so it is on the proxy and reachable from this page`, + ).toHaveCount(1, { timeout: 30_000 }); + + await chooseOption(page, teamSelector(page), E2E_TEAM_CRUD_ALIAS); + await expect( + modelRow(page, CHAT_MODEL_A), + `${E2E_TEAM_CRUD_ALIAS} lists ${CHAT_MODEL_A}`, + ).toHaveCount(1, { + timeout: 15_000, + }); + await expect( + modelRow(page, CHAT_MODEL_B), + `${E2E_TEAM_CRUD_ALIAS} lists ${CHAT_MODEL_B}`, + ).toHaveCount(1, { + timeout: 15_000, + }); + await expect( + modelRow(page, ungrantedModelName), + `${ungrantedModelName} is on the proxy but not granted to ${E2E_TEAM_CRUD_ALIAS}, so it must not be listed`, + ).toHaveCount(0); + + await chooseOption(page, teamSelector(page), E2E_TEAM_ORG_ALIAS); + await expect( + modelRow(page, CHAT_MODEL_A), + `${E2E_TEAM_ORG_ALIAS} lists ${CHAT_MODEL_A}`, + ).toHaveCount(1, { + timeout: 15_000, + }); + await expect( + page.getByTestId("pagination-range"), + `${E2E_TEAM_ORG_ALIAS} lists the one model it grants and nothing else`, + ).toHaveText("Showing 1-1 of 1", { timeout: 15_000 }); + await expect( + modelRow(page, CHAT_MODEL_B), + `${CHAT_MODEL_B} belongs to another team and must not leak into ${E2E_TEAM_ORG_ALIAS}`, + ).toHaveCount(0); + await expect( + modelRow(page, ungrantedModelName), + `${ungrantedModelName} is granted to no team and must not leak into ${E2E_TEAM_ORG_ALIAS}`, + ).toHaveCount(0); + + await chooseOption(page, viewSelector(page), ALL_MODELS_VIEW); + await expect( + modelRow(page, CHAT_MODEL_A), + `switching to ${ALL_MODELS_VIEW} leaves the table populated rather than blanking it`, + ).toHaveCount(1, { timeout: 15_000 }); + + await page.reload(); + await expect( + teamSelector(page), + "the team selection is not persisted across a reload, so the table returns to the personal view", + ).toContainText(PERSONAL_TEAM, { timeout: 15_000 }); + await expect( + viewSelector(page), + "the view selection is not persisted across a reload either", + ).toContainText(CURRENT_TEAM_VIEW, { timeout: 15_000 }); + await expect( + modelRow(page, ungrantedModelName), + "the personal view still renders models after a reload rather than coming back empty", + ).toHaveCount(1, { timeout: 30_000 }); + }); +}); diff --git a/tests/e2e/ui/tests/modelsPage/editLitellmParams.spec.ts b/tests/e2e/ui/tests/modelsPage/editLitellmParams.spec.ts new file mode 100644 index 00000000000..4480515ae59 --- /dev/null +++ b/tests/e2e/ui/tests/modelsPage/editLitellmParams.spec.ts @@ -0,0 +1,252 @@ +import { + test as base, + expect, + type Page as PlaywrightPage, +} from "@playwright/test"; +import { ADMIN_STORAGE_PATH } from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { navigateToPage } from "../../helpers/navigation"; +import { captureRequestBody, readBack } from "../../helpers/roundTrip"; +import { masterKey, sendChatCompletion } from "../../helpers/traffic"; + +const MOCK_LLM_BASE = `http://127.0.0.1:${process.env.MOCK_LLM_PORT ?? "8090"}/v1`; +const CUSTOM_PARAM = "extra_headers"; +const CUSTOM_PARAM_VALUE = { "X-E2E-Edit-Probe": "one" }; + +type StoredParams = Record; + +async function readStoredParams( + page: PlaywrightPage, + modelId: string, +): Promise { + const body = await readBack<{ data: { litellm_params: StoredParams }[] }>( + page, + `/model/info?litellm_model_id=${modelId}`, + ); + return body.data[0]?.litellm_params ?? {}; +} + +function paramsEditor(page: PlaywrightPage) { + return page.getByPlaceholder('"rpm": 100'); +} + +async function editParams( + page: PlaywrightPage, + mutate: (params: StoredParams) => StoredParams, +): Promise { + await page.getByRole("button", { name: "Edit Settings" }).click(); + const editor = paramsEditor(page); + await expect( + editor, + "the LiteLLM Params editor is reachable on every visit to the edit form", + ).toBeVisible({ + timeout: 15_000, + }); + const shown = JSON.parse(await editor.inputValue()) as StoredParams; + await editor.fill(JSON.stringify(mutate(shown), null, 2)); +} + +async function deleteDeployment( + page: PlaywrightPage, + id: string, +): Promise { + const post = () => + page.request.post("/model/delete", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { id }, + }); + const deleted = await post().catch(() => post()); + expect( + deleted.ok(), + `cleanup: /model/delete ${id} returned ${deleted.status()}`, + ).toBe(true); +} + +const uniqueSuffix = (): string => + `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + +const test = base.extend<{ + deployment: { readonly modelName: string; readonly createdModelId: string }; +}>({ + deployment: async ({ page, request }, use) => { + const modelName = `e2e-edit-params-${uniqueSuffix()}`; + const created = await page.request.post("/model/new", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { + model_name: modelName, + litellm_params: { + model: `openai/${modelName}`, + api_base: MOCK_LLM_BASE, + api_key: "fake-key", + }, + model_info: {}, + }, + }); + expect( + created.ok(), + `/model/new failed: ${created.status()} ${await created.text()}`, + ).toBe(true); + const createdModelId = (await created.json()).model_info?.id; + expect(createdModelId, "model id from /model/new").toBeTruthy(); + + try { + await expect + .poll( + async () => { + try { + await sendChatCompletion(request, { + model: modelName, + prompt: `warmup ${modelName}`, + }); + return true; + } catch { + return false; + } + }, + { + message: `deployment ${modelName} never became routable after /model/new`, + timeout: 60_000, + }, + ) + .toBe(true); + await use({ modelName, createdModelId }); + } finally { + await deleteDeployment(page, createdModelId); + } + }, +}); + +test.describe("Edit LiteLLM Params on a deployment", () => { + test.use({ storageState: ADMIN_STORAGE_PATH }); + + test("params added on a deployment can be re-edited, and the deployment keeps serving", async ({ + page, + request, + deployment: { modelName, createdModelId }, + }) => { + await navigateToPage(page, Page.Models); + const modelIdCell = page.getByTestId(`model-id-${createdModelId}`); + await expect( + modelIdCell, + `the Models table lists ${modelName}`, + ).toBeVisible({ timeout: 15_000 }); + await modelIdCell.click(); + await expect(page.getByText("Back to Models").first()).toBeVisible({ + timeout: 15_000, + }); + + await editParams(page, (params) => ({ + ...params, + temperature: 0.2, + [CUSTOM_PARAM]: CUSTOM_PARAM_VALUE, + })); + const firstSave = await captureRequestBody( + page, + { method: "PATCH", urlIncludes: `/model/${createdModelId}/update` }, + async () => { + await page.getByRole("button", { name: "Save Changes" }).click(); + }, + ); + expect( + firstSave.litellm_params?.temperature, + "the added temperature goes on the wire", + ).toBe(0.2); + expect( + firstSave.litellm_params?.[CUSTOM_PARAM], + `the added ${CUSTOM_PARAM} goes on the wire`, + ).toEqual(CUSTOM_PARAM_VALUE); + expect( + firstSave.litellm_params?.model, + "a params edit does not rewrite the upstream model", + ).toBe(`openai/${modelName}`); + expect( + firstSave.litellm_params?.api_base, + "a params edit does not rewrite the api base", + ).toBe(MOCK_LLM_BASE); + expect( + firstSave.litellm_params, + "the credential is never re-sent, so a masked placeholder cannot overwrite the stored key", + ).not.toHaveProperty("api_key"); + + await expect + .poll( + async () => (await readStoredParams(page, createdModelId)).temperature, + { + message: "the added temperature never reached the stored deployment", + timeout: 20_000, + }, + ) + .toBe(0.2); + const afterFirstSave = await readStoredParams(page, createdModelId); + expect( + afterFirstSave[CUSTOM_PARAM], + `the added ${CUSTOM_PARAM} reached the stored deployment`, + ).toEqual(CUSTOM_PARAM_VALUE); + expect( + afterFirstSave.model, + "the stored upstream model survived the edit", + ).toBe(`openai/${modelName}`); + expect( + afterFirstSave.api_base, + "the stored api base survived the edit", + ).toBe(MOCK_LLM_BASE); + + await editParams(page, (params) => ({ + ...Object.fromEntries( + Object.entries(params).filter(([key]) => key !== CUSTOM_PARAM), + ), + temperature: 0.7, + })); + const secondSave = await captureRequestBody( + page, + { method: "PATCH", urlIncludes: `/model/${createdModelId}/update` }, + async () => { + await page.getByRole("button", { name: "Save Changes" }).click(); + }, + ); + expect( + secondSave.litellm_params?.temperature, + "a param set by an earlier save can be edited again", + ).toBe(0.7); + expect( + secondSave.litellm_params, + `dropping ${CUSTOM_PARAM} from the editor drops it from the request the UI sends`, + ).not.toHaveProperty(CUSTOM_PARAM); + expect( + secondSave.litellm_params?.model, + "a second params edit still leaves the upstream model alone", + ).toBe(`openai/${modelName}`); + expect( + secondSave.litellm_params?.api_base, + "a second params edit still leaves the api base alone", + ).toBe(MOCK_LLM_BASE); + expect( + secondSave.litellm_params, + "the credential is still never re-sent", + ).not.toHaveProperty("api_key"); + + await expect + .poll( + async () => (await readStoredParams(page, createdModelId)).temperature, + { + message: + "the re-edited temperature never reached the stored deployment", + timeout: 20_000, + }, + ) + .toBe(0.7); + + await page.reload(); + await expect( + page + .getByRole("tabpanel", { name: "Overview" }) + .getByText('"temperature": 0.7'), + "reopening the deployment renders the re-edited value, not the one from the first save", + ).toBeVisible({ timeout: 20_000 }); + + await sendChatCompletion(request, { + model: modelName, + prompt: `still serving ${modelName}`, + }); + }); +}); diff --git a/tests/e2e/ui/tests/modelsPage/modelHealthStatus.spec.ts b/tests/e2e/ui/tests/modelsPage/modelHealthStatus.spec.ts new file mode 100644 index 00000000000..247cce1b85d --- /dev/null +++ b/tests/e2e/ui/tests/modelsPage/modelHealthStatus.spec.ts @@ -0,0 +1,245 @@ +import { + test as base, + expect, + type Locator, + type Page as PlaywrightPage, +} from "@playwright/test"; +import { ADMIN_STORAGE_PATH } from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { navigateToPage } from "../../helpers/navigation"; +import { readBack } from "../../helpers/roundTrip"; +import { masterKey } from "../../helpers/traffic"; + +const MOCK_LLM_BASE = `http://127.0.0.1:${process.env.MOCK_LLM_PORT ?? "8090"}/v1`; +const UNREACHABLE_BASE = "http://127.0.0.1:9/v1"; + +async function isRegistered( + page: PlaywrightPage, + modelName: string, +): Promise { + const body = await readBack<{ data: { model_name?: string }[] }>( + page, + "/v2/model/info", + ); + return body.data.some((row) => row.model_name === modelName); +} + +function healthRow(page: PlaywrightPage, modelName: string): Locator { + return page.getByRole("row").filter({ hasText: modelName }); +} + +function pageOf(label: string): { current: number; total: number } { + const [current, total] = label + .replace("Page ", "") + .split(" of ") + .map((part) => Number(part.trim())); + return { current, total }; +} + +async function locateHealthRow( + page: PlaywrightPage, + modelName: string, +): Promise { + const pageLabel = page.getByTestId("pagination-page"); + await expect( + pageLabel, + "the health table reports which page it is showing", + ).toBeVisible({ timeout: 20_000 }); + + const deadline = Date.now() + 60_000; + while (Date.now() < deadline) { + const row = healthRow(page, modelName); + const onThisPage = await row + .first() + .waitFor({ state: "visible", timeout: 3_000 }) + .then(() => true) + .catch(() => false); + if (onThisPage) return row; + + const { current, total } = pageOf(await pageLabel.innerText()); + const goTo = current < total ? current + 1 : 1; + if (total === 1) continue; + await page + .getByRole("button", { + name: current < total ? "Go to next page" : "Go to first page", + }) + .click(); + await expect(pageLabel).toContainText(`Page ${goTo} of`, { + timeout: 15_000, + }); + } + return healthRow(page, modelName); +} + +async function openHealthTab(page: PlaywrightPage): Promise { + await page.getByRole("tab", { name: "Health Status" }).click(); + await expect( + page.getByRole("heading", { name: "Model Health Status" }), + ).toBeVisible({ timeout: 15_000 }); +} + +async function expectStatus( + page: PlaywrightPage, + modelName: string, + status: string, +): Promise { + const row = await locateHealthRow(page, modelName); + await expect(row, `${modelName} has one row in the health table`).toHaveCount( + 1, + { timeout: 20_000 }, + ); + await expect( + row.getByText(status, { exact: true }), + `the Health Status cell for ${modelName} reads ${status}`, + ).toHaveCount(1, { timeout: 60_000 }); +} + +async function deleteDeployment( + page: PlaywrightPage, + id: string, +): Promise { + const post = () => + page.request.post("/model/delete", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { id }, + }); + const deleted = await post().catch(() => post()); + expect( + deleted.ok(), + `cleanup: /model/delete ${id} returned ${deleted.status()}`, + ).toBe(true); +} + +const uniqueSuffix = (): string => + `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + +async function withDeployment( + page: PlaywrightPage, + prefix: string, + apiBase: string, + use: (name: string) => Promise, +): Promise { + const name = `${prefix}-${uniqueSuffix()}`; + const created = await page.request.post("/model/new", { + headers: { Authorization: `Bearer ${masterKey()}` }, + data: { + model_name: name, + litellm_params: { + model: `openai/${name}`, + api_base: apiBase, + api_key: "fake-key", + }, + model_info: {}, + }, + }); + expect( + created.ok(), + `/model/new for ${name} failed: ${created.status()} ${await created.text()}`, + ).toBe(true); + const id = (await created.json()).model_info?.id; + expect(id, `model id from /model/new for ${name}`).toBeTruthy(); + try { + await expect + .poll(() => isRegistered(page, name), { + message: `deployment ${name} never appeared in /v2/model/info after create`, + timeout: 60_000, + }) + .toBe(true); + await use(name); + } finally { + await deleteDeployment(page, id); + } +} + +const test = base.extend<{ reachableName: string; unreachableName: string }>({ + reachableName: async ({ page }, use) => { + await withDeployment(page, "e2e-health-up", MOCK_LLM_BASE, use); + }, + unreachableName: async ({ page }, use) => { + await withDeployment(page, "e2e-health-down", UNREACHABLE_BASE, use); + }, +}); + +test.describe("Model health status", () => { + test.use({ storageState: ADMIN_STORAGE_PATH }); + + test("Run Health Check reports a reachable deployment healthy and an unreachable one unhealthy", async ({ + page, + reachableName, + unreachableName, + }) => { + await navigateToPage(page, Page.Models); + await openHealthTab(page); + + for (const name of [reachableName, unreachableName]) { + const row = await locateHealthRow(page, name); + await expect(row, `${name} has one row in the health table`).toHaveCount( + 1, + { timeout: 20_000 }, + ); + await row + .getByRole("button", { name: "Run Health Check", exact: true }) + .click(); + } + + await expectStatus(page, reachableName, "healthy"); + await expect( + healthRow(page, reachableName).getByText("unhealthy", { exact: true }), + "a reachable deployment is never reported unhealthy", + ).toHaveCount(0); + await expectStatus(page, unreachableName, "unhealthy"); + + const successDetail = ( + await locateHealthRow(page, reachableName) + ).getByRole("button", { + name: "View response details", + }); + await expect( + successDetail, + `${reachableName} offers its health check response for inspection`, + ).toBeVisible({ timeout: 60_000 }); + await successDetail.click(); + const successDialog = page.getByRole("dialog"); + await expect( + successDialog.getByRole("heading", { + name: `Health Check Response - ${reachableName}`, + }), + "the healthy deployment's detail opens its own response dialog", + ).toBeVisible({ timeout: 10_000 }); + await successDialog.getByRole("button", { name: "Close" }).last().click(); + await expect(successDialog).toBeHidden({ timeout: 10_000 }); + + const errorDetail = ( + await locateHealthRow(page, unreachableName) + ).getByRole("button", { + name: "View full error details", + }); + await expect( + errorDetail, + `${unreachableName} offers its health check error for inspection`, + ).toBeVisible({ timeout: 60_000 }); + await errorDetail.click(); + const errorDialog = page.getByRole("dialog"); + await expect( + errorDialog.getByRole("heading", { + name: `Health Check Error - ${unreachableName}`, + }), + "the unreachable deployment's detail opens its own error dialog", + ).toBeVisible({ timeout: 10_000 }); + await expect( + errorDialog, + "the error dialog carries the upstream connection failure, not a generic message", + ).toContainText(/connection error/i, { timeout: 10_000 }); + await expect( + errorDialog, + "the error dialog names the endpoint that could not be reached", + ).toContainText(UNREACHABLE_BASE); + await errorDialog.getByRole("button", { name: "Close" }).last().click(); + await expect(errorDialog).toBeHidden({ timeout: 10_000 }); + + await page.reload(); + await openHealthTab(page); + await expectStatus(page, reachableName, "healthy"); + await expectStatus(page, unreachableName, "unhealthy"); + }); +}); From b3151073d2a8ee274fd067c1d4cfb17da0a82459 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 8 Sep 2026 22:49:54 -0700 Subject: [PATCH 081/105] test(e2e/ui): cover key budget window, non-admin model scope edit, and key blocking (#40027) * test(e2e/ui): cover key budget window, non-admin model scope edit, and key blocking Three Playwright specs for the Virtual Keys flows customers hit most, each reading its result back through /key/info and /v1/chat/completions rather than trusting the toast: - a monthly spend cap and reset window set through Edit Settings, surviving a reload, with clearing the window leaving the cap in place - a team member narrowing their own team key's models, and the proxy refusing the model they dropped - blocking a key from its detail page, then unblocking it Each test owns the key it edits and deletes it on teardown, so retries and --repeat-each never run out of fixtures. * test(e2e/ui): tighten virtual key specs from review feedback Replace the mutable suite-level key state with a Playwright fixture, so the alias and token are never reassigned and cleanup stays tied to the test. Assert /key/delete succeeded instead of discarding the response, so a failed cleanup surfaces rather than leaving rows behind. Drop the explanatory JSDoc the repo's comment policy disallows, keeping only the one line explaining why Date.now() alone is not unique enough. Type the master-key POST helper against a real guard instead of casting to Record. Assert the unblocked key is served with a 200, not just the response text, and that clearing the reset window also clears budget_reset_at. * test(ui): assert the team response through Playwright --- tests/e2e/ui/helpers/navigation.ts | 10 + tests/e2e/ui/helpers/traffic.ts | 51 ++++- .../internalUserKeyScope.spec.ts | 208 ++++++++++++++++++ .../ui/tests/proxy-admin/keyBlocking.spec.ts | 112 ++++++++++ .../tests/proxy-admin/keyBudgetWindow.spec.ts | 101 +++++++++ 5 files changed, 478 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/ui/tests/internal-user/internalUserKeyScope.spec.ts create mode 100644 tests/e2e/ui/tests/proxy-admin/keyBlocking.spec.ts create mode 100644 tests/e2e/ui/tests/proxy-admin/keyBudgetWindow.spec.ts diff --git a/tests/e2e/ui/helpers/navigation.ts b/tests/e2e/ui/helpers/navigation.ts index 4a7c4e7baa9..e0e7b4da396 100644 --- a/tests/e2e/ui/helpers/navigation.ts +++ b/tests/e2e/ui/helpers/navigation.ts @@ -73,3 +73,13 @@ export async function clickTeamId(page: PlaywrightPage, teamId: string): Promise await cell.click(); await expect(page.getByText("Back to Teams")).toBeVisible({ timeout: 10_000 }); } + +export async function openKeyDetail(page: PlaywrightPage, alias: string): Promise { + await page.getByPlaceholder("Search by key alias or ID").fill(alias); + const row = page.getByRole("row").filter({ hasText: alias }); + await expect(row, `key row "${alias}" never appeared on the Virtual Keys page`).toBeVisible({ timeout: 15_000 }); + await row.getByRole("button", { name: alias }).click(); + await expect(page.getByText("Back to Keys"), `key detail for "${alias}" never opened`).toBeVisible({ + timeout: 15_000, + }); +} diff --git a/tests/e2e/ui/helpers/traffic.ts b/tests/e2e/ui/helpers/traffic.ts index 7f8417cdffb..cb68747b364 100644 --- a/tests/e2e/ui/helpers/traffic.ts +++ b/tests/e2e/ui/helpers/traffic.ts @@ -1,4 +1,4 @@ -import { APIRequestContext, expect } from "@playwright/test"; +import { APIRequestContext, APIResponse, expect } from "@playwright/test"; /** Model names served by fixtures/config.yml, both backed by the mock LLM server. */ export const CHAT_MODEL_A = "fake-openai-gpt-4"; @@ -15,6 +15,9 @@ export const masterKey = (): string => process.env.LITELLM_MASTER_KEY || "sk-123 export const rootPath = (): string => process.env.SERVER_ROOT_PATH ?? ""; +/** Date.now() alone collides: `--repeat-each` starts its copies inside the same millisecond. */ +export const uniqueSuffix = (): string => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + interface ChatOptions { model: string; prompt: string; @@ -25,9 +28,8 @@ interface ChatOptions { traceId?: string; } -/** POST /v1/chat/completions and return the completion id (the Logs Request ID). */ -export async function sendChatCompletion(request: APIRequestContext, opts: ChatOptions): Promise { - const res = await request.post(`${rootPath()}/v1/chat/completions`, { +const postChatCompletion = (request: APIRequestContext, opts: ChatOptions): Promise => + request.post(`${rootPath()}/v1/chat/completions`, { headers: { Authorization: `Bearer ${opts.apiKey ?? masterKey()}`, "Content-Type": "application/json", @@ -39,12 +41,26 @@ export async function sendChatCompletion(request: APIRequestContext, opts: ChatO ...(opts.traceId ? { litellm_trace_id: opts.traceId } : {}), }, }); + +/** POST /v1/chat/completions and return the completion id (the Logs Request ID). */ +export async function sendChatCompletion(request: APIRequestContext, opts: ChatOptions): Promise { + const res = await postChatCompletion(request, opts); expect(res.ok(), `chat completion for ${opts.model} failed (${res.status()}): ${await res.text()}`).toBe(true); const body = await res.json(); expect(body.choices?.[0]?.message?.content).toContain(MOCK_RESPONSE_TEXT); return body.id as string; } +export interface ChatAttempt { + status: number; + body: string; +} + +export async function attemptChatCompletion(request: APIRequestContext, opts: ChatOptions): Promise { + const res = await postChatCompletion(request, opts); + return { status: res.status(), body: await res.text() }; +} + /** `key` is the sk- value to authenticate with; `token` is its hash, which spend aggregates are keyed by. */ export async function createVirtualKey( request: APIRequestContext, @@ -66,6 +82,33 @@ export async function createVirtualKey( }; } +export interface KeyInfo { + key_alias: string | null; + max_budget: number | null; + budget_duration: string | null; + budget_reset_at: string | null; + blocked: boolean | null; + models: string[]; + team_id: string | null; +} + +export async function readKeyInfo(request: APIRequestContext, token: string): Promise { + const res = await request.get(`${rootPath()}/key/info?key=${encodeURIComponent(token)}`, { + headers: { Authorization: `Bearer ${masterKey()}` }, + }); + expect(res.ok(), `GET /key/info for ${token} failed (${res.status()}): ${await res.text()}`).toBe(true); + const body = await res.json(); + return body.info as KeyInfo; +} + +export async function deleteVirtualKey(request: APIRequestContext, token: string): Promise { + const res = await request.post(`${rootPath()}/key/delete`, { + headers: { Authorization: `Bearer ${masterKey()}`, "Content-Type": "application/json" }, + data: { keys: [token] }, + }); + expect(res.ok(), `key delete for ${token} failed (${res.status()}): ${await res.text()}`).toBe(true); +} + /** Spend logs are flushed on a timer, so an assertion straight after a completion races the writer. */ export async function waitForSpendLog( request: APIRequestContext, diff --git a/tests/e2e/ui/tests/internal-user/internalUserKeyScope.spec.ts b/tests/e2e/ui/tests/internal-user/internalUserKeyScope.spec.ts new file mode 100644 index 00000000000..f923841257a --- /dev/null +++ b/tests/e2e/ui/tests/internal-user/internalUserKeyScope.spec.ts @@ -0,0 +1,208 @@ +import { test, expect, type APIRequestContext } from "@playwright/test"; +import { Page } from "../../fixtures/pages"; +import { + dismissFeedbackPopup, + navigateToPage, + openKeyDetail, +} from "../../helpers/navigation"; +import { + CHAT_MODEL_A, + CHAT_MODEL_B, + MOCK_RESPONSE_TEXT, + attemptChatCompletion, + createVirtualKey, + deleteVirtualKey, + masterKey, + readKeyInfo, + rootPath, + uniqueSuffix, +} from "../../helpers/traffic"; + +const MEMBER_PASSWORD = "E2e-Team-Member-Pass-1!"; + +interface CreatedTeam { + readonly team_id: string; +} + +function assertCreatedTeam(body: unknown): asserts body is CreatedTeam { + expect(body, "/team/new returned no team_id").toMatchObject({ + team_id: expect.any(String), + }); +} + +async function postAsMaster( + request: APIRequestContext, + path: string, + data: Record, +): Promise { + const res = await request.post(`${rootPath()}${path}`, { + headers: { + Authorization: `Bearer ${masterKey()}`, + "Content-Type": "application/json", + }, + data, + }); + expect( + res.ok(), + `POST ${path} failed (${res.status()}): ${await res.text()}`, + ).toBe(true); + return res.json(); +} + +test.describe("Internal User - own team key model scope", () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test("a team member narrows their own key's models and the proxy enforces it", async ({ + page, + request, + }) => { + const suffix = uniqueSuffix(); + const email = `team-member-${suffix}@test.local`; + const userId = `e2e-key-scope-user-${suffix}`; + const alias = `e2e-key-scope-${suffix}`; + + const team = await postAsMaster(request, "/team/new", { + team_alias: `E2E Key Scope ${suffix}`, + models: [CHAT_MODEL_A, CHAT_MODEL_B], + team_member_permissions: ["/key/generate", "/key/update", "/key/info"], + }); + assertCreatedTeam(team); + const teamId = team.team_id; + + try { + await postAsMaster(request, "/user/new", { + user_id: userId, + user_email: email, + user_role: "internal_user", + auto_create_key: false, + }); + await postAsMaster(request, "/user/update", { + user_id: userId, + password: MEMBER_PASSWORD, + }); + await postAsMaster(request, "/team/member_add", { + team_id: teamId, + member: { role: "user", user_id: userId }, + }); + + const created = await createVirtualKey(request, { + key_alias: alias, + team_id: teamId, + user_id: userId, + models: [], + }); + + try { + await page.goto("/ui/login"); + await page.getByPlaceholder("Enter your username").fill(email); + await page + .getByPlaceholder("Enter your password") + .fill(MEMBER_PASSWORD); + await page.getByRole("button", { name: "Login", exact: true }).click(); + await expect( + page.locator("a", { hasText: "Virtual Keys" }), + `${email} never reached the dashboard`, + ).toBeVisible({ timeout: 30_000 }); + await dismissFeedbackPopup(page); + + await navigateToPage(page, Page.ApiKeys); + await openKeyDetail(page, alias); + + await page.getByRole("tab", { name: "Settings" }).click(); + await page.getByRole("button", { name: "Edit Settings" }).click(); + + await page.getByRole("combobox", { name: "Select models" }).click(); + await expect( + page.getByRole("option", { name: CHAT_MODEL_A, exact: true }), + `the Models dropdown does not offer ${CHAT_MODEL_A} to a team member`, + ).toBeVisible({ timeout: 15_000 }); + await expect( + page.getByRole("option", { name: CHAT_MODEL_B, exact: true }), + `the Models dropdown does not offer ${CHAT_MODEL_B} to a team member`, + ).toBeVisible(); + + await page + .getByRole("option", { name: CHAT_MODEL_A, exact: true }) + .click(); + await page.keyboard.press("Escape"); + + const updated = page.waitForResponse( + (res) => + res.url().includes("/key/update") && + res.request().method() === "POST", + ); + await page.getByRole("button", { name: "Save Changes" }).click(); + const updateStatus = (await updated).status(); + expect( + updateStatus, + "a team member's own-key edit was refused", + ).toBeGreaterThanOrEqual(200); + expect( + updateStatus, + "a team member's own-key edit was refused", + ).toBeLessThan(300); + await expect( + page.getByText("Key updated successfully").first(), + ).toBeVisible({ timeout: 15_000 }); + + await expect + .poll( + async () => (await readKeyInfo(request, created.token)).models, + { + message: `the narrowed model scope never reached /key/info for ${alias}`, + timeout: 20_000, + }, + ) + .toEqual([CHAT_MODEL_A]); + + await expect + .poll( + async () => + await attemptChatCompletion(request, { + model: CHAT_MODEL_B, + prompt: `out of scope ${suffix}`, + apiKey: created.key, + }), + { + message: `${CHAT_MODEL_B} was still served after the key was narrowed to ${CHAT_MODEL_A}`, + timeout: 30_000, + }, + ) + .toMatchObject({ + status: 403, + body: expect.stringContaining(CHAT_MODEL_B), + }); + + const inScope = await attemptChatCompletion(request, { + model: CHAT_MODEL_A, + prompt: `in scope ${suffix}`, + apiKey: created.key, + }); + expect( + inScope, + `${CHAT_MODEL_A} is no longer served by the narrowed key`, + ).toMatchObject({ + status: 200, + body: expect.stringContaining(MOCK_RESPONSE_TEXT), + }); + } finally { + await deleteVirtualKey(request, created.token); + } + } finally { + await request.post(`${rootPath()}/user/delete`, { + headers: { + Authorization: `Bearer ${masterKey()}`, + "Content-Type": "application/json", + }, + data: { user_ids: [userId] }, + }); + await request.post(`${rootPath()}/team/delete`, { + headers: { + Authorization: `Bearer ${masterKey()}`, + "Content-Type": "application/json", + }, + data: { team_ids: [teamId] }, + }); + } + }); +}); diff --git a/tests/e2e/ui/tests/proxy-admin/keyBlocking.spec.ts b/tests/e2e/ui/tests/proxy-admin/keyBlocking.spec.ts new file mode 100644 index 00000000000..99a8065a797 --- /dev/null +++ b/tests/e2e/ui/tests/proxy-admin/keyBlocking.spec.ts @@ -0,0 +1,112 @@ +import { test as base, expect } from "@playwright/test"; +import { ADMIN_STORAGE_PATH } from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { dismissFeedbackPopup, navigateToPage, openKeyDetail } from "../../helpers/navigation"; +import { + CHAT_MODEL_A, + MOCK_RESPONSE_TEXT, + attemptChatCompletion, + createVirtualKey, + deleteVirtualKey, + readKeyInfo, + sendChatCompletion, + uniqueSuffix, +} from "../../helpers/traffic"; + +interface ScopedKey { + alias: string; + token: string; + apiKey: string; +} + +const test = base.extend<{ scopedKey: ScopedKey }>({ + scopedKey: async ({ page }, use) => { + const alias = `e2e-block-key-${uniqueSuffix()}`; + const created = await createVirtualKey(page.request, { + key_alias: alias, + models: [CHAT_MODEL_A], + }); + await use({ alias, token: created.token, apiKey: created.key }); + await deleteVirtualKey(page.request, created.token); + }, +}); + +test.describe("Proxy Admin - Key blocking", () => { + test.use({ storageState: ADMIN_STORAGE_PATH }); + + test("blocking a key stops it serving and unblocking restores it", async ({ page, scopedKey }) => { + const { alias, token, apiKey } = scopedKey; + + await sendChatCompletion(page.request, { + model: CHAT_MODEL_A, + prompt: `pre-block ${alias}`, + apiKey, + }); + + await navigateToPage(page, Page.ApiKeys); + await dismissFeedbackPopup(page); + await openKeyDetail(page, alias); + + await page.getByRole("button", { name: "More key actions" }).click(); + await page.getByRole("menuitem", { name: "Block Key" }).click(); + const blockDialog = page.getByRole("dialog", { name: "Block Key" }); + await expect(blockDialog, "the Block Key confirmation never opened").toBeVisible({ timeout: 10_000 }); + await blockDialog.getByRole("button", { name: "Block", exact: true }).click(); + + await expect + .poll(async () => (await readKeyInfo(page.request, token)).blocked, { + message: "the key never came back blocked from /key/info", + timeout: 20_000, + }) + .toBe(true); + + await expect + .poll( + async () => + await attemptChatCompletion(page.request, { + model: CHAT_MODEL_A, + prompt: "blocked", + apiKey, + }), + { + message: "a blocked key was still served by /v1/chat/completions", + timeout: 30_000, + }, + ) + .toMatchObject({ status: 401, body: expect.stringContaining("blocked") }); + + await page.reload(); + await expect( + page.getByText("Blocked", { exact: true }), + "the reloaded key detail does not show the key as blocked", + ).toBeVisible({ timeout: 15_000 }); + + await page.getByRole("button", { name: "More key actions" }).click(); + await page.getByRole("menuitem", { name: "Unblock Key" }).click(); + const unblockDialog = page.getByRole("dialog", { name: "Unblock Key" }); + await expect(unblockDialog, "the Unblock Key confirmation never opened").toBeVisible({ timeout: 10_000 }); + await unblockDialog.getByRole("button", { name: "Unblock", exact: true }).click(); + + await expect + .poll(async () => (await readKeyInfo(page.request, token)).blocked, { + message: "the key never came back unblocked from /key/info", + timeout: 20_000, + }) + .toBe(false); + + await expect + .poll( + async () => + await attemptChatCompletion(page.request, { + model: CHAT_MODEL_A, + prompt: "unblocked", + apiKey, + }), + { + message: "an unblocked key is still refused by /v1/chat/completions", + timeout: 30_000, + }, + ) + .toMatchObject({ status: 200, body: expect.stringContaining(MOCK_RESPONSE_TEXT) }); + }); +}); diff --git a/tests/e2e/ui/tests/proxy-admin/keyBudgetWindow.spec.ts b/tests/e2e/ui/tests/proxy-admin/keyBudgetWindow.spec.ts new file mode 100644 index 00000000000..4e4d0a395c3 --- /dev/null +++ b/tests/e2e/ui/tests/proxy-admin/keyBudgetWindow.spec.ts @@ -0,0 +1,101 @@ +import { test as base, expect } from "@playwright/test"; +import { ADMIN_STORAGE_PATH, E2E_TEAM_CRUD_ID } from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { dismissFeedbackPopup, navigateToPage, openKeyDetail } from "../../helpers/navigation"; +import { captureRequestBody } from "../../helpers/roundTrip"; +import { CHAT_MODEL_A, createVirtualKey, deleteVirtualKey, readKeyInfo, uniqueSuffix } from "../../helpers/traffic"; + +interface ScopedKey { + alias: string; + token: string; +} + +const test = base.extend<{ scopedKey: ScopedKey }>({ + scopedKey: async ({ page }, use) => { + const alias = `e2e-budget-window-${uniqueSuffix()}`; + const created = await createVirtualKey(page.request, { + key_alias: alias, + team_id: E2E_TEAM_CRUD_ID, + models: [CHAT_MODEL_A], + }); + await use({ alias, token: created.token }); + await deleteVirtualKey(page.request, created.token); + }, +}); + +test.describe("Proxy Admin - Key budget window", () => { + test.use({ storageState: ADMIN_STORAGE_PATH }); + + test("a monthly spend cap survives a reload, and clearing the window keeps the cap", async ({ page, scopedKey }) => { + const { alias, token } = scopedKey; + + const before = await readKeyInfo(page.request, token); + expect(before.max_budget, "a freshly generated key starts with no budget").toBeNull(); + + await navigateToPage(page, Page.ApiKeys); + await dismissFeedbackPopup(page); + await openKeyDetail(page, alias); + + await page.getByRole("tab", { name: "Settings" }).click(); + await page.getByRole("button", { name: "Edit Settings" }).click(); + + await page.getByRole("spinbutton", { name: "Max Budget (USD)" }).fill("12.5"); + await page.getByLabel("Reset Budget", { exact: true }).click(); + await page.getByRole("option", { name: "monthly", exact: true }).click(); + await page.getByRole("button", { name: "Save Changes" }).click(); + + await expect + .poll(async () => (await readKeyInfo(page.request, token)).max_budget, { + message: "the $12.50 cap never reached /key/info", + timeout: 20_000, + }) + .toBe(12.5); + await expect + .poll(async () => (await readKeyInfo(page.request, token)).budget_duration, { + message: "the monthly reset window never reached /key/info", + timeout: 20_000, + }) + .toBe("30d"); + + const capped = await readKeyInfo(page.request, token); + const resetAt = new Date(capped.budget_reset_at ?? ""); + expect(Number.isNaN(resetAt.getTime()), "a monthly window left the key with no budget_reset_at").toBe(false); + expect(resetAt.getTime(), "budget_reset_at was set in the past").toBeGreaterThan(Date.now()); + expect(resetAt.getUTCDate(), "a monthly window resets on the 1st, a daily one would not").toBe(1); + + await page.reload(); + await expect( + page.getByRole("paragraph").filter({ hasText: "of $12.50" }), + "the reloaded key detail does not render the $12.50 cap", + ).toBeVisible({ timeout: 15_000 }); + + await page.getByRole("tab", { name: "Settings" }).click(); + await expect( + page.getByTestId("budget-reset-value"), + "the reloaded key detail does not name the 30d reset window", + ).toHaveText(/Every 30d/, { timeout: 15_000 }); + + await page.getByRole("button", { name: "Edit Settings" }).click(); + await page.getByLabel("Reset Budget", { exact: true }).click(); + await page.getByRole("option", { name: "Never resets", exact: true }).click(); + + const cleared = await captureRequestBody(page, { method: "POST", urlIncludes: "/key/update" }, async () => { + await page.getByRole("button", { name: "Save Changes" }).click(); + }); + expect(cleared).toHaveProperty("budget_duration"); + expect(cleared.budget_duration, "clearing the window must send budget_duration: null explicitly").toBeNull(); + + await expect + .poll(async () => (await readKeyInfo(page.request, token)).budget_duration, { + message: "the reset window was never cleared on /key/info", + timeout: 20_000, + }) + .toBeNull(); + + const after = await readKeyInfo(page.request, token); + expect(after.budget_reset_at, "clearing the reset window left a stale next-reset timestamp").toBeNull(); + expect(after.max_budget, "clearing the reset window also wiped the spend cap").toBe(12.5); + expect(after.models, "editing the budget left the key's models untouched").toEqual(before.models); + expect(after.team_id, "editing the budget left the key's team untouched").toEqual(before.team_id); + }); +}); From 36bd7f113837caadd3f3d400dd246cd4649b7b33 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 8 Sep 2026 22:50:13 -0700 Subject: [PATCH 082/105] fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e (#40022) * fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e PUT /v1/mcp/toolset dumped its payload with exclude_none, so a field sent as null looked exactly like one the caller left out and the stored value survived. An admin could not clear a toolset's description: the save reported success and the old text came straight back. It now dumps with exclude_unset, so absent keeps and null clears, which is what PUT /v1/mcp/server already did. A null tools list clears the selection to empty, and a null toolset_name is ignored because a toolset always has a name. Adds create, read, partial-update, clear and delete e2e coverage for MCP servers and toolsets, with every read-back polled on every replica so an edit that lands on one replica and not another fails the test, plus an enforcement test proving a key granted a toolset lists exactly that toolset's tools against the real Datadog upstream. * fix(e2e): refuse a read-back that no replica serves A read-back over an empty replica mapping satisfied every predicate and returned as if it had converged, so it would have asserted nothing and passed. No wiring can produce that today, since the replica list always falls back to at least one URL, but a helper whose whole job is proving a write reached every replica should not have a shape that passes vacuously. * fix(mcp): keep a null tools list a no-op on toolset update Treating a null tools list as a clear meant an existing client that sends tools=null during a partial update, meaning "leave the selection alone", silently lost every tool the toolset grants. That is a permission surface, so the quiet version of it is the worst version. A toolset always has a tool list, the same way it always has a name, so a null on either is now a no-op. Emptying the selection is an explicit [], which cannot be confused with a field the caller left out, and which is what the dashboard already sends. * fix(e2e): keep MCP admin routes on the data plane /v1/mcp/* is a lazily mounted feature, so a gateway registers it on the first matching request, which happens after the startup route trim that drops management endpoints. Routing it to the control plane therefore sent every MCP call to the one backend process: the new lifecycle read-backs proved a single process rather than every replica, and mcp_client's await_registered barrier waited on a registry that does not serve the tools/list call it guards, so the existing MCP suites polled a gateway that had not synced yet until poll_timeout Verified against a two-gateway split stack (backend on 4001, gateways on 4010 and 4011, one postgres): both gateways answer /v1/mcp/server and /v1/mcp/toolset, and each served 6 server reads and 7 toolset reads over the run * fix(e2e): grant the toolset by the tool's own name, not the wire name tools/list serves a tool as , but a toolset grants by the tool's own name: resolve_toolset_permissions reads toolset.tools[].tool_name straight through, and the prefix is added on the way out. The test built the toolset from the names tools/list reported, so the grant matched nothing, the scoped key listed no tools, and await_tools ran out its whole poll_timeout before failing Measure the prefix off search_datadog_logs, whose own name is known, rather than guessing it from the alias, since the proxy can be configured to prefix with a short server id instead. The expectation compared against tools/list stays in wire names; only what the toolset stores crosses back * test(mcp): build immutable lifecycle updates and replica results * test: validate opaque stream IDs and hide log-reader credentials * test: isolate auto-router scenarios and clean partial setup * test: honor Datadog search rate-limit reset headers * test: share the Datadog read-back deadline across retries * test: preserve captured MCP toolset update fields --- .../_experimental/mcp_server/toolset_db.py | 16 +- .../mcp_management_endpoints.py | 4 + tests/e2e/coverage_registry/mcp.yaml | 8 + tests/e2e/coverage_registry/mgmt.yaml | 10 + tests/e2e/e2e_http.py | 64 +++- .../test_responses_bridge_streaming_e2e.py | 24 +- tests/e2e/logging/datadog_reader.py | 124 +++++--- tests/e2e/logging/test_datadog_reader.py | 223 +++++++++++++ tests/e2e/management/management_client.py | 35 +++ .../e2e/management/test_mcp_lifecycle_e2e.py | 294 ++++++++++++++++++ tests/e2e/mcp/datadog_mcp.py | 7 +- tests/e2e/mcp/mcp_client.py | 39 ++- .../mcp/test_mcp_toolset_enforcement_e2e.py | 95 ++++++ tests/e2e/models.py | 73 ++++- tests/e2e/proxy_client.py | 216 ++++++++++++- .../test_auto_router_regressions_e2e.py | 239 +++++++------- tests/e2e/test_e2e_http.py | 91 +++++- tests/e2e/test_proxy_client.py | 95 +++++- .../mcp_server/test_mcp_partial_update.py | 71 ++++- 19 files changed, 1501 insertions(+), 227 deletions(-) create mode 100644 tests/e2e/logging/test_datadog_reader.py create mode 100644 tests/e2e/management/test_mcp_lifecycle_e2e.py create mode 100644 tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py diff --git a/litellm/proxy/_experimental/mcp_server/toolset_db.py b/litellm/proxy/_experimental/mcp_server/toolset_db.py index ecaaf35e817..48bad178927 100644 --- a/litellm/proxy/_experimental/mcp_server/toolset_db.py +++ b/litellm/proxy/_experimental/mcp_server/toolset_db.py @@ -132,10 +132,18 @@ async def update_mcp_toolset( data: UpdateMCPToolsetRequest, touched_by: str, ) -> MCPToolset | None: - data_dict: Final = data.model_dump(exclude_none=True, exclude={"toolset_id"}) - if "tools" in data_dict: - data_dict["tools"] = json.dumps(data_dict["tools"]) - data_dict["updated_by"] = touched_by + """A partial update: absent keeps, null clears. A toolset always has a name and a + tool list, so a null ``toolset_name`` or ``tools`` is a no-op rather than a clear; + emptying the tool selection is an explicit ``[]``, which cannot be mistaken for a + caller that left the field out.""" + data_dict: Final = dict( # mutable-ok: Prisma requires a plain dict for JSON query serialization + ( + (field, json.dumps(value) if field == "tools" else value) + for field, value in data.model_dump(exclude_unset=True).items() + if field != "toolset_id" and (field not in ("toolset_name", "tools") or value is not None) + ), + updated_by=touched_by, + ) try: row: Final = await _toolset_table(prisma_client).update( where={"toolset_id": data.toolset_id}, diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index d5c3427f29a..2aa7fdc7393 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -2673,6 +2673,8 @@ if MCP_AVAILABLE: """ Updates the MCP Server in the db. + Partial update: a field left out of the payload keeps its stored value, and a field sent as null is cleared. + Parameters: - payload: UpdateMCPServerRequest - Required. The updated mcp server data. ``` @@ -3098,6 +3100,8 @@ if MCP_AVAILABLE: user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), litellm_changed_by: str | None = Header(None), ): + """Partial update: a field left out keeps its stored value, and a field sent as null is cleared, except + ``toolset_name`` and ``tools``, which a toolset always has; empty the tool selection with an explicit [].""" prisma_client: Final = get_prisma_client_or_throw("Database not connected. Connect a database to your proxy") if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: raise HTTPException( diff --git a/tests/e2e/coverage_registry/mcp.yaml b/tests/e2e/coverage_registry/mcp.yaml index ab644118a47..f853e9ff8d6 100644 --- a/tests/e2e/coverage_registry/mcp.yaml +++ b/tests/e2e/coverage_registry/mcp.yaml @@ -119,3 +119,11 @@ assertions: [succeeds] source: "server.py:1089" rationale: Smoke; rarely used; same auth model as tools +- id: mcp.list_tools.api_key.toolset_scoped + module: mcp + tier: P0 + operation: list_tools + auth_family: api_key + assertions: [toolset_scoped] + source: "user_api_key_auth_mcp.py:2137" + rationale: "A key granted a toolset lists exactly the toolset's tools: the rest of the server's catalog stays hidden and every stored name resolves" diff --git a/tests/e2e/coverage_registry/mgmt.yaml b/tests/e2e/coverage_registry/mgmt.yaml index 860d96a50b4..c8d7037d2fd 100644 --- a/tests/e2e/coverage_registry/mgmt.yaml +++ b/tests/e2e/coverage_registry/mgmt.yaml @@ -76,3 +76,13 @@ - {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)"} - {id: mgmt.credential.new.serves_request, module: mgmt, tier: P1, surface: api, assertions: [serves_request], source: "credential_endpoints/endpoints.py:42", rationale: "Stored credential resolves into a deployment and serves a live /messages request"} - {id: mgmt.model.test_connection.happy_path, module: mgmt, tier: P0, surface: api, assertions: [happy_path], source: "_health_endpoints.py:1785", rationale: "Test Connection for a responses-mode Bedrock Mantle deployment reaches the live provider and reports success; this exact shape 500ed on an acompletion partial before v1.91.0", fail_before_fix: proven} +- {id: mgmt.mcp_server.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1577", rationale: "Every field of an admin-created MCP server reads back verbatim, by id and in the list, on every replica"} +- {id: mgmt.mcp_server.list.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1112", rationale: "The MCP page grid lists a created server with the same field values its detail view reports"} +- {id: mgmt.mcp_server.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "mcp_management_endpoints.py:2665", rationale: "A dashboard edit of one field leaves the others intact and is visible on every replica after one save; edits that took several saves to stick were a customer defect"} +- {id: mgmt.mcp_server.update.clear_persists, module: mgmt, tier: P0, surface: api, assertions: [clear_persists], source: "mcp_management_endpoints.py:2665", rationale: "An explicit null clears the stored field (absent keeps, null clears)"} +- {id: mgmt.mcp_server.delete.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:2139", rationale: "A deleted server is gone by id and from the list on every replica"} +- {id: mgmt.mcp_toolset.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:3009", rationale: "Toolset tools read back under the exact server_id and tool_name written; a toolset stored under one name and read under another granted nothing"} +- {id: mgmt.mcp_toolset.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "mcp_management_endpoints.py:3098", rationale: "Editing the description leaves the tools and name intact"} +- {id: mgmt.mcp_toolset.update.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:3098", rationale: "Narrowing the tools to one entry reads back exactly that entry"} +- {id: mgmt.mcp_toolset.update.clear_persists, module: mgmt, tier: P0, surface: api, assertions: [clear_persists], source: "mcp_management_endpoints.py:3098", fail_before_fix: proven, rationale: "An explicit null clears the stored description; the update used to drop null and keep the old value"} +- {id: mgmt.mcp_toolset.delete.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:3149", rationale: "A deleted toolset is gone by id and from the list on every replica"} diff --git a/tests/e2e/e2e_http.py b/tests/e2e/e2e_http.py index 9d5f1658e91..415c72bbb3c 100644 --- a/tests/e2e/e2e_http.py +++ b/tests/e2e/e2e_http.py @@ -49,6 +49,11 @@ class AnthropicHeaders(AuthHeaders): anthropic_version: str = Field(default="2023-06-01", alias="anthropic-version") +class PartialBody(BaseModel): + """A body for a partial-update route (absent = keep, null = clear): a field left + unset is omitted from the wire, and a field set to None is sent as JSON null.""" + + class NoBody(BaseModel): """Empty body/query for routes that take none.""" @@ -252,6 +257,13 @@ def assert_auth_denied(result: StreamingResponse, context: str) -> None: f"{context}: expected 401/403, got {result.status_code}: {result.body[:300]}" ) + +def wire_body(json: BaseModel) -> dict[str, object]: + if isinstance(json, PartialBody): + return json.model_dump(by_alias=True, exclude_unset=True) + return json.model_dump(by_alias=True, exclude_none=True) + + def _headers(headers: BaseModel) -> dict[str, str]: dumped: dict[str, object] = headers.model_dump(by_alias=True, exclude_none=True) return {key: str(value) for key, value in dumped.items()} @@ -307,9 +319,26 @@ def request_with_retry[T: RetryableResponse]( return issue() -def _classify[R: BaseModel]( - resp: requests.Response, response_type: type[R] -) -> Result[R]: +class ClassifiableResponse(Protocol): + """What classifying an outcome reads off a response. requests.Response satisfies + it, and so does a fake, so the classification rules are testable on their own.""" + + @property + def status_code(self) -> int: ... + + @property + def ok(self) -> bool: ... + + @property + def text(self) -> str: ... + + @property + def content(self) -> bytes: ... + + def json(self) -> object: ... + + +def classify[R: BaseModel](resp: ClassifiableResponse, response_type: type[R]) -> Result[R]: if resp.status_code == 401: return UnauthorizedError(body=resp.text) if resp.status_code == 429: @@ -317,7 +346,8 @@ def _classify[R: BaseModel]( if not resp.ok: return UnknownApiError(status_code=resp.status_code, body=resp.text) try: - return Success(status_code=resp.status_code, data=response_type.model_validate(resp.json())) + payload: Final[object] = resp.json() if resp.content else {} + return Success(status_code=resp.status_code, data=response_type.model_validate(payload)) except Exception as exc: # noqa: BLE001 - any parse/validation failure is a value return ValidationError(message=str(exc)) @@ -335,13 +365,13 @@ def post[R: BaseModel]( lambda: requests.post( str(url), headers=_headers(headers), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), timeout=timeout, ) ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def get[R: BaseModel]( @@ -363,7 +393,7 @@ def get[R: BaseModel]( ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def get_external[R: BaseModel]( @@ -383,7 +413,7 @@ def get_external[R: BaseModel]( ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def delete[R: BaseModel]( @@ -400,14 +430,14 @@ def delete[R: BaseModel]( lambda: requests.delete( str(url), headers=_headers(headers), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), params=_params(params), timeout=timeout, ) ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def patch[R: BaseModel]( @@ -423,13 +453,13 @@ def patch[R: BaseModel]( lambda: requests.patch( str(url), headers=_headers(headers), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), timeout=timeout, ) ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def put[R: BaseModel]( @@ -445,13 +475,13 @@ def put[R: BaseModel]( lambda: requests.put( str(url), headers=_headers(headers), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), timeout=timeout, ) ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def probe( @@ -555,7 +585,7 @@ def send( str(url), headers=_headers(headers), params=_params(params), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), stream=stream, timeout=timeout, ) @@ -605,7 +635,7 @@ def upload[R: BaseModel]( ) except requests.RequestException as exc: return NetworkError(message=str(exc)) - return _classify(resp, response_type) + return classify(resp, response_type) def stream_binary( @@ -623,7 +653,7 @@ def stream_binary( resp = requests.post( str(url), headers=_headers(headers), - json=json.model_dump(by_alias=True, exclude_none=True), + json=wire_body(json), stream=True, timeout=timeout, ) diff --git a/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py index 9a45743a0cd..75817340876 100644 --- a/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py +++ b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py @@ -16,8 +16,10 @@ into a chat completion chunk. Two customer-visible contracts only hold on that p from __future__ import annotations +from typing import Final, Literal + import pytest -from pydantic import BaseModel +from pydantic import BaseModel, Field from e2e_config import unique_marker from e2e_http import StreamingResponse @@ -51,7 +53,8 @@ class _BridgeChoice(BaseModel): class _BridgeChunk(BaseModel): id: str - choices: list[_BridgeChoice] = [] + object: Literal["chat.completion.chunk"] + choices: list[_BridgeChoice] = Field(default_factory=list) class _WeatherArgs(BaseModel): @@ -103,16 +106,19 @@ class TestResponsesBridgeChatCompletionsStreaming: resources.key(), ChatBody( model=bridged_model, - messages=[ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}")], + messages=[ + ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}") + ], max_tokens=64, stream=True, ), ) - chunks = _bridge_chunks(result) - ids = {chunk.id for chunk in chunks} + chunks: Final = _bridge_chunks(result) + assert len(chunks) > 1, "the shared-id contract needs more than one streamed chunk" + ids: Final = frozenset(chunk.id for chunk in chunks) assert len(ids) == 1, f"bridged stream used {len(ids)} different chunk ids: {sorted(ids)[:5]}" - assert ids.pop().startswith("chatcmpl-"), f"bridged chunk id is not chat-completion shaped: {chunks[0].id}" + assert chunks[0].id.strip(), "bridged stream emitted an empty chunk id" @pytest.mark.covers( "llm.chat_completions.openai.basic.stream.bridge_streams_sse", @@ -134,9 +140,9 @@ class TestResponsesBridgeChatCompletionsStreaming: chunks = _bridge_chunks(result) content = "".join(choice.delta.content or "" for chunk in chunks for choice in chunk.choices) assert content.strip(), f"bridged stream completed with no content deltas: {result.stream_events[:3]}" - assert any( - choice.finish_reason for chunk in chunks for choice in chunk.choices - ), f"bridged stream never emitted a finish_reason: {result.stream_events[-3:]}" + assert any(choice.finish_reason for chunk in chunks for choice in chunk.choices), ( + f"bridged stream never emitted a finish_reason: {result.stream_events[-3:]}" + ) assert result.stream_done, f"bridged stream did not terminate with [DONE]: {result.stream_events[-2:]}" @pytest.mark.covers( diff --git a/tests/e2e/logging/datadog_reader.py b/tests/e2e/logging/datadog_reader.py index d0f478185c2..368c20cb6aa 100644 --- a/tests/e2e/logging/datadog_reader.py +++ b/tests/e2e/logging/datadog_reader.py @@ -12,8 +12,12 @@ empty result. External reads go through ``e2e_http``. from __future__ import annotations +import math +import random import time -from dataclasses import dataclass +from collections.abc import Callable, Mapping +from dataclasses import dataclass, field +from typing import Final import pytest from pydantic import BaseModel, ConfigDict, Field @@ -27,17 +31,33 @@ from e2e_config import ( DD_SITE, POLL_TIMEOUT, ) -from e2e_http import URL, Headers, RateLimitedError, Success, post +from e2e_http import URL, Headers, StreamingResponse, send -#: How many rate-limited responses in a row one search tolerates before the -#: hard fail; each retry sleeps a full search interval, so this rides out a -#: burst from a concurrent consumer of the org-wide search budget. -_RATE_LIMIT_RETRIES = 5 +type SearchCall = Callable[[str, float], StreamingResponse] + + +def _seconds(value: str | None) -> float | None: + if value is None: + return None + try: + seconds: Final = float(value) + except ValueError: + return None + return seconds if math.isfinite(seconds) and seconds >= 0 else None + + +def _rate_limit_delay(headers: Mapping[str, str]) -> float: + delays: Final = tuple( + delay + for name in ("x-ratelimit-reset", "retry-after") + if (delay := _seconds(headers.get(name))) is not None + ) + return max(1.0, max(delays, default=DD_SEARCH_INTERVAL)) class _DdAuthHeaders(Headers): - api_key: str = Field(serialization_alias="DD-API-KEY") - app_key: str = Field(serialization_alias="DD-APPLICATION-KEY") + api_key: str = Field(serialization_alias="DD-API-KEY", repr=False) + app_key: str = Field(serialization_alias="DD-APPLICATION-KEY", repr=False) class _SearchFilter(BaseModel): @@ -88,8 +108,12 @@ class _SearchResponse(BaseModel): @dataclass(frozen=True, slots=True) class DdLogsReader: site: str - api_key: str - app_key: str + api_key: str = field(repr=False) + app_key: str = field(repr=False) + search: SearchCall | None = field(default=None, repr=False) + now: Callable[[], float] = field(default=time.monotonic, repr=False) + sleep: Callable[[float], None] = field(default=time.sleep, repr=False) + jitter: Callable[[], float] = field(default=random.random, repr=False) def events_for_marker(self, marker: str) -> list[DdLogEvent]: """Every ingested event whose attributes carry the marker. DataDog @@ -108,25 +132,28 @@ class DdLogsReader: a single event. A 429 backs off and retries - the search budget is org-wide, so another consumer can empty it under us - while any other failure stays a hard fail.""" - for _ in range(_RATE_LIMIT_RETRIES): - result = post( - URL(f"https://api.{self.site}/api/v2/logs/events/search"), - headers=_DdAuthHeaders(api_key=self.api_key, app_key=self.app_key), - json=_SearchRequest(filter=_SearchFilter(query=query)), - response_type=_SearchResponse, - timeout=30.0, - ) - match result: - case Success(data=page): - return [event.attributes for event in page.data] - case RateLimitedError(retry_after_seconds=retry_after): - time.sleep(retry_after if retry_after else DD_SEARCH_INTERVAL) - case failure: - pytest.fail(f"DataDog Logs Search API at api.{self.site} failed: {failure}") + return self._events_for_query(query, self.now() + POLL_TIMEOUT) + + def _events_for_query(self, query: str, deadline: float) -> list[DdLogEvent]: + search: Final = self.search or self._search_page + while (remaining := deadline - self.now()) > 0: + if (result := search(query, min(30.0, remaining))).ok: + return [event.attributes for event in _SearchResponse.model_validate_json(result.body).data] + if result.status_code != 429: + pytest.fail(f"DataDog Logs Search API at api.{self.site} failed with HTTP {result.status_code}") + if (delay := min(_rate_limit_delay(result.headers) + self.jitter(), deadline - self.now())) > 0: + self.sleep(delay) pytest.fail( - f"DataDog Logs Search API at api.{self.site} still rate-limited after " - f"{_RATE_LIMIT_RETRIES} retries {DD_SEARCH_INTERVAL}s apart - the org-wide " - "logs_public_search_api budget (2 requests per 10s) is exhausted by another consumer" + f"DataDog Logs Search API at api.{self.site} remained rate-limited for {POLL_TIMEOUT}s; " + "the org-wide logs_public_search_api budget is exhausted" + ) + + def _search_page(self, query: str, timeout: float) -> StreamingResponse: + return send( + URL(f"https://api.{self.site}/api/v2/logs/events/search"), + headers=_DdAuthHeaders(api_key=self.api_key, app_key=self.app_key), + json=_SearchRequest(filter=_SearchFilter(query=query)), + timeout=timeout, ) def poll_events_for_marker(self, marker: str) -> list[DdLogEvent]: @@ -140,33 +167,42 @@ class DdLogsReader: hide from the exactly-one assertion - real-DataDog jitter can surface one call's two events tens of seconds apart. Searches pace at DD_SEARCH_INTERVAL, not POLL_INTERVAL, to respect the search API's - request budget. At the deadline the last result is returned as-is.""" - deadline = time.monotonic() + POLL_TIMEOUT - while time.monotonic() < deadline: - events = self.events_for_query(query) + request budget. Discovery, quota retries, and duplicate detection share + one POLL_TIMEOUT deadline; an incomplete settle window fails closed.""" + deadline: Final = self.now() + POLL_TIMEOUT + while (remaining := deadline - self.now()) > 0: + events = self._events_for_query(query, deadline) if events: - return self._settled_events_for_query(query, events) - time.sleep(DD_SEARCH_INTERVAL) - return self.events_for_query(query) + return self._settled_events_for_query(query, events, deadline) + if (remaining := deadline - self.now()) > 0: + self.sleep(min(DD_SEARCH_INTERVAL, remaining)) + return [] - def _settled_events_for_query(self, query: str, events: list[DdLogEvent]) -> list[DdLogEvent]: + def _settled_events_for_query(self, query: str, events: list[DdLogEvent], deadline: float) -> list[DdLogEvent]: """Re-read at every search interval until the settle window closes; a duplicate ends the watch early because more waiting cannot clear it. Keep the last non-empty result: a transient empty search (index lag) must not erase events already confirmed earlier in the settle window. + A successful final search must reach the full settle window before the + shared read-back deadline; otherwise duplicate detection is incomplete. """ - settle_deadline = time.monotonic() + DD_SETTLE_SECONDS + settle_deadline: Final = self.now() + DD_SETTLE_SECONDS last_nonempty = events - while time.monotonic() < settle_deadline: - time.sleep(DD_SEARCH_INTERVAL) - latest = self.events_for_query(query) - if not latest: - continue + if len(events) > 1: + return events + while (remaining := deadline - self.now()) > 0: + self.sleep(min(DD_SEARCH_INTERVAL, remaining)) + if self.now() >= deadline: + break + latest = self._events_for_query(query, deadline) if len(latest) > 1: return latest - last_nonempty = latest - return last_nonempty + if latest: + last_nonempty = latest + if self.now() >= settle_deadline: + return last_nonempty + pytest.fail(f"DataDog log delivery could not complete its duplicate-detection window within {POLL_TIMEOUT}s") def build_dd_logs_reader() -> DdLogsReader: diff --git a/tests/e2e/logging/test_datadog_reader.py b/tests/e2e/logging/test_datadog_reader.py new file mode 100644 index 00000000000..910a1cefd42 --- /dev/null +++ b/tests/e2e/logging/test_datadog_reader.py @@ -0,0 +1,223 @@ +import json +from collections.abc import Iterator, Sequence +from dataclasses import dataclass +from typing import Final + +import pytest + +from datadog_reader import DdLogsReader +from datadog_reader import _DdAuthHeaders # pyright: ignore[reportPrivateUsage] # verifies private auth-header serialization +from e2e_config import DD_SEARCH_INTERVAL, POLL_TIMEOUT +from e2e_http import StreamingResponse + + +def test_failure_diagnostics_hide_credentials_without_changing_auth_headers() -> None: + api_key: Final = "test-datadog-api-secret" + app_key: Final = "test-datadog-app-secret" + reader: Final = DdLogsReader(site="datadoghq.com", api_key=api_key, app_key=app_key) + headers: Final = _DdAuthHeaders(api_key=api_key, app_key=app_key) + + for value in (reader, headers): + assert api_key not in repr(value) + assert app_key not in repr(value) + + assert headers.model_dump(by_alias=True) == { + "DD-API-KEY": api_key, + "DD-APPLICATION-KEY": app_key, + } + + +@dataclass +class Clock: + elapsed: float = 0.0 + + def now(self) -> float: + return self.elapsed + + def sleep(self, seconds: float) -> None: + self.elapsed += seconds + + +@dataclass +class Search: + responses: Iterator[StreamingResponse] + calls: tuple[tuple[str, float], ...] = () + + def __call__(self, query: str, timeout: float) -> StreamingResponse: + self.calls += ((query, timeout),) + return next(self.responses) + + +def _page(*event_ids: str) -> StreamingResponse: + return StreamingResponse( + status_code=200, + body=json.dumps({"data": [{"attributes": {"attributes": {"id": event_id}}} for event_id in event_ids]}), + ) + + +def _reader(responses: Sequence[StreamingResponse], clock: Clock) -> tuple[DdLogsReader, Search]: + search: Final = Search(iter(responses)) + return DdLogsReader( + site="us5.datadoghq.com", + api_key="test-api-secret", + app_key="test-app-secret", + search=search, + now=clock.now, + sleep=clock.sleep, + jitter=lambda: 0.25, + ), search + + +def test_429_honors_server_reset_and_preserves_duplicate_events() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "6"}), _page("first", "duplicate")), + clock, + ) + + events: Final = reader.events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first", "duplicate") + assert clock.elapsed == 6.25 + assert search.calls == (("test-marker", 30.0), ("test-marker", 30.0)) + + +@pytest.mark.parametrize("reset", ("", "invalid", "nan", "inf", "-1")) +def test_invalid_reset_uses_search_interval(reset: str) -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": reset}), _page()), clock + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == DD_SEARCH_INTERVAL + 0.25 + + +def test_zero_reset_cannot_create_a_busy_retry_loop() -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "0"}), _page()), clock + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == 1.25 + + +def test_retry_after_is_not_shortened_by_an_earlier_reset() -> None: + clock: Final = Clock() + reader, _ = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": "2", "retry-after": "8"}), _page()), + clock, + ) + + assert reader.events_for_query("test-marker") == [] + assert clock.elapsed == 8.25 + + +def test_rate_limit_wait_stops_at_deadline_without_issuing_another_request() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT * 10)}),), clock + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert search.calls == (("test-marker", 30.0),) + + +def test_late_retry_cannot_receive_a_fresh_request_timeout() -> None: + clock: Final = Clock() + reader, search = _reader( + (StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT - 5)}), _page()), + clock, + ) + + assert reader.events_for_query("test-marker") == [] + assert search.calls == (("test-marker", 30.0), ("test-marker", 4.75)) + + +@pytest.mark.parametrize("status", (-1, 401, 403, 500)) +def test_non_quota_failures_are_not_retried_or_treated_as_empty_results(status: int) -> None: + clock: Final = Clock() + reader, search = _reader((StreamingResponse(status_code=status, body=""), _page()), clock) + + with pytest.raises(pytest.fail.Exception, match=f"failed with HTTP {status}"): + reader.events_for_query("test-marker") + + assert search.calls == (("test-marker", 30.0),) + assert clock.elapsed == 0 + + +def test_polling_quota_retries_share_the_original_deadline() -> None: + clock: Final = Clock() + reader, search = _reader( + (_page(), StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT)})), + clock, + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == 2 + + +def test_empty_polling_does_not_start_a_final_search_after_its_deadline() -> None: + clock: Final = Clock() + attempts: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) + reader, search = _reader((_page(),) * attempts, clock) + + assert reader.poll_events_for_query("test-marker") == [] + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == attempts + + +def test_settlement_quota_retries_keep_the_remaining_readback_budget() -> None: + clock: Final = Clock() + empty_reads: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) - 2 + reader, search = _reader( + (_page(),) * empty_reads + + (_page("first"), StreamingResponse(status_code=429, body="", headers={"x-ratelimit-reset": str(POLL_TIMEOUT)})), + clock, + ) + + with pytest.raises(pytest.fail.Exception, match="remained rate-limited"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert search.calls[-1] == ("test-marker", DD_SEARCH_INTERVAL) + assert len(search.calls) == empty_reads + 2 + + +def test_settlement_detects_a_duplicate_on_the_final_search() -> None: + clock: Final = Clock() + reader, _ = _reader((_page("first"), _page("first"), _page(), _page("first", "duplicate")), clock) + + events: Final = reader.poll_events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first", "duplicate") + assert clock.elapsed == 30 + + +def test_settlement_keeps_confirmed_events_through_empty_searches() -> None: + clock: Final = Clock() + reader, _ = _reader((_page("first"), _page(), _page(), _page()), clock) + + events: Final = reader.poll_events_for_query("test-marker") + + assert tuple(event.attributes["id"] for event in events) == ("first",) + assert clock.elapsed == 30 + + +def test_late_delivery_cannot_pass_without_a_complete_settle_window() -> None: + clock: Final = Clock() + empty_reads: Final = int(POLL_TIMEOUT / DD_SEARCH_INTERVAL) - 2 + reader, search = _reader((_page(),) * empty_reads + (_page("first"), _page("first")), clock) + + with pytest.raises(pytest.fail.Exception, match="duplicate-detection window"): + reader.poll_events_for_query("test-marker") + + assert clock.elapsed == POLL_TIMEOUT + assert len(search.calls) == empty_reads + 2 diff --git a/tests/e2e/management/management_client.py b/tests/e2e/management/management_client.py index 2b897f5f07f..1ef0d89a8f9 100644 --- a/tests/e2e/management/management_client.py +++ b/tests/e2e/management/management_client.py @@ -43,6 +43,9 @@ from models import ( KeyResetSpendBody, KeyResetSpendResponse, KeyUpdateBody, + McpServerCreateBody, + McpServerRow, + McpServerUpdateBody, ModelDeleteBody, OrgDeleteBody, OrgInfoParams, @@ -537,6 +540,38 @@ class ManagementClient: ).root ) + def create_mcp_server(self, body: McpServerCreateBody) -> McpServerRow: + return unwrap( + self.proxy.transport.post( + "/v1/mcp/server", + headers=self.proxy.transport.master, + json=body, + response_type=McpServerRow, + ) + ) + + def update_mcp_server(self, body: McpServerUpdateBody) -> McpServerRow: + """PUT /v1/mcp/server, the call behind the dashboard's Save Changes: a partial + update where a field left unset keeps its stored value and None clears it.""" + return unwrap( + self.proxy.transport.put( + "/v1/mcp/server", + headers=self.proxy.transport.master, + json=body, + response_type=McpServerRow, + ) + ) + + def delete_mcp_server(self, server_id: str) -> Result[NoBody]: + """DELETE /v1/mcp/server/{server_id}. Returns the outcome so the act phase can + unwrap it while a deferred teardown can ignore an already-deleted server.""" + return self.proxy.transport.delete( + f"/v1/mcp/server/{server_id}", + headers=self.proxy.transport.master, + json=NoBody(), + response_type=NoBody, + ) + def chat_status(self, key: str, model: str, content: str) -> StreamingResponse: return self.proxy.transport.send( "/chat/completions", diff --git a/tests/e2e/management/test_mcp_lifecycle_e2e.py b/tests/e2e/management/test_mcp_lifecycle_e2e.py new file mode 100644 index 00000000000..9257d697647 --- /dev/null +++ b/tests/e2e/management/test_mcp_lifecycle_e2e.py @@ -0,0 +1,294 @@ +"""Live e2e: the MCP server and toolset management routes' lifecycle contract. + +Two customer defects sit on these routes, and each step here is the read-back that +would have caught one of them: a dashboard edit that took several saves to stick +because the read landed on a replica the write had not reached, and a toolset whose +tools were stored under one name and read back under another, so it granted +nothing. Every read-back therefore polls every replica that serves the route +(ProxyClient.read_back_everywhere) and asserts the exact values written, and both +update routes are held to the same partial-update contract: a field left out of the +payload keeps its stored value, a field sent as null is cleared. The server URL is +unreachable on purpose; only persistence is under test, never a tool call. +""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping +from typing import Final + +import pytest +from e2e_config import unique_marker +from e2e_http import unwrap +from lifecycle import ResourceManager +from management_client import ManagementClient +from models import ( + McpInfo, + McpServerCreateBody, + McpServerListResponse, + McpServerRow, + McpServerUpdateBody, + ToolsetCreateBody, + ToolsetListResponse, + ToolsetRow, + ToolsetTool, + ToolsetUpdateBody, +) + +pytestmark = pytest.mark.e2e + +UNREACHABLE_URL: Final = "https://e2e-fake-mcp.test.local/mcp" + + +def _create_server(client: ManagementClient, resources: ResourceManager) -> tuple[McpServerCreateBody, str]: + name: Final = f"e2e_mcp_lifecycle_{unique_marker()}" + body: Final = McpServerCreateBody( + server_name=name, + alias=name, + url=UNREACHABLE_URL, + transport="http", + description="e2e lifecycle server", + mcp_info=McpInfo( + server_name=f"{name} (display)", + description="shown on the MCP page", + logo_url="https://e2e.test.local/logo.png", + ), + ) + server_id: Final = client.create_mcp_server(body).server_id + resources.defer(lambda: client.delete_mcp_server(server_id)) + return body, server_id + + +def _assert_server_matches(row: McpServerRow, written: McpServerCreateBody, *, where: str) -> None: + stored: Final = (row.server_name, row.alias, row.url, row.transport, row.description, row.mcp_info) + expected: Final = ( + written.server_name, + written.alias, + written.url, + written.transport, + written.description, + written.mcp_info, + ) + assert stored == expected, f"{where}: stored {stored}, expected {expected}" + + +def _server_everywhere( + client: ManagementClient, server_id: str, *, settled: Callable[[McpServerRow], bool] +) -> Mapping[str, McpServerRow]: + return client.proxy.read_body_back_everywhere(f"/v1/mcp/server/{server_id}", McpServerRow, settled=settled) + + +def _listed_server_everywhere(client: ManagementClient, server_id: str) -> Mapping[str, McpServerRow]: + listings: Final = client.proxy.read_body_back_everywhere( + "/v1/mcp/server", + McpServerListResponse, + settled=lambda rows: any(row.server_id == server_id for row in rows.root), + ) + return {replica: next(row for row in rows.root if row.server_id == server_id) for replica, rows in listings.items()} + + +class TestMcpServerLifecycle: + @pytest.mark.covers("mgmt.mcp_server.new.persists") + def test_create_persists_every_field_on_every_replica( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + body, server_id = _create_server(client, resources) + + by_id: Final = _server_everywhere(client, server_id, settled=lambda row: row.server_id == server_id) + for replica, row in by_id.items(): + _assert_server_matches(row, body, where=f"GET /v1/mcp/server/{server_id} on {replica}") + + @pytest.mark.skip( + reason=( + "product gap: GET /v1/mcp/server builds each row from the in-memory registry, whose " + "_build_mcp_server_table sets description from mcp_info['description'], so the list " + "reports the mcp_info description while GET /v1/mcp/server/{server_id} reports the " + "stored description column. A server created with both set to different text reads " + "back with two different descriptions depending on the route" + ) + ) + @pytest.mark.covers("mgmt.mcp_server.list.persists") + def test_created_server_is_listed_with_every_field( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + body, server_id = _create_server(client, resources) + + for replica, row in _listed_server_everywhere(client, server_id).items(): + _assert_server_matches(row, body, where=f"GET /v1/mcp/server on {replica}") + + @pytest.mark.covers("mgmt.mcp_server.update.preserves_unrelated_fields") + def test_updating_only_the_alias_keeps_every_other_field_on_every_replica( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + body, server_id = _create_server(client, resources) + renamed: Final = f"{body.alias}_renamed" + + _ = client.update_mcp_server(McpServerUpdateBody(server_id=server_id, alias=renamed)) + + after_one_put: Final = _server_everywhere(client, server_id, settled=lambda row: row.alias == renamed) + for replica, row in after_one_put.items(): + _assert_server_matches( + row, + body.model_copy(update={"alias": renamed}), + where=f"GET /v1/mcp/server/{server_id} on {replica} after one PUT of alias", + ) + + @pytest.mark.covers("mgmt.mcp_server.update.clear_persists") + def test_clearing_the_description_with_null_reads_back_null( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + body, server_id = _create_server(client, resources) + + _ = client.update_mcp_server(McpServerUpdateBody(server_id=server_id, description=None)) + + cleared: Final = _server_everywhere(client, server_id, settled=lambda row: row.description is None) + for replica, row in cleared.items(): + _assert_server_matches( + row, + body.model_copy(update={"description": None}), + where=f"GET /v1/mcp/server/{server_id} on {replica} after PUT description=null", + ) + + @pytest.mark.covers("mgmt.mcp_server.delete.persists") + def test_delete_removes_the_server_from_every_replica( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + + _ = unwrap(client.delete_mcp_server(server_id)) + + gone: Final = client.proxy.gone_everywhere(f"/v1/mcp/server/{server_id}") + assert set(gone.values()) == {404}, f"a deleted server must 404 on every replica; got {dict(gone)}" + listings: Final = client.proxy.read_body_back_everywhere( + "/v1/mcp/server", + McpServerListResponse, + settled=lambda rows: all(row.server_id != server_id for row in rows.root), + ) + for replica, rows in listings.items(): + assert all(row.server_id != server_id for row in rows.root), ( + f"GET /v1/mcp/server on {replica} still lists the deleted server {server_id}" + ) + + +def _create_toolset( + client: ManagementClient, resources: ResourceManager, server_id: str +) -> tuple[ToolsetCreateBody, str]: + body: Final = ToolsetCreateBody( + toolset_name=f"e2e_toolset_{unique_marker()}", + description="e2e lifecycle toolset", + tools=[ + ToolsetTool(server_id=server_id, tool_name="search_datadog_logs"), + ToolsetTool(server_id=server_id, tool_name="get_datadog_metric"), + ], + ) + toolset_id: Final = client.proxy.create_toolset(body).toolset_id + resources.defer(lambda: client.proxy.delete_toolset(toolset_id)) + return body, toolset_id + + +def _assert_toolset_matches(row: ToolsetRow, written: ToolsetCreateBody, *, where: str) -> None: + stored: Final = (row.toolset_name, row.description, row.tools) + expected: Final = (written.toolset_name, written.description, written.tools) + assert stored == expected, f"{where}: stored {stored}, expected {expected}" + + +def _toolset_everywhere( + client: ManagementClient, toolset_id: str, *, settled: Callable[[ToolsetRow], bool] +) -> Mapping[str, ToolsetRow]: + return client.proxy.read_body_back_everywhere(f"/v1/mcp/toolset/{toolset_id}", ToolsetRow, settled=settled) + + +class TestMcpToolsetLifecycle: + @pytest.mark.covers("mgmt.mcp_toolset.new.persists") + def test_create_persists_both_tools_under_the_exact_names_written( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + body, toolset_id = _create_toolset(client, resources, server_id) + + by_id: Final = _toolset_everywhere(client, toolset_id, settled=lambda row: row.toolset_id == toolset_id) + for replica, row in by_id.items(): + _assert_toolset_matches(row, body, where=f"GET /v1/mcp/toolset/{toolset_id} on {replica}") + listings: Final = client.proxy.read_body_back_everywhere( + "/v1/mcp/toolset", + ToolsetListResponse, + settled=lambda rows: any(row.toolset_id == toolset_id for row in rows.root), + ) + for replica, rows in listings.items(): + _assert_toolset_matches( + next(row for row in rows.root if row.toolset_id == toolset_id), + body, + where=f"GET /v1/mcp/toolset on {replica}", + ) + + @pytest.mark.covers("mgmt.mcp_toolset.update.preserves_unrelated_fields") + def test_updating_only_the_description_keeps_the_tools_and_name( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + body, toolset_id = _create_toolset(client, resources, server_id) + + _ = client.proxy.update_toolset(ToolsetUpdateBody(toolset_id=toolset_id, description="edited")) + + edited: Final = _toolset_everywhere(client, toolset_id, settled=lambda row: row.description == "edited") + for replica, row in edited.items(): + _assert_toolset_matches( + row, + body.model_copy(update={"description": "edited"}), + where=f"GET /v1/mcp/toolset/{toolset_id} on {replica} after PUT of description", + ) + + @pytest.mark.covers("mgmt.mcp_toolset.update.persists") + def test_updating_the_tools_to_one_entry_reads_back_exactly_that_entry( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + body, toolset_id = _create_toolset(client, resources, server_id) + kept: Final = body.tools[:1] + + _ = client.proxy.update_toolset(ToolsetUpdateBody(toolset_id=toolset_id, tools=kept)) + + narrowed: Final = _toolset_everywhere(client, toolset_id, settled=lambda row: row.tools == kept) + for replica, row in narrowed.items(): + _assert_toolset_matches( + row, + body.model_copy(update={"tools": kept}), + where=f"GET /v1/mcp/toolset/{toolset_id} on {replica} after PUT of one tool", + ) + + @pytest.mark.covers("mgmt.mcp_toolset.update.clear_persists") + def test_clearing_the_description_with_null_reads_back_null( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + body, toolset_id = _create_toolset(client, resources, server_id) + + _ = client.proxy.update_toolset(ToolsetUpdateBody(toolset_id=toolset_id, description=None)) + + cleared: Final = _toolset_everywhere(client, toolset_id, settled=lambda row: row.description is None) + for replica, row in cleared.items(): + _assert_toolset_matches( + row, + body.model_copy(update={"description": None}), + where=f"GET /v1/mcp/toolset/{toolset_id} on {replica} after PUT description=null", + ) + + @pytest.mark.covers("mgmt.mcp_toolset.delete.persists") + def test_delete_removes_the_toolset_from_every_replica( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + _, server_id = _create_server(client, resources) + _, toolset_id = _create_toolset(client, resources, server_id) + + _ = unwrap(client.proxy.delete_toolset(toolset_id)) + + gone: Final = client.proxy.gone_everywhere(f"/v1/mcp/toolset/{toolset_id}") + assert set(gone.values()) == {404}, f"a deleted toolset must 404 on every replica; got {dict(gone)}" + listings: Final = client.proxy.read_body_back_everywhere( + "/v1/mcp/toolset", + ToolsetListResponse, + settled=lambda rows: all(row.toolset_id != toolset_id for row in rows.root), + ) + for replica, rows in listings.items(): + assert all(row.toolset_id != toolset_id for row in rows.root), ( + f"GET /v1/mcp/toolset on {replica} still lists the deleted toolset {toolset_id}" + ) diff --git a/tests/e2e/mcp/datadog_mcp.py b/tests/e2e/mcp/datadog_mcp.py index d1ea53a0b3b..352b4446cfd 100644 --- a/tests/e2e/mcp/datadog_mcp.py +++ b/tests/e2e/mcp/datadog_mcp.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from collections.abc import Sequence from e2e_config import datadog_mcp_url, unique_marker from lifecycle import ResourceManager @@ -35,7 +36,11 @@ def register_datadog_mcp( resources: ResourceManager, *, mcp_access_groups: list[str] | None = None, + allowed_tools: Sequence[str] | None = (SEARCH_LOGS_TOOL,), ) -> str: + """Register the core Datadog toolset with its credentials from the env. By default + the server exposes only `search_datadog_logs`; pass `allowed_tools=None` to expose + every tool the core toolset serves.""" assert_dd_mcp_creds() name = f"e2e_dd_mcp_{unique_marker()}" server_id = client.register_server( @@ -47,7 +52,7 @@ def register_datadog_mcp( "DD-API-KEY": _dd_api_key(), "DD-APPLICATION-KEY": _dd_app_key(), }, - allowed_tools=[SEARCH_LOGS_TOOL], + allowed_tools=None if allowed_tools is None else list(allowed_tools), mcp_access_groups=mcp_access_groups, ) resources.defer(lambda: client.delete_server(server_id)) diff --git a/tests/e2e/mcp/mcp_client.py b/tests/e2e/mcp/mcp_client.py index 73453478e5a..210fc7a1e98 100644 --- a/tests/e2e/mcp/mcp_client.py +++ b/tests/e2e/mcp/mcp_client.py @@ -16,11 +16,11 @@ import time from collections.abc import Mapping from dataclasses import dataclass -from pydantic import BaseModel, ConfigDict, Field, RootModel +from pydantic import BaseModel, ConfigDict, Field from e2e_config import settle_propagation from e2e_http import Headers, NoBody, Result, Success, UnknownApiError, unwrap -from models import KeyGenerateBody, ObjectPermission +from models import KeyGenerateBody, McpServerListResponse, McpServerRow, ObjectPermission from proxy_client import ProxyClient McpToolArg = str | int | float | bool | list[str] | dict[str, str] @@ -46,16 +46,6 @@ class McpServerNewResponse(BaseModel): server_id: str -class McpServerRow(BaseModel): - server_id: str - alias: str | None = None - url: str | None = None - - -class McpServersListResponse(RootModel[list[McpServerRow]]): - pass - - class McpToolMcpInfo(BaseModel): server_id: str | None = None alias: str | None = None @@ -193,7 +183,7 @@ class McpClient: "/v1/mcp/server", headers=self.proxy.transport.master, params=NoBody(), - response_type=McpServersListResponse, + response_type=McpServerListResponse, ) ).root @@ -224,11 +214,16 @@ class McpClient: user_id: str, mcp_servers: list[str] | None, mcp_access_groups: list[str] | None = None, + mcp_toolsets: list[str] | None = None, models: list[str] | None = None, ) -> str: object_permission = ( - ObjectPermission(mcp_servers=mcp_servers, mcp_access_groups=mcp_access_groups) - if mcp_servers is not None or mcp_access_groups is not None + ObjectPermission( + mcp_servers=mcp_servers, + mcp_access_groups=mcp_access_groups, + mcp_toolsets=mcp_toolsets, + ) + if mcp_servers is not None or mcp_access_groups is not None or mcp_toolsets is not None else None ) return self.proxy.generate_key( @@ -272,6 +267,20 @@ class McpClient: ) time.sleep(self.proxy.poll_interval) + def await_tools(self, key: str, server_id: str, *, expected: frozenset[str]) -> frozenset[str]: + """Poll tools/list until `server_id`'s tools as `key` sees them are exactly + `expected`, and return the last listing either way, so the caller's equality + assertion names the difference. Fails at poll_timeout only when the read + itself never succeeded.""" + deadline = time.monotonic() + self.proxy.poll_timeout + while True: + result = self.list_tools(key) + if isinstance(result, Success) and result.data.tool_names_for_server(server_id) == expected: + return expected + if time.monotonic() >= deadline: + return unwrap(result).tool_names_for_server(server_id) + time.sleep(self.proxy.poll_interval) + def await_call_tool( self, key: str, diff --git a/tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py b/tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py new file mode 100644 index 00000000000..6b901145eb1 --- /dev/null +++ b/tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py @@ -0,0 +1,95 @@ +"""Live e2e: a key granted a toolset lists exactly the toolset's tools. + +An admin registers the real Datadog remote MCP server with its whole core toolset +exposed, discovers two of its tool names through a key granted the server outright, +and curates a toolset naming exactly those two. A second key is granted the server +plus that toolset, and its tools/list must come back as exactly those two names: no +more, so the rest of the server's catalog stays hidden behind the toolset, and no +fewer, so a tool stored under one name and read under another (which granted +nothing) fails here first. Requires DD_API_KEY + DD_APP_KEY (the suite's real MCP +upstream). +""" + +from __future__ import annotations + +from typing import Final + +import pytest +from datadog_mcp import SEARCH_LOGS_TOOL, register_datadog_mcp +from e2e_config import unique_marker +from e2e_http import unwrap +from lifecycle import ResourceManager +from mcp_client import McpClient +from models import ToolsetCreateBody, ToolsetTool + +pytestmark = pytest.mark.e2e + + +def _key( + client: McpClient, + resources: ResourceManager, + label: str, + *, + server_id: str, + toolset_id: str | None = None, +) -> str: + key: Final = client.generate_key( + user_id=f"e2e-mcp-{label}-{unique_marker()}", + mcp_servers=[server_id], + mcp_toolsets=None if toolset_id is None else [toolset_id], + ) + resources.defer(lambda: client.proxy.delete_key(key)) + return key + + +def _wire_prefix(wire_name: str, tool_name: str, catalog: frozenset[str]) -> str: + """The prefix tools/list puts in front of one server's tool names, measured off a + tool whose own name is known rather than guessed from the alias. A toolset grants + by the tool's own name, never the wire name, and the prefix is whatever the proxy + is configured to build (the alias, or a short server id), so measuring it is the + only way to cross between the two.""" + assert wire_name.endswith(tool_name), f"tools/list served {wire_name!r}, expected it to end with {tool_name!r}" + prefix: Final = wire_name[: len(wire_name) - len(tool_name)] + unprefixed: Final = frozenset(name for name in catalog if not name.startswith(prefix)) + assert not unprefixed, ( + f"every tool of one server shares the wire prefix {prefix!r}, so {sorted(unprefixed)} " + f"cannot be reduced to the names a toolset grants by" + ) + return prefix + + +class TestMcpToolsetEnforcement: + @pytest.mark.covers("mcp.list_tools.api_key.toolset_scoped") + def test_key_granted_a_toolset_lists_exactly_its_tools(self, client: McpClient, resources: ResourceManager) -> None: + server_id: Final = register_datadog_mcp(client, resources, allowed_tools=None) + client.await_registered(server_id) + + catalog_key: Final = _key(client, resources, "catalog", server_id=server_id) + known_wire: Final = client.await_tool(catalog_key, server_id, SEARCH_LOGS_TOOL) + catalog: Final = unwrap(client.list_tools(catalog_key)).tool_names_for_server(server_id) + assert len(catalog) > 2, ( + f"the Datadog core toolset must serve more tools than the toolset names, or the " + f"restriction has nothing to hide; got {sorted(catalog)}" + ) + prefix: Final = _wire_prefix(known_wire, SEARCH_LOGS_TOOL, catalog) + chosen_wire: Final = frozenset(sorted(catalog)[:2]) + chosen: Final = frozenset(name.removeprefix(prefix) for name in chosen_wire) + + toolset: Final = client.proxy.create_toolset( + ToolsetCreateBody( + toolset_name=f"e2e_toolset_{unique_marker()}", + description="two Datadog tools", + tools=[ToolsetTool(server_id=server_id, tool_name=name) for name in sorted(chosen)], + ) + ) + resources.defer(lambda: client.proxy.delete_toolset(toolset.toolset_id)) + assert frozenset(tool.tool_name for tool in toolset.tools) == chosen, ( + f"toolset stored {toolset.tools}, expected the two names {sorted(chosen)} verbatim" + ) + + scoped_key: Final = _key(client, resources, "toolset", server_id=server_id, toolset_id=toolset.toolset_id) + listed: Final = client.await_tools(scoped_key, server_id, expected=chosen_wire) + assert listed == chosen_wire, ( + f"a key granted the toolset must list exactly its two tools; " + f"got {sorted(listed)}, expected {sorted(chosen_wire)}" + ) diff --git a/tests/e2e/models.py b/tests/e2e/models.py index 9f2654e0eec..62810e6cfd9 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -10,6 +10,7 @@ from collections.abc import Sequence from datetime import datetime from typing import Final, Literal +from e2e_http import PartialBody from pydantic import AliasChoices, BaseModel, ConfigDict, Field, RootModel, model_serializer, model_validator # ---------- keys ---------- @@ -55,6 +56,7 @@ class KeyMetadata(BaseModel): class ObjectPermission(BaseModel): mcp_servers: list[str] | None = None mcp_access_groups: list[str] | None = None + mcp_toolsets: list[str] | None = None class KeyGenerateBody(BaseModel): @@ -77,7 +79,7 @@ class KeyGenerateBody(BaseModel): allowed_passthrough_routes: list[str] | None = None metadata: KeyMetadata | None = None object_permission: ObjectPermission | None = None - router_settings: "RouterSettingsOverride | None" = None + router_settings: RouterSettingsOverride | None = None class KeyGenerateResponse(BaseModel): @@ -516,6 +518,15 @@ class CountTokensResponse(BaseModel): # ---------- mcp servers ---------- +class McpInfo(BaseModel): + """The `mcp_info` display block stored on an MCP server; only the fields the + lifecycle test writes and reads back.""" + + server_name: str | None = None + description: str | None = None + logo_url: str | None = None + + class McpServerCreateBody(BaseModel): """POST /v1/mcp/server. For a gateway-managed OAuth server, `auth_type` is `oauth2` and `oauth2_flow` is `authorization_code`; the upstream endpoints @@ -530,6 +541,18 @@ class McpServerCreateBody(BaseModel): oauth2_flow: Literal["client_credentials", "authorization_code"] | None = None authorization_url: str | None = None token_url: str | None = None + server_name: str | None = None + description: str | None = None + mcp_info: McpInfo | None = None + + +class McpServerUpdateBody(PartialBody): + """PUT /v1/mcp/server: a field left unset keeps its stored value, a field set + to None is cleared.""" + + server_id: str + alias: str | None = None + description: str | None = None class McpServerInfo(BaseModel): @@ -543,6 +566,54 @@ class McpServerInfo(BaseModel): allow_all_keys: bool | None = None +class McpServerRow(McpServerInfo): + """A stored MCP server as the create, get, and list routes return it: the + fields the lifecycle test asserts survive the round trip.""" + + server_name: str | None = None + transport: str | None = None + description: str | None = None + mcp_info: McpInfo | None = None + + +class McpServerListResponse(RootModel[list[McpServerRow]]): + """GET /v1/mcp/server answers with a bare array of servers.""" + + +class ToolsetTool(BaseModel): + server_id: str + tool_name: str + + +class ToolsetCreateBody(BaseModel): + toolset_name: str + description: str | None = None + tools: list[ToolsetTool] + + +class ToolsetUpdateBody(PartialBody): + """PUT /v1/mcp/toolset: a field left unset keeps its stored value, a field set + to None is cleared.""" + + toolset_id: str + description: str | None = None + tools: list[ToolsetTool] | None = None + + +class ToolsetRow(BaseModel): + """A stored toolset as POST /v1/mcp/toolset, GET /v1/mcp/toolset/{toolset_id}, + and each row of GET /v1/mcp/toolset return it.""" + + toolset_id: str + toolset_name: str + description: str | None = None + tools: list[ToolsetTool] = Field(default_factory=list) + + +class ToolsetListResponse(RootModel[list[ToolsetRow]]): + """GET /v1/mcp/toolset answers with a bare array of toolsets.""" + + class EmbedBody(BaseModel): model: str input: str diff --git a/tests/e2e/proxy_client.py b/tests/e2e/proxy_client.py index 520cbfde5a9..1bac5116a9d 100644 --- a/tests/e2e/proxy_client.py +++ b/tests/e2e/proxy_client.py @@ -12,6 +12,7 @@ import time import warnings from collections.abc import Callable, Mapping from dataclasses import dataclass +from functools import reduce from datetime import datetime from types import MappingProxyType from typing import Final @@ -26,6 +27,7 @@ from e2e_http import ( Result, StreamingResponse, Success, + UnknownApiError, is_ok, unwrap, ) @@ -70,6 +72,9 @@ from models import ( SpendLogsPage, SpendLogsPageParams, SpendLogsParams, + ToolsetCreateBody, + ToolsetRow, + ToolsetUpdateBody, ) from e2e_config import ( CONTROL_PLANE_BASE_URL, @@ -82,7 +87,7 @@ from e2e_config import ( SLOW_PROVIDER_TIMEOUT_SECONDS, settle_propagation, ) -from transport import HttpTransport, SplitTransport, Transport +from transport import HttpTransport, SplitTransport, Transport, is_control_plane_path RowsPredicate = Callable[[list[SpendLogRow]], bool] @@ -235,6 +240,99 @@ def servable_timeout_message( ) +type ReplicaRead[T] = Callable[[float], T] + + +@dataclass(frozen=True, slots=True) +class EverywhereConverged[T]: + """Every replica answered with something `settled` accepts, keyed by replica.""" + + answers: Mapping[str, T] + + +@dataclass(frozen=True, slots=True) +class NeverConvergedOn[T]: + """`replica` ran out its budget without an answer `settled` accepts; `last` is + its final answer, so the failure can say what that replica still serves.""" + + replica: str + last: T + + +def _last_answer[T]( + read: ReplicaRead[T], + *, + settled: Callable[[T], bool], + timeout: float, + interval: float, + request_timeout: float, + now: Callable[[], float], + sleep: Callable[[float], None], +) -> T: + """Poll `read` until `settled` accepts its answer or `timeout` runs out, and + return the last answer either way. Each read's request timeout is clamped to + the budget left, and the final poll runs even when less than an interval + remains, so a deadline never skips the read that would have settled.""" + deadline: Final = now() + timeout + answer = read(min(request_timeout, timeout)) + while not settled(answer): + remaining = deadline - now() + if remaining <= 0: + return answer + sleep(min(interval, remaining)) + answer = read(min(request_timeout, remaining)) + return answer + + +def await_everywhere[T]( + reads: Mapping[str, ReplicaRead[T]], + *, + settled: Callable[[T], bool], + timeout: float, + interval: float, + request_timeout: float, + now: Callable[[], float], + sleep: Callable[[float], None], +) -> EverywhereConverged[T] | NeverConvergedOn[T]: + """`_last_answer` against every replica in turn, each with the full budget, so a + write counts as visible only once the last replica reflects it, and stop at the + first replica that never converges. Clock and sleep are injected.""" + def read_replica( + outcome: EverywhereConverged[T] | NeverConvergedOn[T], + item: tuple[str, ReplicaRead[T]], + ) -> EverywhereConverged[T] | NeverConvergedOn[T]: + if isinstance(outcome, NeverConvergedOn): + return outcome + replica, read = item + answer: Final = _last_answer( + read, + settled=settled, + timeout=timeout, + interval=interval, + request_timeout=request_timeout, + now=now, + sleep=sleep, + ) + if not settled(answer): + return NeverConvergedOn(replica=replica, last=answer) + return EverywhereConverged(answers=MappingProxyType({**outcome.answers, replica: answer})) + + initial: Final[EverywhereConverged[T] | NeverConvergedOn[T]] = EverywhereConverged(answers=MappingProxyType({})) + return reduce(read_replica, reads.items(), initial) + + +def _is_not_found[R: BaseModel](result: Result[R]) -> bool: + return isinstance(result, UnknownApiError) and result.status_code == 404 + + +def _status_of[R: BaseModel](result: Result[R]) -> int: + match result: + case Success(status_code=status_code) | UnknownApiError(status_code=status_code): + return status_code + case _: + return -1 + + type Poller[T] = Callable[[], T] @@ -321,6 +419,7 @@ def converge_timeout_message(*, what: str, replica: str, timeout: float, last_re class ProxyClient: transport: Transport replicas: Mapping[str, Transport] + control_replicas: Mapping[str, Transport] poll_timeout: float = 120.0 poll_interval: float = 5.0 model_servable_timeout: float = MODEL_SERVABLE_TIMEOUT @@ -569,6 +668,112 @@ class ProxyClient: if not is_ok(result): warnings.warn(f"delete_model({model_id!r}) failed: {result}", stacklevel=2) + # ---- replica read-back ---------------------------------------------- + + def replicas_for(self, path: str) -> Mapping[str, Transport]: + """The replicas that serve `path`: every data-plane replica for an LLM route, + and for a management route the control-plane replicas, since the data-plane + replicas trim management routes and answer them 404. A monolith serves both + from every replica, so a management read-back polls all of them; a split + deployment exposes one control-plane address (there is one backend process + behind it on the stack these suites run against), so it polls that. A + control plane fronting several backends would need its own replica list to + prove each one converged, the way PROXY_REPLICA_URLS does for the gateways. + Never empty: a read-back against no replica would assert nothing and pass.""" + replicas: Final = self.control_replicas if is_control_plane_path(path) else self.replicas + assert replicas, f"no replica is configured to serve {path}, so a read-back there would prove nothing" + return replicas + + def read_body_back_everywhere[R: BaseModel]( + self, path: str, response_type: type[R], *, settled: Callable[[R], bool] + ) -> Mapping[str, R]: + """GET `path` on every replica that serves it, polling each to poll_timeout + until `settled` accepts its body, and fail naming the first replica that + never converged. Returns each replica's settled body, keyed by replica, so + the caller can assert the rest of it.""" + outcome: Final = await_everywhere( + {url: self._reader(transport, path, response_type) for url, transport in self.replicas_for(path).items()}, + settled=lambda result: isinstance(result, Success) and settled(result.data), + timeout=self.poll_timeout, + interval=self.poll_interval, + request_timeout=REQUEST_TIMEOUT, + now=time.monotonic, + sleep=time.sleep, + ) + match outcome: + case EverywhereConverged(answers=answers): + return MappingProxyType({url: unwrap(result) for url, result in answers.items()}) + case NeverConvergedOn(replica=replica, last=last): + raise AssertionError( + f"GET {path} on {replica} never converged within {self.poll_timeout}s of the write; " + f"last read: {last}" + ) + + def gone_everywhere(self, path: str) -> Mapping[str, int]: + """Poll GET `path` on every replica that serves it until each stops serving + it, and fail naming the first replica that still does at poll_timeout. + Returns each replica's final status, so the caller asserts the 404 itself.""" + outcome: Final = await_everywhere( + {url: self._reader(transport, path, NoBody) for url, transport in self.replicas_for(path).items()}, + settled=_is_not_found, + timeout=self.poll_timeout, + interval=self.poll_interval, + request_timeout=REQUEST_TIMEOUT, + now=time.monotonic, + sleep=time.sleep, + ) + match outcome: + case EverywhereConverged(answers=answers): + return MappingProxyType({url: _status_of(result) for url, result in answers.items()}) + case NeverConvergedOn(replica=replica, last=last): + raise AssertionError( + f"GET {path} on {replica} still answers {self.poll_timeout}s after the delete; last read: {last}" + ) + + @staticmethod + def _reader[R: BaseModel](transport: Transport, path: str, response_type: type[R]) -> ReplicaRead[Result[R]]: + return lambda request_timeout: transport.get( + path, + headers=transport.master, + params=NoBody(), + response_type=response_type, + timeout=request_timeout, + ) + + # ---- mcp toolsets --------------------------------------------------- + + def create_toolset(self, body: ToolsetCreateBody) -> ToolsetRow: + return unwrap( + self.transport.post( + "/v1/mcp/toolset", + headers=self.transport.master, + json=body, + response_type=ToolsetRow, + ) + ) + + def update_toolset(self, body: ToolsetUpdateBody) -> ToolsetRow: + """PUT /v1/mcp/toolset: a partial update where a field left unset keeps its + stored value and None clears it.""" + return unwrap( + self.transport.put( + "/v1/mcp/toolset", + headers=self.transport.master, + json=body, + response_type=ToolsetRow, + ) + ) + + def delete_toolset(self, toolset_id: str) -> Result[NoBody]: + """DELETE /v1/mcp/toolset/{toolset_id}. Returns the outcome so the act phase + can unwrap it while a deferred teardown can ignore an already-deleted row.""" + return self.transport.delete( + f"/v1/mcp/toolset/{toolset_id}", + headers=self.transport.master, + json=NoBody(), + response_type=NoBody, + ) + def create_credential(self, body: CredentialCreateBody) -> None: unwrap( self.transport.post( @@ -736,7 +941,10 @@ def build_proxy_client( base URLs are the same for a monolithic proxy, so routing is then a no-op. ``replica_urls`` (PROXY_REPLICA_URLS) names every data-plane replica the model barrier polls directly; it is the data-plane URL itself unless the stack - exports each gateway's own address. + exports each gateway's own address. Management read-backs poll those same + replicas when the two planes share a base URL (a monolith, where every replica + serves every route) and the control plane alone when they differ (a split + deployment, where the data-plane replicas do not serve management routes). The endpoints are injectable for callers that resolve the proxy some other way than ``e2e_config``'s env names (see ``claude_code/_env.py``); they must @@ -764,9 +972,13 @@ def build_proxy_client( for url in replica_urls } ) + control_replicas: Final = ( + replicas if control_plane_base_url == base_url else MappingProxyType({control_plane_base_url: split.control}) + ) return ProxyClient( transport=split, replicas=replicas, + control_replicas=control_replicas, poll_timeout=POLL_TIMEOUT, poll_interval=POLL_INTERVAL, ) diff --git a/tests/e2e/router/test_auto_router_regressions_e2e.py b/tests/e2e/router/test_auto_router_regressions_e2e.py index 188db2a8eb5..374badcf5fc 100644 --- a/tests/e2e/router/test_auto_router_regressions_e2e.py +++ b/tests/e2e/router/test_auto_router_regressions_e2e.py @@ -41,6 +41,7 @@ which stores either the registered alias or the provider-prefixed form. import json import os from collections.abc import Iterator +from contextlib import ExitStack from dataclasses import dataclass from typing import Final @@ -120,19 +121,10 @@ class ResponsesApiResponse(BaseModel): @dataclass(frozen=True, slots=True) -class TagSplitDeployments: - """Scenario A mirrors the customer-shaped config from GitHub issue #36619: - plain deployment registered first, tier deployment and marker both tagged. - Scenario B flips both axes for GitHub issue #36621: marker registered first - and its tier deployment left untagged, so routing depends neither on - registration order nor on tier deployments carrying tags.""" - - tag_a: str - shared_a: str - tier_a: str - tag_b: str - shared_b: str - tier_b: str +class TagSplitDeployment: + tag: str + shared: str + tier: str @dataclass(frozen=True, slots=True) @@ -173,9 +165,7 @@ def _uniform_tier_config(tier_model: str) -> dict[str, object]: } -def _key_for( - proxy: ProxyClient, resources: ResourceManager, models: list[str], tag_filtering: bool = False -) -> str: +def _key_for(proxy: ProxyClient, resources: ResourceManager, models: list[str], tag_filtering: bool = False) -> str: key: Final = proxy.generate_key( KeyGenerateBody( models=models, @@ -211,46 +201,61 @@ def _assert_served_only_by(rows: list[SpendLogRow], allowed: frozenset[str], con ) -@pytest.fixture(scope="module") -def split(proxy: ProxyClient) -> Iterator[TagSplitDeployments]: +@pytest.fixture(scope="class") +def router_stack() -> Iterator[ExitStack]: + with ExitStack() as stack: + yield stack + + +def _register_models( + proxy: ProxyClient, stack: ExitStack, registrations: tuple[tuple[str, LiteLLMParamsBody], ...] +) -> None: + for name, params in registrations: + stack.callback(proxy.delete_model, proxy.create_model(name, params)) + + +def _tag_split(proxy: ProxyClient, stack: ExitStack, *, marker_first: bool) -> TagSplitDeployment: marker: Final = unique_marker() - deployments: Final = TagSplitDeployments( - tag_a=f"e2e-split-a-{marker}", - shared_a=f"e2e-autoroute-a-{marker}", - tier_a=f"e2e-tier-a-{marker}", - tag_b=f"e2e-split-b-{marker}", - shared_b=f"e2e-autoroute-b-{marker}", - tier_b=f"e2e-tier-b-{marker}", + named: Final = TagSplitDeployment( + tag=f"e2e-split-{marker}", + shared=f"e2e-autoroute-{marker}", + tier=f"e2e-tier-{marker}", ) anthropic_key: Final = _provider_key("ANTHROPIC_API_KEY") - marker_params_a: Final = LiteLLMParamsBody( - model="auto_router/complexity_router", - complexity_router_config=_uniform_tier_config(deployments.tier_a), - tags=[deployments.tag_a], + marker_registration: Final = ( + named.shared, + LiteLLMParamsBody( + model="auto_router/complexity_router", + complexity_router_config=_uniform_tier_config(named.tier), + tags=[named.tag], + ), ) - marker_params_b: Final = LiteLLMParamsBody( - model="auto_router/complexity_router", - complexity_router_config=_uniform_tier_config(deployments.tier_b), - tags=[deployments.tag_b], + tier_registration: Final = ( + named.tier, + LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key, tags=None if marker_first else [named.tag]), ) - registrations: Final[tuple[tuple[str, LiteLLMParamsBody], ...]] = ( - (deployments.shared_a, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)), - (deployments.tier_a, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key, tags=[deployments.tag_a])), - (deployments.shared_a, marker_params_a), - (deployments.shared_b, marker_params_b), - (deployments.tier_b, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=anthropic_key)), - (deployments.shared_b, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)), + plain_registration: Final = (named.shared, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=anthropic_key)) + registrations: Final = ( + (marker_registration, tier_registration, plain_registration) + if marker_first + else (plain_registration, tier_registration, marker_registration) ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield deployments - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, stack, registrations) + return named -@pytest.fixture(scope="module") -def zero_priced_alias(proxy: ProxyClient) -> Iterator[ZeroPricedAlias]: +@pytest.fixture(scope="class") +def plain_first_split(proxy: ProxyClient, router_stack: ExitStack) -> TagSplitDeployment: + return _tag_split(proxy, router_stack, marker_first=False) + + +@pytest.fixture(scope="class") +def marker_first_split(proxy: ProxyClient, router_stack: ExitStack) -> TagSplitDeployment: + return _tag_split(proxy, router_stack, marker_first=True) + + +@pytest.fixture(scope="class") +def zero_priced_alias(proxy: ProxyClient, router_stack: ExitStack) -> ZeroPricedAlias: marker: Final = unique_marker() named: Final = ZeroPricedAlias(alias=f"e2e-priced-alias-{marker}", tier=f"e2e-priced-tier-{marker}") alias_params: Final = LiteLLMParamsBody( @@ -263,16 +268,12 @@ def zero_priced_alias(proxy: ProxyClient) -> Iterator[ZeroPricedAlias]: (named.tier, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.alias, alias_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def heuristic_split(proxy: ProxyClient) -> Iterator[HeuristicSplit]: +@pytest.fixture(scope="class") +def heuristic_split(proxy: ProxyClient, router_stack: ExitStack) -> HeuristicSplit: marker: Final = unique_marker() named: Final = HeuristicSplit( alias=f"e2e-heuristic-router-{marker}", @@ -289,16 +290,12 @@ def heuristic_split(proxy: ProxyClient) -> Iterator[HeuristicSplit]: (named.strong, LiteLLMParamsBody(model=STRONG_MODEL, api_key=_provider_key("OPENAI_API_KEY"))), (named.alias, LiteLLMParamsBody(model="auto_router/complexity_router", complexity_router_config=config)), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def semantic_auto_router(proxy: ProxyClient) -> Iterator[SemanticAutoRouter]: +@pytest.fixture(scope="class") +def semantic_auto_router(proxy: ProxyClient, router_stack: ExitStack) -> SemanticAutoRouter: marker: Final = unique_marker() named: Final = SemanticAutoRouter( marker=f"e2e-semantic-router-{marker}", @@ -321,16 +318,12 @@ def semantic_auto_router(proxy: ProxyClient) -> Iterator[SemanticAutoRouter]: (named.fallback, LiteLLMParamsBody(model=PLAIN_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.marker, marker_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named -@pytest.fixture(scope="module") -def credentialed_alias(proxy: ProxyClient) -> Iterator[CredentialedAlias]: +@pytest.fixture(scope="class") +def credentialed_alias(proxy: ProxyClient, router_stack: ExitStack) -> CredentialedAlias: marker: Final = unique_marker() named: Final = CredentialedAlias(alias=f"e2e-cred-alias-{marker}", tier=f"e2e-cred-tier-{marker}") alias_params: Final = LiteLLMParamsBody( @@ -342,104 +335,110 @@ def credentialed_alias(proxy: ProxyClient) -> Iterator[CredentialedAlias]: (named.tier, LiteLLMParamsBody(model=CHEAP_MODEL, api_key=_provider_key("ANTHROPIC_API_KEY"))), (named.alias, alias_params), ) - created: Final = tuple(proxy.create_model(name, params) for name, params in registrations) - try: - yield named - finally: - for model_id in created: - proxy.delete_model(model_id) + _register_models(proxy, router_stack, registrations) + return named class TestTagSplitRouting: @pytest.mark.covers("reliability.routing.tagged_marker.request_tag_selects_marker") def test_body_tagged_chat_routes_through_the_marker_to_its_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36619: with tag filtering on, a chat request whose body metadata tags match the tagged marker under a shared model name is answered by the marker's tier deployment, not by the plain deployment that was registered under the name first.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - chat: Final = unwrap(proxy.chat(key, _hello_chat_body(split.shared_a, tags=[split.tag_a]))) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + chat: Final = unwrap(proxy.chat(key, _hello_chat_body(plain_first_split.shared, tags=[plain_first_split.tag]))) assert chat.choices, "tagged chat through the shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "body-tagged chat on the shared name") + _assert_served_only_by(rows, CHEAP_SERVED | {plain_first_split.tier}, "body-tagged chat on the shared name") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_chat_is_always_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36620: untagged chat requests to the shared name succeed on every call and are all served by the plain deployment; the tagged marker never captures them, so no intermittent auto-router errors and no tier hijacking.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) for _ in range(5): - chat = unwrap(proxy.chat(key, _hello_chat_body(split.shared_a))) + chat = unwrap(proxy.chat(key, _hello_chat_body(plain_first_split.shared))) assert chat.choices, "untagged chat through the shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=5) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged chat on the shared name") + _assert_served_only_by(rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged chat on the shared name") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_messages_is_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36620 on the /v1/messages surface: an untagged Anthropic-native request to the shared name is served by the plain deployment, not captured by the tagged marker.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - answer: Final = unwrap(proxy.messages(key, _hello_messages_body(split.shared_a))) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + answer: Final = unwrap(proxy.messages(key, _hello_messages_body(plain_first_split.shared))) assert answer.content or answer.choices, "untagged /v1/messages returned neither content nor choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged /v1/messages on the shared name") + _assert_served_only_by( + rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged /v1/messages on the shared name" + ) class TestUntaggedTierDeployments: @pytest.mark.covers("reliability.routing.tagged_marker.header_tag_selects_marker") def test_header_tagged_messages_routes_through_the_marker_to_an_untagged_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """Pins GitHub issue #36621: a /v1/messages request tagged only via the x-litellm-tags header selects the tagged marker, and the rewrite still lands on the tier deployment even though that deployment carries no tags, because the marker consumed the routing tags.""" - key: Final = _key_for(proxy, resources, [split.shared_b, split.tier_b], tag_filtering=True) - headers: Final = TaggedAnthropicHeaders(authorization=f"Bearer {key}", x_litellm_tags=split.tag_b) + key: Final = _key_for( + proxy, resources, [marker_first_split.shared, marker_first_split.tier], tag_filtering=True + ) + headers: Final = TaggedAnthropicHeaders(authorization=f"Bearer {key}", x_litellm_tags=marker_first_split.tag) answer: Final = unwrap( proxy.transport.post( "/v1/messages", headers=headers, - json=_hello_messages_body(split.shared_b), + json=_hello_messages_body(marker_first_split.shared), response_type=AnthropicMessagesResponse, ) ) assert answer.content or answer.choices, "header-tagged /v1/messages returned neither content nor choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_b}, "header-tagged /v1/messages on the shared name") + _assert_served_only_by( + rows, CHEAP_SERVED | {marker_first_split.tier}, "header-tagged /v1/messages on the shared name" + ) @pytest.mark.covers("reliability.routing.tagged_marker.untagged_tier_deployments_still_served") def test_body_tagged_chat_reaches_the_untagged_tier_after_marker_rewrite( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """Pins the tag-consumption half of GitHub issue #36621: after the tagged marker rewrites the request to its tier model, the consumed routing tags no longer constrain deployment selection, so the untagged tier deployment serves the request instead of a strict-tag denial.""" - key: Final = _key_for(proxy, resources, [split.shared_b, split.tier_b], tag_filtering=True) - chat: Final = unwrap(proxy.chat(key, _hello_chat_body(split.shared_b, tags=[split.tag_b]))) + key: Final = _key_for( + proxy, resources, [marker_first_split.shared, marker_first_split.tier], tag_filtering=True + ) + chat: Final = unwrap( + proxy.chat(key, _hello_chat_body(marker_first_split.shared, tags=[marker_first_split.tag])) + ) assert chat.choices, "body-tagged chat through the marker-first shared name returned no choices" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_b}, "body-tagged chat with untagged tier") + _assert_served_only_by(rows, CHEAP_SERVED | {marker_first_split.tier}, "body-tagged chat with untagged tier") @pytest.mark.covers("reliability.routing.tagged_marker.tag_semantics_stay_strict") def test_tagged_call_straight_at_an_untagged_deployment_stays_denied( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, marker_first_split: TagSplitDeployment ) -> None: """The tag-consumption fix must not loosen strict tag semantics: a tagged request aimed directly at an untagged deployment (no marker involved) is still rejected with the 401 tags-configuration error.""" - key: Final = _key_for(proxy, resources, [split.tier_b], tag_filtering=True) - result: Final = proxy.chat(key, _hello_chat_body(split.tier_b, tags=[split.tag_b])) + key: Final = _key_for(proxy, resources, [marker_first_split.tier], tag_filtering=True) + result: Final = proxy.chat(key, _hello_chat_body(marker_first_split.tier, tags=[marker_first_split.tag])) assert isinstance(result, UnauthorizedError), ( f"expected the tagged direct call to an untagged deployment to be denied with 401, got {result}" ) @@ -451,37 +450,39 @@ class TestUntaggedTierDeployments: class TestResponsesApiTagRouting: @pytest.mark.covers("reliability.routing.tagged_marker.responses_input_routes_through_marker") def test_header_tagged_responses_with_string_input_routes_to_the_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the /v1/responses surface of the tag split (GitHub issues #36620/#36621): a /v1/responses request with string input, tagged via the x-litellm-tags header, succeeds and routes through the tagged marker to its tier.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) - headers: Final = TaggedAuthHeaders(authorization=f"Bearer {key}", x_litellm_tags=split.tag_a) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) + headers: Final = TaggedAuthHeaders(authorization=f"Bearer {key}", x_litellm_tags=plain_first_split.tag) body: Final = ResponsesBody( - model=split.shared_a, input=f"say hello {unique_marker()}", max_output_tokens=64 + model=plain_first_split.shared, input=f"say hello {unique_marker()}", max_output_tokens=64 ) answer: Final = unwrap( proxy.transport.post("/v1/responses", headers=headers, json=body, response_type=ResponsesApiResponse) ) assert answer.id, "header-tagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "header-tagged /v1/responses string input") + _assert_served_only_by( + rows, CHEAP_SERVED | {plain_first_split.tier}, "header-tagged /v1/responses string input" + ) @pytest.mark.covers("reliability.routing.tagged_marker.responses_input_routes_through_marker") def test_body_tagged_responses_with_list_input_routes_to_the_tier( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the body-tag and list-input combination of the same split: /v1/responses with litellm_metadata.tags and structured input items routes through the tagged marker to its tier.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) body: Final = ResponsesBody( - model=split.shared_a, + model=plain_first_split.shared, input=[ResponsesInputItem(role="user", content=f"say hello {unique_marker()}")], max_output_tokens=64, - litellm_metadata=ResponsesTagMetadata(tags=[split.tag_a]), + litellm_metadata=ResponsesTagMetadata(tags=[plain_first_split.tag]), ) answer: Final = unwrap( proxy.transport.post( @@ -493,18 +494,18 @@ class TestResponsesApiTagRouting: ) assert answer.id, "body-tagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, CHEAP_SERVED | {split.tier_a}, "body-tagged /v1/responses list input") + _assert_served_only_by(rows, CHEAP_SERVED | {plain_first_split.tier}, "body-tagged /v1/responses list input") @pytest.mark.covers("reliability.routing.tagged_marker.untagged_request_served_by_plain_deployment") def test_untagged_responses_is_served_by_the_plain_deployment( - self, proxy: ProxyClient, resources: ResourceManager, split: TagSplitDeployments + self, proxy: ProxyClient, resources: ResourceManager, plain_first_split: TagSplitDeployment ) -> None: """Pins the untagged half of the /v1/responses tag split: an untagged request to the shared name is served by the plain deployment, matching the chat and messages surfaces.""" - key: Final = _key_for(proxy, resources, [split.shared_a, split.tier_a], tag_filtering=True) + key: Final = _key_for(proxy, resources, [plain_first_split.shared, plain_first_split.tier], tag_filtering=True) body: Final = ResponsesBody( - model=split.shared_a, input=f"say hello {unique_marker()}", max_output_tokens=64 + model=plain_first_split.shared, input=f"say hello {unique_marker()}", max_output_tokens=64 ) answer: Final = unwrap( proxy.transport.post( @@ -516,7 +517,9 @@ class TestResponsesApiTagRouting: ) assert answer.id, "untagged /v1/responses returned no response id" rows: Final = proxy.poll_logs_for_key(key, min_rows=1) - _assert_served_only_by(rows, PLAIN_SERVED | {split.shared_a}, "untagged /v1/responses on the shared name") + _assert_served_only_by( + rows, PLAIN_SERVED | {plain_first_split.shared}, "untagged /v1/responses on the shared name" + ) class TestStrategyAliasPricing: @@ -551,9 +554,7 @@ class TestComplexityHeuristicScope: while the accompanying ~2KB agent system prompt is packed with enough reasoning and complexity keywords that scoring the combined text lands in REASONING; only ask-only scoring keeps this on the cheap tier.""" - key: Final = _key_for( - proxy, resources, [heuristic_split.alias, heuristic_split.cheap, heuristic_split.strong] - ) + key: Final = _key_for(proxy, resources, [heuristic_split.alias, heuristic_split.cheap, heuristic_split.strong]) body: Final = ChatBody( model=heuristic_split.alias, messages=[ diff --git a/tests/e2e/test_e2e_http.py b/tests/e2e/test_e2e_http.py index 66841725d1d..81cd6c8d3d1 100644 --- a/tests/e2e/test_e2e_http.py +++ b/tests/e2e/test_e2e_http.py @@ -13,13 +13,24 @@ monkeypatches anything. from __future__ import annotations from collections.abc import Callable, Iterator, Mapping, Sequence -from dataclasses import dataclass, field +from dataclasses import dataclass from types import MappingProxyType from typing import Final import pytest - -from e2e_http import RETRY_ATTEMPTS, TRANSIENT_STATUSES, request_with_retry, streaming_outcome +from e2e_http import ( + RETRY_ATTEMPTS, + TRANSIENT_STATUSES, + NoBody, + PartialBody, + Success, + ValidationError, + classify, + request_with_retry, + streaming_outcome, + wire_body, +) +from pydantic import BaseModel, TypeAdapter @dataclass @@ -33,10 +44,10 @@ class FakeResponse: @dataclass class SleepRecorder: - delays: list[float] = field(default_factory=list) + delays: tuple[float, ...] = () def __call__(self, seconds: float) -> None: - self.delays.append(seconds) + self.delays += (seconds,) def _issue_from(responses: Sequence[FakeResponse]) -> Callable[[], FakeResponse]: @@ -55,7 +66,7 @@ class TestTransientRetryPolicy: sleep = SleepRecorder() result = request_with_retry(_issue_from(responses), sleep=sleep) assert result is responses[0] - assert sleep.delays == [] + assert sleep.delays == () assert responses[0].close_calls == 0 def test_429_is_never_retried(self) -> None: @@ -63,7 +74,7 @@ class TestTransientRetryPolicy: sleep = SleepRecorder() result = request_with_retry(_issue_from(responses), sleep=sleep) assert result is responses[0] - assert sleep.delays == [] + assert sleep.delays == () assert responses[0].close_calls == 0 def test_overloaded_529_retries_with_backoff_then_returns_the_success(self) -> None: @@ -71,7 +82,7 @@ class TestTransientRetryPolicy: sleep = SleepRecorder() result = request_with_retry(_issue_from(responses), sleep=sleep) assert result is responses[1] - assert sleep.delays == [0.5] + assert sleep.delays == (0.5,) assert responses[0].close_calls == 1 assert responses[1].close_calls == 0 @@ -80,7 +91,7 @@ class TestTransientRetryPolicy: sleep = SleepRecorder() result = request_with_retry(_issue_from(responses), sleep=sleep) assert result is responses[RETRY_ATTEMPTS - 1] - assert sleep.delays == [0.5, 1.0] + assert sleep.delays == (0.5, 1.0) assert [r.close_calls for r in responses] == [1, 1, 0, 0] @@ -134,3 +145,65 @@ class TestStreamEventArrivals: assert result.stream_events == [] assert result.stream_event_arrivals == [] assert result.body == "bad request" + + +class _ServerUpdate(PartialBody): + server_id: str + alias: str | None = None + description: str | None = None + + +class _ServerCreate(BaseModel): + alias: str + description: str | None = None + + +class TestWireBody: + """A partial-update body must put exactly the caller's choice on the wire: an + omitted field stays off it so the route keeps the stored value, and an explicit + None goes out as JSON null so the route clears it. Plain bodies keep dropping + None, which is what every create route expects.""" + + def test_partial_body_omits_unset_fields_and_sends_explicit_none_as_null(self) -> None: + assert wire_body(_ServerUpdate(server_id="s1", description=None)) == {"server_id": "s1", "description": None} + assert wire_body(_ServerUpdate(server_id="s1", alias="renamed")) == {"server_id": "s1", "alias": "renamed"} + + def test_plain_body_drops_none_fields(self) -> None: + assert wire_body(_ServerCreate(alias="a", description=None)) == {"alias": "a"} + + +_JSON: Final[TypeAdapter[object]] = TypeAdapter(object) + + +@dataclass +class FakeJsonResponse: + """The `classify` view of a response: a status, the raw body bytes, and the + parse that would raise on an empty one.""" + + status_code: int + content: bytes + + @property + def ok(self) -> bool: + return self.status_code < 400 + + @property + def text(self) -> str: + return self.content.decode() + + def json(self) -> object: + return _JSON.validate_json(self.content) + + +class TestClassifyEmptyBody: + """A delete that answers 202 with no body is a success, not a parse failure: + the MCP server and toolset delete routes both answer that way, and reading it + as a failure would hide a delete that did not happen behind one that did.""" + + def test_empty_2xx_body_is_a_success(self) -> None: + result: Final = classify(FakeJsonResponse(status_code=202, content=b""), NoBody) + assert isinstance(result, Success) and result.status_code == 202 + + def test_body_that_is_not_json_is_still_a_validation_failure(self) -> None: + result: Final = classify(FakeJsonResponse(status_code=200, content=b""), NoBody) + assert isinstance(result, ValidationError) diff --git a/tests/e2e/test_proxy_client.py b/tests/e2e/test_proxy_client.py index 2caac58333f..3b84a47e3cc 100644 --- a/tests/e2e/test_proxy_client.py +++ b/tests/e2e/test_proxy_client.py @@ -15,28 +15,35 @@ from collections.abc import Iterable, Mapping from dataclasses import dataclass from itertools import chain, repeat from types import MappingProxyType -from typing import Final +from typing import Final, cast import pytest - from e2e_config import parse_replica_urls from e2e_http import Result, Success from models import KeyInfo, KeyInfoResponse, ModelListEntry, ModelsListResponse from proxy_client import ( - Poller, ConvergeOutcome, Converged, + EverywhereConverged, ModelsPoller, + NeverConvergedOn, NotConverged, NotServableOn, + Poller, + ProxyClient, + ReplicaRead, Servable, await_converged_everywhere, + await_everywhere, await_servable_everywhere, - first_lagging_replica, + build_proxy_client, converge_timeout_message, + first_lagging_replica, ) +from transport import Transport MODEL: Final = "gpt-under-test" +_NO_TRANSPORTS: Final = cast(Transport, None) TIMEOUT: Final = 10.0 INTERVAL: Final = 2.0 RPM_BEFORE_UPDATE: Final = 100 @@ -187,3 +194,83 @@ class TestParseReplicaUrls: def test_falls_back_to_the_data_plane_address_when_unset(self) -> None: assert parse_replica_urls("", "http://lb") == ("http://lb",) + + +def _answers(answers: Iterable[str]) -> ReplicaRead[str]: + it: Final = iter(answers) + return lambda _timeout: next(it) + + +def _await_everywhere(reads: Mapping[str, ReplicaRead[str]]) -> EverywhereConverged[str] | NeverConvergedOn[str]: + clock: Final = FakeClock() + return await_everywhere( + reads, + settled=lambda answer: answer == "renamed", + timeout=TIMEOUT, + interval=INTERVAL, + request_timeout=5.0, + now=clock.now, + sleep=clock.sleep, + ) + + +class TestAwaitEverywhere: + def test_waits_for_the_lagging_replica_and_returns_every_settled_answer(self) -> None: + reads: Final = { + "gateway-1": _answers(repeat("renamed")), + "gateway-2": _answers(chain(repeat("stale", 2), repeat("renamed"))), + } + outcome: Final = _await_everywhere(reads) + assert isinstance(outcome, EverywhereConverged) + assert dict(outcome.answers) == {"gateway-1": "renamed", "gateway-2": "renamed"} + + def test_names_the_replica_that_never_converges_with_what_it_last_served(self) -> None: + reads: Final = { + "gateway-1": _answers(repeat("renamed")), + "gateway-2": _answers(repeat("stale")), + } + assert _await_everywhere(reads) == NeverConvergedOn(replica="gateway-2", last="stale") + + def test_polls_until_the_deadline_before_giving_up(self) -> None: + lagging: Final = chain(repeat("stale", int(TIMEOUT / INTERVAL)), repeat("renamed")) + outcome: Final = _await_everywhere({"gateway-1": _answers(lagging)}) + assert isinstance(outcome, EverywhereConverged), outcome + + +class TestReplicasFor: + def test_split_deployment_reads_management_routes_back_from_the_control_plane(self) -> None: + client: Final = build_proxy_client( + base_url="http://lb", + control_plane_base_url="http://backend", + replica_urls=("http://gateway-1", "http://gateway-2"), + ) + assert set(client.replicas_for("/key/info")) == {"http://backend"} + assert set(client.replicas_for("/v1/models")) == {"http://gateway-1", "http://gateway-2"} + + def test_monolith_reads_management_routes_back_from_every_replica(self) -> None: + client: Final = build_proxy_client( + base_url="http://lb", + control_plane_base_url="http://lb", + replica_urls=("http://pod-1", "http://pod-2"), + ) + assert set(client.replicas_for("/key/info")) == {"http://pod-1", "http://pod-2"} + + def test_mcp_admin_routes_read_back_from_every_data_plane_replica(self) -> None: + """/v1/mcp/* is a lazily mounted feature, so a data-plane replica serves it + too and answers from its own in-memory registry. Routing it to the control + plane would leave every replica but that one unproven, and would move the + tools/list barrier in mcp_client off the plane that serves tools/list.""" + client: Final = build_proxy_client( + base_url="http://lb", + control_plane_base_url="http://backend", + replica_urls=("http://gateway-1", "http://gateway-2"), + ) + assert set(client.replicas_for("/v1/mcp/server/abc")) == {"http://gateway-1", "http://gateway-2"} + assert set(client.replicas_for("/v1/mcp/toolset/abc")) == {"http://gateway-1", "http://gateway-2"} + + def test_a_route_no_replica_serves_is_refused_rather_than_read_back_vacuously(self) -> None: + """A read-back over zero replicas would satisfy every predicate and assert + nothing, so asking for one fails instead of passing silently.""" + client: Final = ProxyClient(transport=_NO_TRANSPORTS, replicas={}, control_replicas={}) + with pytest.raises(AssertionError, match="no replica is configured"): + _ = client.replicas_for("/v1/models") 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 c0d055edb7f..669e094fee4 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 @@ -1,10 +1,11 @@ """ -Tests for partial-update semantics of PUT /v1/mcp/server. +Tests for partial-update semantics of PUT /v1/mcp/server and PUT /v1/mcp/toolset. A partial update must only write the fields the caller explicitly provided. Omitting a field must NOT reset it to its Pydantic schema default (e.g. ``transport=sse``, ``mcp_access_groups=[]``, ``allow_all_keys=False``), which -would silently overwrite the existing DB row. +would silently overwrite the existing DB row, and a field the caller sent as null +must be cleared rather than left at its stored value. """ import json @@ -850,3 +851,69 @@ async def test_cf_pair_switch_does_not_clear_dcr_bridge(): data = UpdateMCPServerRequest(server_id="s", auth_type="oauth_delegate") data_dict = await _run_update_with_existing(data, existing_auth_type="true_passthrough") assert "dcr_bridge" not in data_dict + + +def _mock_toolset_prisma(): + """A prisma double whose update answers with a row the reader can expand, so the + call under test returns instead of failing inside the row mapper.""" + updated_row = MagicMock() + updated_row.model_dump.return_value = { + "toolset_id": "ts-1", + "toolset_name": "ops", + "description": None, + "tools": "[]", + } + mock_prisma = MagicMock() + mock_prisma.db.litellm_mcptoolsettable = AsyncMock() + mock_prisma.db.litellm_mcptoolsettable.update = AsyncMock(return_value=updated_row) + return mock_prisma + + +async def _run_toolset_update(payload: dict) -> dict: + """The columns PUT /v1/mcp/toolset writes for this payload, minus the audit stamp + every write carries. The prisma double is injected, so nothing is patched.""" + from litellm.proxy._experimental.mcp_server.toolset_db import update_mcp_toolset + from litellm.types.mcp_server.mcp_toolset import UpdateMCPToolsetRequest + + mock_prisma = _mock_toolset_prisma() + await update_mcp_toolset(mock_prisma, UpdateMCPToolsetRequest.model_validate(payload), "test-user") + written = dict(mock_prisma.db.litellm_mcptoolsettable.update.call_args[1]["data"]) + assert written["updated_by"] == "test-user" + return {name: value for name, value in written.items() if name != "updated_by"} + + +@pytest.mark.asyncio +async def test_toolset_partial_update_clears_description_on_explicit_null(): + """The dump used to drop None, so a null description could never clear the stored + one: the toolset kept a description its owner had deleted.""" + assert await _run_toolset_update({"toolset_id": "ts-1", "description": None}) == {"description": None} + + +@pytest.mark.asyncio +async def test_toolset_partial_update_omits_the_fields_the_caller_left_out(): + tools = [{"server_id": "s1", "tool_name": "alpha"}] + assert await _run_toolset_update({"toolset_id": "ts-1", "tools": tools}) == {"tools": json.dumps(tools)} + + +@pytest.mark.asyncio +async def test_toolset_partial_update_ignores_null_tools_rather_than_revoking_them(): + """A client that sends tools=null means "leave the selection alone", so the grants + survive. Clearing them is an explicit [], which cannot be confused with an omitted + field; treating null as a clear would silently revoke every tool the toolset grants.""" + assert await _run_toolset_update({"toolset_id": "ts-1", "tools": None, "description": "kept"}) == { + "description": "kept" + } + + +@pytest.mark.asyncio +async def test_toolset_partial_update_empties_the_selection_on_an_explicit_empty_list(): + assert await _run_toolset_update({"toolset_id": "ts-1", "tools": []}) == {"tools": "[]"} + + +@pytest.mark.asyncio +async def test_toolset_partial_update_ignores_a_null_name(): + """A toolset always has a name, so a null toolset_name is a no-op, not a clear + that would write a NOT NULL column to null.""" + assert await _run_toolset_update({"toolset_id": "ts-1", "toolset_name": None, "description": "kept"}) == { + "description": "kept" + } From e8e3172d7d70558929f32f057ebe4c7471c8c352 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 8 Sep 2026 23:10:18 -0700 Subject: [PATCH 083/105] fix(model-management): honor an explicit null as a clear on model update (#40047) * fix(model-management): honor an explicit null as a clear on model update PATCH /model/{model_id}/update merged the patch with exclude_none and then popped explicit nulls only for the mirrored pricing fields, so a null sent for max_input_tokens, mode, supports_vision or any other key was dropped and a value pinned by an earlier save could never be removed. The route now follows JSON Merge Patch over both blobs: a key absent from the body is unchanged, a key sent as null is removed from the stored row, and a key sent with a value is set. Ownership and identity keys keep ignoring a null, as do the fields the stored models require, since clearing one writes a row no reload can rebuild. Mirrored pricing keys still clear from both blobs. Clearing a price also needed the router to stop merging a deployment's cost-map entry onto its previous registration, which left the old rate in place and kept billing at a price the deployment no longer carried. Adds a create, read, partial-update, clear, enforce, delete lifecycle e2e that reads back on every replica, and a harness helper for that read-back. * fix(router): keep a deployment id that names a real model from evicting its catalog entry Deployments are keyed into litellm.model_cost alongside the built-in catalog, so evicting a deployment's stale entry by id could take a real model's entry with it: registering a deployment whose model_info.id is "gpt-4o" stripped that model's pricing, context window and capability flags process-wide, for every other deployment of it, until the next price-map reload. Only evict an entry this registration owns. A colliding id keeps the previous merge, which pollutes the catalog entry rather than emptying it. Also pins the Admin UI round trip: the model edit form echoes the whole /model/info row back on save, and that read reports every key the deployment never stored as an explicit null, so the clear path has to leave those keys alone. * fix(router): decide cost-map eviction by what this registrar created The previous guard read a catalog entry off `litellm_provider`, so a deployment that declares its own provider in model_info was treated as one and kept billing at a price it no longer carried. It also only held for a single registration: a second one under a colliding id saw the id the first merge left behind and evicted the catalog entry anyway. Track the cost-map keys this registrar creates instead. A key it created is evicted before re-registration; one it did not is left to merge, which is what a deployment id colliding with a catalog model name needs. Also folds the required-fields comment into the docstring that already gives the reason. * fix(router): release a deployment's cost-map key when it is deleted The ownership ledger only grew. A deleted deployment kept its claim, so if a later catalog refresh started publishing a model under that same name, the next registration would treat the catalog entry as the deployment's own and evict it. Deleting a deployment now gives the key back, which also stops the ledger growing for the life of the process. * fix(router): hold a cost-map key while another live router still serves it The claim is process-wide but the release was per-deletion, so with two routers serving one deployment id, the first deletion put the survivor back on merging and the price it had just cleared would keep billing. Release the key only once no live router still serves that id. * fix(router): register a router in the live set when it gains a deployment _live_routers was only joined when a router was constructed with a model_list, but a router built empty is populated through add_deployment, and the empty branch exists for exactly that. Such a router was invisible to the live-router scan, so deleting the deployment from another router released the shared cost-map key while it was still serving that id. Joining the set where a deployment enters the list covers every path, and it also lets a price reload rebuild what a dynamically built router serves. * fix(e2e): read the stored model row from the control plane, not each gateway The lifecycle suite polled /model/info on every URL in PROXY_REPLICA_URLS. Those URLs are the stack's gateways, and gateway/routes/allowlist.py trims them to the LLM data-plane surface, so /model/info answers only on the backend and 404s on every replica. All five tests failed at their first read-back in CI while passing against a monolith, where one process serves both planes. The stored row has one answer behind it, so it is read through the shared transport, which routes control-plane paths to the backend. What every gateway must agree on is which models it serves, so the create and delete steps poll /v1/models per replica instead, a route the gateway does serve. read_back_everywhere now rejects a control-plane path outright rather than timing out on it. Two things surfaced behind that. /public/ was missing from the transport's control-plane prefixes, so model_cost_map() was routed to a gateway and 404'd, and the billing steps needed a data-plane wait: a PATCH lands on the backend and each gateway picks it up on its own config reload, measured here at 12-24s, so they now drive calls until the new rate reaches the spend row and let the deadline fail them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01C1S92J8gSxxKVe1JBzxWBF * test(models): keep polling outcomes immutable and document shared ownership * test: validate opaque stream IDs and hide log-reader credentials * test: isolate auto-router scenarios and clean partial setup --------- Co-authored-by: Claude Opus 5 --- .../model_management_endpoints.py | 70 +++- litellm/router.py | 26 +- tests/e2e/coverage_registry/mgmt.yaml | 2 + .../management/test_model_lifecycle_e2e.py | 365 ++++++++++++++++++ tests/e2e/models.py | 74 +++- tests/e2e/proxy_client.py | 199 +++++++++- tests/e2e/test_proxy_client.py | 57 ++- tests/e2e/transport.py | 1 + .../test_model_management_endpoints.py | 178 ++++++++- .../test_router_model_cost_isolation.py | 192 +++++++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 5 +- 11 files changed, 1130 insertions(+), 39 deletions(-) create mode 100644 tests/e2e/management/test_model_lifecycle_e2e.py diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 0f19e9ce149..742b9d9817f 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -119,6 +119,7 @@ from litellm.types.router import ( SPECIAL_MODEL_INFO_PARAMS, Deployment, GenericLiteLLMParams, + LiteLLM_Params, ModelInfo, updateDeployment, ) @@ -728,6 +729,44 @@ def _ptu_priced_deployment(model_params: Deployment) -> Deployment: ) +_OWNERSHIP_FIELDS: Final = frozenset( + { + "db_model", + "team_id", + "team_public_model_name", + "access_groups", + "created_at", + "created_by", + "updated_at", + "updated_by", + "blocked", + } +) + +_STORED_REQUIRED_FIELDS: Final = frozenset( + name for model in (LiteLLM_Params, ModelInfo) for name, field in model.model_fields.items() if field.is_required() +) + +_NULL_CLEAR_IGNORED_FIELDS: Final = _OWNERSHIP_FIELDS | _STORED_REQUIRED_FIELDS | frozenset(PTU_MODEL_INFO_FIELDS) + + +def _explicitly_cleared_fields(patch: BaseModel | None) -> frozenset[str]: + """The keys a patch sends as an explicit null, which update_db_model removes from the + stored blob (JSON Merge Patch). Ownership keys are left alone, as are the keys the stored + models require, since clearing one writes a row no reload can rebuild through + LiteLLM_Params / ModelInfo. The PTU keys are handled by _explicitly_cleared_ptu_fields, + whose clear is gated on the feature flag. Applied after both blobs merge, so a model_info blob + the UI echoes back cannot resurrect a pricing key the litellm_params patch clears. + """ + if patch is None: + return frozenset() + return frozenset( + field + for field in patch.model_fields_set + if field not in _NULL_CLEAR_IGNORED_FIELDS and getattr(patch, field) is None + ) + + def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> PrismaCompatibleUpdateDBModel: if updated_patch.model_info is not None: _raise_if_ptu_cost_attribution_disabled(updated_patch.model_info.model_dump(exclude_none=True)) @@ -748,25 +787,15 @@ def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> Pr if updated_patch.model_info: merged_model_info.update(updated_patch.model_info.model_dump(exclude_none=True)) - # Honor explicit-null clears LAST, after both merges, so a model_info blob the UI - # passes through (which today re-sends the OLD pricing on every save) cannot - # silently undo a litellm_params clear via .update(). - # - # Restricted to SPECIAL_MODEL_INFO_PARAMS (input/output cost per token/character - # and cache read/write costs) so this path cannot be used to null out privileged - # model_info fields like team_id or access groups. SPECIAL_MODEL_INFO_PARAMS are - # mirrored between litellm_params and model_info by Deployment.__init__, so the - # clear propagates to both blobs. - if updated_patch.litellm_params: - for field in updated_patch.litellm_params.model_fields_set: - if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.litellm_params, field) is None: - merged_litellm_params.pop(field, None) - merged_model_info.pop(field, None) + for field in _explicitly_cleared_fields(updated_patch.litellm_params): + merged_litellm_params.pop(field, None) + if field in SPECIAL_MODEL_INFO_PARAMS: + merged_model_info.pop(field, None) + for field in _explicitly_cleared_fields(updated_patch.model_info): + merged_model_info.pop(field, None) + if field in SPECIAL_MODEL_INFO_PARAMS: + merged_litellm_params.pop(field, None) if updated_patch.model_info: - for field in updated_patch.model_info.model_fields_set: - if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.model_info, field) is None: - merged_model_info.pop(field, None) - merged_litellm_params.pop(field, None) for field in _explicitly_cleared_ptu_fields(updated_patch.model_info): merged_model_info.pop(field, None) @@ -816,8 +845,9 @@ async def patch_model( """ PATCH Endpoint for partial model updates. - Only updates the fields specified in the request while preserving other existing values. - Follows proper PATCH semantics by only modifying provided fields. + JSON Merge Patch semantics over `litellm_params` and `model_info`: a key absent from the + body is unchanged, a key sent as null is removed from the stored row, and a key sent with a + value is set (identity and ownership keys such as `id` and `team_id` ignore a null). Args: model_id: The ID of the model to update diff --git a/litellm/router.py b/litellm/router.py index 934a4ac86a9..1252d7e7487 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -628,6 +628,15 @@ RETRY_BREADCRUMB_EXCLUDED_KWARGS: Final = frozenset( RETRY_BREADCRUMB_LIMIT: Final = 4 +# Cost-map keys created by _register_deployment_in_model_cost, which shares one flat +# namespace with the built-in model catalog. Only a key it created may be evicted, or a +# deployment whose id names a real model would strip that model's pricing and +# capabilities for every other deployment of it. delete_deployment gives a key back once no +# live router still serves that id, so a later catalog refresh that starts serving the name +# is not treated as a deployment's own. +_DEPLOYMENT_COST_MAP_KEYS: Final[set[str]] = set() # mutable-ok: ownership of shared cost-map keys + + class FallbackAwareStreamWrapper(CustomStreamWrapper): """Base for the Router's chat-completion stream wrappers, which are built around the attempt the Router picked first and have to repoint themselves when a fallback takes over.""" @@ -9761,6 +9770,7 @@ class Router: """ idx: Final = len(self.model_list) self.model_list.append(model) + _live_routers.add(self) # mutable-ok: track dynamic routers without extending their lifetimes self._invalidate_model_group_info_cache() self._invalidate_access_groups_cache() @@ -9929,7 +9939,12 @@ class Router: """Write a deployment's metadata into ``litellm.model_cost``. Runs when a deployment is added and again after a price data reload, so - the entries a refresh rebuilds are the ones a fresh boot would produce. + the entries a refresh rebuilds are the ones a fresh boot would produce. An + entry this function created is replaced rather than merged, so a price cleared + from the deployment does not linger from an earlier registration and keep + billing at the old rate. An entry it did not create is left to merge, because + a deployment id that collides with a catalog model name shares that model's + entry with every other deployment of it. Nothing is recorded for replay: a refresh walks the live routers instead, so a deleted, repointed or never-added deployment, and a discarded router, drop out of the rebuild on their own. @@ -9946,6 +9961,10 @@ class Router: } if model_id is not None: + if model_id in _DEPLOYMENT_COST_MAP_KEYS: + litellm.model_cost.pop(model_id, None) # mutable-ok: remove cleared prices from the shared entry + elif model_id not in litellm.model_cost: + _DEPLOYMENT_COST_MAP_KEYS.add(model_id) # mutable-ok: retain shared ownership across reloads litellm.register_model( model_cost={model_id: model_info}, persist_across_reloads=False, @@ -10042,6 +10061,11 @@ class Router: _budget_limiter: Final = self._get_router_deployment_budget_limiter() if _budget_limiter is not None: _budget_limiter.unregister_deployment_budget(model_id=id) + if not any( + router is not self and id in router.model_id_to_deployment_index_map + for router in tuple(_live_routers) + ): + _DEPLOYMENT_COST_MAP_KEYS.discard(id) # mutable-ok: the last owning router released this key try: self._unregister_pre_routing_strategy_for_deployment( deployment=item if isinstance(item, Deployment) else Deployment(**item) diff --git a/tests/e2e/coverage_registry/mgmt.yaml b/tests/e2e/coverage_registry/mgmt.yaml index c8d7037d2fd..83f1711a245 100644 --- a/tests/e2e/coverage_registry/mgmt.yaml +++ b/tests/e2e/coverage_registry/mgmt.yaml @@ -76,6 +76,8 @@ - {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)"} - {id: mgmt.credential.new.serves_request, module: mgmt, tier: P1, surface: api, assertions: [serves_request], source: "credential_endpoints/endpoints.py:42", rationale: "Stored credential resolves into a deployment and serves a live /messages request"} - {id: mgmt.model.test_connection.happy_path, module: mgmt, tier: P0, surface: api, assertions: [happy_path], source: "_health_endpoints.py:1785", rationale: "Test Connection for a responses-mode Bedrock Mantle deployment reaches the live provider and reports success; this exact shape 500ed on an acompletion partial before v1.91.0", fail_before_fix: proven} +- {id: mgmt.model.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "model_management_endpoints.py:731", rationale: "A partial PATCH changes only the keys it names; every other stored key reads back byte-for-byte, and the new rate reaches billing"} +- {id: mgmt.model.update.clear_persists, module: mgmt, tier: P0, surface: api, assertions: [clear_persists], source: "model_management_endpoints.py:731", fail_before_fix: proven, rationale: "An explicit null on PATCH removes the key from the stored row and billing falls back to the cost map; before the fix only mirrored pricing keys could be cleared"} - {id: mgmt.mcp_server.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1577", rationale: "Every field of an admin-created MCP server reads back verbatim, by id and in the list, on every replica"} - {id: mgmt.mcp_server.list.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1112", rationale: "The MCP page grid lists a created server with the same field values its detail view reports"} - {id: mgmt.mcp_server.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "mcp_management_endpoints.py:2665", rationale: "A dashboard edit of one field leaves the others intact and is visible on every replica after one save; edits that took several saves to stick were a customer defect"} diff --git a/tests/e2e/management/test_model_lifecycle_e2e.py b/tests/e2e/management/test_model_lifecycle_e2e.py new file mode 100644 index 00000000000..99044efeb46 --- /dev/null +++ b/tests/e2e/management/test_model_lifecycle_e2e.py @@ -0,0 +1,365 @@ +"""Live e2e: the lifecycle of a DB-stored deployment through the model management +routes, read back on every gateway replica. + +Each test registers its own gpt-4o-mini mock deployment through /model/new (deleted +on teardown) with non-default pricing, context window, mode, and api_base pinned, then +walks the lifecycle up to the step it proves: the create reads back field for field, +a partial PATCH changes only the key it names, an explicit null on PATCH removes the +key from the stored row (JSON Merge Patch), a call after the price clear is billed at +the cost map's rate rather than the cleared override, and a delete removes the +deployment from /model/info and makes the model name unknown to /chat/completions. + +The stored row is read back from /model/info, a control-plane route with one answer +behind it. What every gateway must agree on is which models it serves, so the create +and delete steps poll /v1/models on every URL in PROXY_REPLICA_URLS through +ProxyClient.read_model_back_everywhere, failing by name on the gateway that never converged. +""" + +from __future__ import annotations + +import math +import time +from collections.abc import Callable +from dataclasses import dataclass +from typing import Final + +import pytest +from pydantic import BaseModel + +from e2e_config import unique_marker +from e2e_http import unwrap +from lifecycle import ResourceManager +from management_client import ManagementClient +from models import ( + ChatBody, + ChatMessage, + Clear, + LiteLLMParamsBody, + LiteLLMParamsPatch, + ModelInfoBody, + ModelInfoEntry, + ModelInfoResponse, + ModelNewBody, + ModelPatchBody, + ModelsListResponse, + SpendLogRow, +) + +pytestmark = pytest.mark.e2e + +BACKEND_MODEL: Final = "gpt-4o-mini" +PINNED_API_BASE: Final = "https://pinned.example.invalid/v1" +PINNED_MAX_INPUT_TOKENS: Final = 4096 +PINNED_INPUT_RATE: Final = 1e-05 +UPDATED_INPUT_RATE: Final = 2e-05 +PINNED_OUTPUT_RATE: Final = 3e-05 + +# A PATCH lands on the control plane, and each gateway picks it up on its own config +# reload, so the first call after the write can still be billed at the old rate. There +# is no price on the gateway's data-plane surface to poll, so the billing steps drive +# calls until the new rate shows up in the spend row and let the deadline be what fails. +BILLING_CONVERGENCE_TIMEOUT: Final = 90.0 +BILLING_CONVERGENCE_INTERVAL: Final = 5.0 + + +@dataclass(frozen=True, slots=True) +class Registered: + model_name: str + model_id: str + + +class _ErrorDetail(BaseModel): + message: str + + +class _ErrorEnvelope(BaseModel): + error: _ErrorDetail + + +def _register(client: ManagementClient, resources: ResourceManager) -> Registered: + """Register a mock gpt-4o-mini deployment with every field under test pinned to a + non-default value, deleted on teardown. max_input_tokens is pinned in + litellm_params only: a value in model_info is copied into the shared cost-map + entry for the backend model, which would leak into every other gpt-4o-mini + deployment on the proxy.""" + model_name: Final = f"e2e-lifecycle-{unique_marker()}" + model_id: Final = client.proxy.register_model( + ModelNewBody( + model_name=model_name, + litellm_params=LiteLLMParamsBody( + model=BACKEND_MODEL, + mock_response="ok", + api_base=PINNED_API_BASE, + input_cost_per_token=PINNED_INPUT_RATE, + output_cost_per_token=PINNED_OUTPUT_RATE, + max_input_tokens=PINNED_MAX_INPUT_TOKENS, + ), + model_info=ModelInfoBody(mode="chat"), + ) + ) + resources.defer(lambda: client.proxy.delete_model(model_id)) + return Registered(model_name=model_name, model_id=model_id) + + +def _entry(body: ModelInfoResponse, model_name: str) -> ModelInfoEntry | None: + return next((entry for entry in body.data if entry.model_name == model_name), None) + + +def _stored_entry( + client: ManagementClient, + model_name: str, + *, + converged: Callable[[ModelInfoEntry], bool], +) -> ModelInfoEntry: + """The stored /model/info row for `model_name`, once it satisfies `converged`. + + /model/info is a control-plane route: the gateways named in PROXY_REPLICA_URLS + serve the LLM surface only, so the stored row has one answer, not one per + gateway. What every gateway must agree on is which models it serves, and + `_assert_served_everywhere` / `_assert_absent_everywhere` poll /v1/models for + that.""" + + def has_converged(body: ModelInfoResponse) -> bool: + entry: Final = _entry(body, model_name) + return entry is not None and converged(entry) + + body: Final = client.proxy.read_model_back("/model/info", ModelInfoResponse, predicate=has_converged) + entry: Final = _entry(body, model_name) + assert entry is not None, f"/model/info stopped listing {model_name!r} between the poll and the read" + return entry + + +def _serves(body: ModelsListResponse, model_name: str) -> bool: + return any(entry.id == model_name for entry in body.data) + + +def _assert_served_everywhere(client: ManagementClient, model_name: str) -> None: + _ = client.proxy.read_model_back_everywhere( + "/v1/models", ModelsListResponse, predicate=lambda body: _serves(body, model_name) + ) + + +def _assert_absent_everywhere(client: ManagementClient, model_name: str) -> None: + _ = client.proxy.read_model_back_everywhere( + "/v1/models", ModelsListResponse, predicate=lambda body: not _serves(body, model_name) + ) + + +def _assert_untouched_keys_as_created(entry: ModelInfoEntry, replica: str) -> None: + """The keys no later step names read back byte-for-byte as /model/new wrote them.""" + params: Final = entry.litellm_params + assert params.model == BACKEND_MODEL, f"{replica}: litellm_params.model {params.model!r} != {BACKEND_MODEL!r}" + assert params.api_base == PINNED_API_BASE, f"{replica}: api_base {params.api_base!r} != {PINNED_API_BASE!r}" + assert params.output_cost_per_token == PINNED_OUTPUT_RATE, ( + f"{replica}: output_cost_per_token {params.output_cost_per_token} != {PINNED_OUTPUT_RATE}" + ) + assert entry.model_info.mode == "chat", f"{replica}: model_info.mode {entry.model_info.mode!r} != 'chat'" + + +def _approx_equal(actual: float, expected: float) -> bool: + return math.isclose(actual, expected, rel_tol=1e-2, abs_tol=1e-9) + + +def _priced(rows: list[SpendLogRow]) -> bool: + return any(row.metadata and row.metadata.cost_breakdown and row.metadata.cost_breakdown.input_cost for row in rows) + + +def _billed_input_cost(client: ManagementClient, model_name: str, key: str) -> tuple[int, float]: + """Drive one chat completion through `model_name` and return the prompt tokens and + input cost its spend row recorded, so a test can assert the rate the gateway actually + billed rather than only the rate it stored.""" + chat: Final = unwrap( + client.proxy.chat( + key, + ChatBody( + model=model_name, + messages=[ChatMessage(role="user", content=f"reply with one word {unique_marker()}")], + max_tokens=16, + ), + ) + ) + assert chat.id is not None, f"chat completion carried no id to find its spend row by: {chat}" + + rows: Final = client.proxy.poll_logs_for_request_id(chat.id, predicate=_priced) + row: Final = next((row for row in rows if row.request_id == chat.id), None) + assert row is not None and row.metadata and row.metadata.cost_breakdown, ( + f"no priced spend row for request {chat.id} before the deadline: {rows}" + ) + prompt_tokens: Final = row.prompt_tokens or 0 + input_cost: Final = row.metadata.cost_breakdown.input_cost + assert prompt_tokens > 0 and input_cost is not None, f"spend row logged no prompt tokens or input cost: {row}" + return prompt_tokens, input_cost + + +def _await_billed_input_cost( + client: ManagementClient, model_name: str, key: str, *, expected_rate: float +) -> tuple[int, float]: + """Drive calls through `model_name` until one is billed at `expected_rate`, and + return the prompt tokens and input cost of the last spend row either way. + + Only the deadline ends the wait unsatisfied: a rate that never reaches the gateway + comes back as the stale cost for the caller to assert on, so the rate the caller + expects is still what decides the test.""" + deadline: Final = time.monotonic() + BILLING_CONVERGENCE_TIMEOUT + while True: + prompt_tokens, input_cost = _billed_input_cost(client, model_name, key) + if _approx_equal(input_cost, prompt_tokens * expected_rate) or time.monotonic() >= deadline: + return prompt_tokens, input_cost + time.sleep(BILLING_CONVERGENCE_INTERVAL) + + +class TestModelLifecycle: + @pytest.mark.covers("mgmt.model.add.persists") + def test_create_reads_back_every_field_and_serves_on_every_replica( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + registered = _register(client, resources) + + entry = _stored_entry(client, registered.model_name, converged=lambda _entry: True) + stored = "/model/info" + + _assert_untouched_keys_as_created(entry, stored) + assert entry.litellm_params.input_cost_per_token == PINNED_INPUT_RATE, ( + f"{stored}: input_cost_per_token {entry.litellm_params.input_cost_per_token} != {PINNED_INPUT_RATE}" + ) + assert entry.litellm_params.max_input_tokens == PINNED_MAX_INPUT_TOKENS, ( + f"{stored}: max_input_tokens {entry.litellm_params.max_input_tokens} != {PINNED_MAX_INPUT_TOKENS}" + ) + assert entry.model_info.id == registered.model_id, ( + f"{stored}: model_info.id {entry.model_info.id!r} != {registered.model_id!r}" + ) + + _assert_served_everywhere(client, registered.model_name) + + @pytest.mark.covers("mgmt.model.update.preserves_unrelated_fields") + def test_partial_update_changes_only_the_named_key( + self, client: ManagementClient, resources: ResourceManager, scoped_key: str + ) -> None: + registered = _register(client, resources) + + stored = client.proxy.patch_model( + registered.model_id, + ModelPatchBody(litellm_params=LiteLLMParamsPatch(input_cost_per_token=UPDATED_INPUT_RATE)), + ) + assert stored.litellm_params.input_cost_per_token == UPDATED_INPUT_RATE, ( + f"PATCH response stores input_cost_per_token {stored.litellm_params.input_cost_per_token}, " + f"sent {UPDATED_INPUT_RATE}" + ) + + entry = _stored_entry( + client, + registered.model_name, + converged=lambda entry: entry.litellm_params.input_cost_per_token == UPDATED_INPUT_RATE, + ) + stored = "/model/info" + + _assert_untouched_keys_as_created(entry, stored) + assert entry.litellm_params.max_input_tokens == PINNED_MAX_INPUT_TOKENS, ( + f"{stored}: max_input_tokens {entry.litellm_params.max_input_tokens} != {PINNED_MAX_INPUT_TOKENS}" + ) + assert entry.model_info.input_cost_per_token == UPDATED_INPUT_RATE, ( + f"{stored}: model_info.input_cost_per_token {entry.model_info.input_cost_per_token} " + f"did not mirror the updated {UPDATED_INPUT_RATE}" + ) + + prompt_tokens, input_cost = _await_billed_input_cost( + client, registered.model_name, scoped_key, expected_rate=UPDATED_INPUT_RATE + ) + assert _approx_equal(input_cost, prompt_tokens * UPDATED_INPUT_RATE), ( + f"input_cost {input_cost} != {prompt_tokens} tokens * updated rate {UPDATED_INPUT_RATE} " + f"= {prompt_tokens * UPDATED_INPUT_RATE}; the partial update did not reach billing" + ) + + @pytest.mark.covers("mgmt.model.update.clear_persists") + def test_explicit_null_removes_the_key_from_the_stored_row( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + registered = _register(client, resources) + + stored = client.proxy.patch_model( + registered.model_id, + ModelPatchBody(litellm_params=LiteLLMParamsPatch(max_input_tokens=Clear(), input_cost_per_token=Clear())), + ) + stored_params = stored.litellm_params.model_fields_set + assert "max_input_tokens" not in stored_params, ( + f"stored litellm_params still carries max_input_tokens " + f"{stored.litellm_params.max_input_tokens} after an explicit null" + ) + assert "input_cost_per_token" not in stored_params, ( + f"stored litellm_params still carries input_cost_per_token " + f"{stored.litellm_params.input_cost_per_token} after an explicit null" + ) + assert "max_input_tokens" not in stored.model_info.model_fields_set, ( + f"stored model_info carries max_input_tokens {stored.model_info.max_input_tokens} after the clear" + ) + assert "input_cost_per_token" not in stored.model_info.model_fields_set, ( + f"stored model_info still mirrors input_cost_per_token {stored.model_info.input_cost_per_token}" + ) + + cost_map_input_rate = client.proxy.model_cost_map()[BACKEND_MODEL].input_cost_per_token + assert cost_map_input_rate is not None, f"cost map has no input rate for {BACKEND_MODEL}" + entry = _stored_entry( + client, + registered.model_name, + converged=lambda entry: "max_input_tokens" not in entry.litellm_params.model_fields_set, + ) + stored = "/model/info" + + _assert_untouched_keys_as_created(entry, stored) + served = entry.litellm_params.model_fields_set + assert "max_input_tokens" not in served, ( + f"{stored}: litellm_params still serves max_input_tokens {entry.litellm_params.max_input_tokens}" + ) + assert "input_cost_per_token" not in served, ( + f"{stored}: litellm_params still serves input_cost_per_token {entry.litellm_params.input_cost_per_token}" + ) + assert entry.model_info.input_cost_per_token == cost_map_input_rate, ( + f"{stored}: model_info.input_cost_per_token {entry.model_info.input_cost_per_token} is not the " + f"cost map's {cost_map_input_rate}; the cleared override {PINNED_INPUT_RATE} still resolves" + ) + + @pytest.mark.covers("mgmt.model.update.clear_persists") + def test_cleared_price_is_billed_at_the_cost_map_rate( + self, client: ManagementClient, resources: ResourceManager, scoped_key: str + ) -> None: + registered = _register(client, resources) + _ = client.proxy.patch_model( + registered.model_id, + ModelPatchBody(litellm_params=LiteLLMParamsPatch(max_input_tokens=Clear(), input_cost_per_token=Clear())), + ) + _ = _stored_entry( + client, + registered.model_name, + converged=lambda entry: "input_cost_per_token" not in entry.litellm_params.model_fields_set, + ) + cost_map_input_rate = client.proxy.model_cost_map()[BACKEND_MODEL].input_cost_per_token + assert cost_map_input_rate is not None, f"cost map has no input rate for {BACKEND_MODEL}" + + prompt_tokens, input_cost = _await_billed_input_cost( + client, registered.model_name, scoped_key, expected_rate=cost_map_input_rate + ) + + assert _approx_equal(input_cost, prompt_tokens * cost_map_input_rate), ( + f"input_cost {input_cost} != {prompt_tokens} tokens * cost map rate {cost_map_input_rate} " + f"= {prompt_tokens * cost_map_input_rate}" + ) + assert not _approx_equal(input_cost, prompt_tokens * PINNED_INPUT_RATE), ( + f"input_cost {input_cost} is still billed at the cleared override {PINNED_INPUT_RATE}" + ) + + @pytest.mark.covers("mgmt.model.delete.persists") + def test_delete_removes_the_deployment_everywhere( + self, client: ManagementClient, resources: ResourceManager, scoped_key: str + ) -> None: + registered = _register(client, resources) + _ = _stored_entry(client, registered.model_name, converged=lambda _entry: True) + + client.delete_model_strict(registered.model_id) + + _assert_absent_everywhere(client, registered.model_name) + refused = client.chat_status(scoped_key, registered.model_name, "hi this is a test") + assert refused.status_code == 400, ( + f"chat against the deleted model must be rejected 400, got {refused.status_code}: {refused.body[:300]}" + ) + envelope = _ErrorEnvelope.model_validate_json(refused.body) + assert envelope.error.message, f"400 body must carry an error message: {refused.body[:300]}" diff --git a/tests/e2e/models.py b/tests/e2e/models.py index 62810e6cfd9..8db37bd25a5 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -655,6 +655,11 @@ class OcrResponse(BaseModel): # ---------- spend logs ---------- +class CostBreakdown(BaseModel): + input_cost: float | None = None + output_cost: float | None = None + + class GuardrailEntityMatch(BaseModel): entity_type: str score: float @@ -672,6 +677,7 @@ class GuardrailRunRecord(BaseModel): class SpendLogMetadata(BaseModel): + cost_breakdown: CostBreakdown | None = None applied_guardrails: list[str] | None = None guardrail_information: list[GuardrailRunRecord] | None = None @@ -815,15 +821,42 @@ class CustomPricing(BaseModel): return prompt_tokens * self.input_cost_per_token + completion_tokens * self.output_cost_per_token +class DeploymentParams(CustomPricing): + """The litellm_params half of a /model/info row: the stored deployment as written, + credentials scrubbed. Unlike model_info it is never back-filled from the cost map, + so a key the store dropped is absent here (check `model_fields_set`).""" + + model: str | None = None + api_base: str | None = None + max_input_tokens: int | None = None + + +class DeploymentModelInfo(CustomPricing): + id: str | None = None + max_input_tokens: int | None = None + + class ModelInfoEntry(BaseModel): """One /model/info row. `litellm_params` is the configured deployment (carries any custom-pricing override); `model_info` is the price the proxy resolved for - it - the override merged over the cost-map defaults.""" + it - the override merged over the cost-map defaults, so a key cleared from the + stored blob reads as the cost-map default here.""" model_config = ConfigDict(protected_namespaces=()) model_name: str - litellm_params: CustomPricing = CustomPricing() - model_info: CustomPricing = CustomPricing() + litellm_params: DeploymentParams = DeploymentParams() + model_info: DeploymentModelInfo = DeploymentModelInfo() + + +class StoredDeployment(BaseModel): + """PATCH /model/{model_id}/update answers with the row as stored: both blobs raw, + nothing back-filled, so a cleared key is absent from `model_fields_set` of the + blob it was cleared from.""" + + model_config = ConfigDict(protected_namespaces=()) + model_name: str + litellm_params: DeploymentParams + model_info: DeploymentModelInfo class ModelInfoResponse(BaseModel): @@ -924,9 +957,10 @@ class LiteLLMParamsBody(BaseModel): timeout: float | None = None tpm: int | None = None weight: int | None = None + max_input_tokens: int | None = None -ModelMode = Literal["batch", "realtime", "image_generation"] +ModelMode = Literal["chat", "batch", "realtime", "image_generation"] class ModelInfoBody(BaseModel): @@ -936,6 +970,7 @@ class ModelInfoBody(BaseModel): # constraint when a prior run's teardown had not removed the row. id: str | None = None mode: ModelMode | None = None + max_input_tokens: int | None = None access_groups: list[str] | None = None team_id: str | None = None allowed_fails_policy: dict[str, int] | None = None @@ -964,6 +999,37 @@ class ModelUpdateBody(BaseModel): model_info: ModelInfoBody +class Clear(BaseModel): + """Serializes to JSON null. The transport dumps every body with exclude_none, so a + field set to this is how a patch carries the explicit null that removes a stored key.""" + + @model_serializer + def _as_null(self) -> None: + return None + + +class LiteLLMParamsPatch(BaseModel): + api_base: str | Clear | None = None + max_input_tokens: int | Clear | None = None + input_cost_per_token: float | Clear | None = None + output_cost_per_token: float | Clear | None = None + + +class ModelInfoPatch(BaseModel): + mode: ModelMode | Clear | None = None + max_input_tokens: int | Clear | None = None + + +class ModelPatchBody(BaseModel): + """PATCH /model/{model_id}/update body, JSON Merge Patch over the stored deployment: + a field left None is dropped from the body and unchanged, a field set to `Clear()` + is sent as null and removed, a field with a value is set.""" + + model_config = ConfigDict(protected_namespaces=()) + litellm_params: LiteLLMParamsPatch | None = None + model_info: ModelInfoPatch | None = None + + class ModelListEntry(BaseModel): id: str diff --git a/tests/e2e/proxy_client.py b/tests/e2e/proxy_client.py index 1bac5116a9d..fa1b06fe7ed 100644 --- a/tests/e2e/proxy_client.py +++ b/tests/e2e/proxy_client.py @@ -10,10 +10,10 @@ from __future__ import annotations import time import warnings -from collections.abc import Callable, Mapping +from collections.abc import Callable, Iterator, Mapping from dataclasses import dataclass -from functools import reduce from datetime import datetime +from functools import reduce from types import MappingProxyType from typing import Final @@ -62,6 +62,7 @@ from models import ( ModelMode, ModelNewBody, ModelNewResponse, + ModelPatchBody, ModelsListParams, ModelsListResponse, ModelUpdateBody, @@ -72,6 +73,7 @@ from models import ( SpendLogsPage, SpendLogsPageParams, SpendLogsParams, + StoredDeployment, ToolsetCreateBody, ToolsetRow, ToolsetUpdateBody, @@ -132,6 +134,103 @@ class NotServableOn: last_result: Result[ModelsListResponse] | None +type BodyReader[R: BaseModel] = Callable[[float], Result[R]] + + +@dataclass(frozen=True, slots=True) +class BodyNotConverged[R: BaseModel]: + """The deadline passed without a read the predicate accepted; `last_result` is the + final read, so the caller can tell a body that never matched from a read that + failed.""" + + last_result: Result[R] | None + + +@dataclass(frozen=True, slots=True) +class BodyConverged[R: BaseModel]: + """Every replica answered a body the predicate accepted; `bodies` is the last read + per replica.""" + + bodies: Mapping[str, R] + + +@dataclass(frozen=True, slots=True) +class BodyNeverConvergedOn[R: BaseModel]: + """`BodyNotConverged` labeled with the replica whose reads never satisfied the predicate.""" + + replica: str + last_result: Result[R] | None + + +def await_body_converged[R: BaseModel]( + read: BodyReader[R], + *, + predicate: Callable[[R], bool], + timeout: float, + interval: float, + request_timeout: float, + now: Callable[[], float], + sleep: Callable[[float], None], +) -> Success[R] | BodyNotConverged[R]: + """Poll `read` until it answers a body `predicate` accepts, or `timeout` passes. + + Each read's request timeout is clamped to the remaining budget, and the sleep + between reads to the time left, so the last read before the deadline is never + skipped. Clock and sleep are injected.""" + deadline: Final = now() + timeout + + def reads() -> Iterator[Result[R]]: + while (remaining := deadline - now()) > 0: + yield read(min(request_timeout, remaining)) + sleep(min(interval, max(deadline - now(), 0.0))) + + def attempts() -> Iterator[Success[R] | BodyNotConverged[R]]: + for result in reads(): + if isinstance(result, Success) and predicate(result.data): + yield result + return + yield BodyNotConverged(last_result=result) + + initial: Final[Success[R] | BodyNotConverged[R]] = BodyNotConverged(last_result=None) + return reduce(lambda _previous, result: result, attempts(), initial) + + +def await_body_converged_everywhere[R: BaseModel]( + readers: Mapping[str, BodyReader[R]], + *, + predicate: Callable[[R], bool], + timeout: float, + interval: float, + request_timeout: float, + now: Callable[[], float], + sleep: Callable[[float], None], +) -> BodyConverged[R] | BodyNeverConvergedOn[R]: + """`await_body_converged` against every replica in turn, each with the full budget, so a + write counts as landed only once every replica serves it.""" + def read_replica( + outcome: BodyConverged[R] | BodyNeverConvergedOn[R], + item: tuple[str, BodyReader[R]], + ) -> BodyConverged[R] | BodyNeverConvergedOn[R]: + if isinstance(outcome, BodyNeverConvergedOn): + return outcome + replica, read = item + match await_body_converged( + read, + predicate=predicate, + timeout=timeout, + interval=interval, + request_timeout=request_timeout, + now=now, + sleep=sleep, + ): + case Success(data=data): + return BodyConverged(bodies=MappingProxyType({**outcome.bodies, replica: data})) + case BodyNotConverged(last_result=last_result): + return BodyNeverConvergedOn(replica=replica, last_result=last_result) + initial: Final[BodyConverged[R] | BodyNeverConvergedOn[R]] = BodyConverged(bodies=MappingProxyType({})) + return reduce(read_replica, readers.items(), initial) + + def await_servable( list_models: Callable[[float], Result[ModelsListResponse]], *, @@ -658,6 +757,102 @@ class ProxyClient: ) ) + def patch_model(self, model_id: str, body: ModelPatchBody) -> StoredDeployment: + """JSON Merge Patch the deployment `model_id` via PATCH /model/{model_id}/update: + a field the body omits is unchanged, one sent as null is removed from the stored + row, one sent with a value is set. See ModelPatchBody for how a null is sent. + Returns the row as stored after the write.""" + return unwrap( + self.transport.patch( + f"/model/{model_id}/update", + headers=self.transport.master, + json=body, + response_type=StoredDeployment, + ) + ) + + def read_model_back_everywhere[R: BaseModel]( + self, path: str, response_type: type[R], *, predicate: Callable[[R], bool] + ) -> Mapping[str, R]: + """GET `path` on every replica until each answers a body `predicate` accepts, + polling to poll_timeout, and return the last body per replica. + + Fails naming the replica that never converged, so a write that reached one + gateway but not the others is caught instead of passing on whichever gateway + the balancer answered from. Falls back to the single proxy address when no + replica list is configured. + + `path` must be a data-plane route. The replicas are gateways, which serve only + the LLM surface, so a control-plane path answers on exactly one service and + 404s on every replica in a split deployment: asking each replica for one is + never the question the caller means. Read those through `self.transport` + instead, which routes them to the control plane.""" + if is_control_plane_path(path): + raise AssertionError( + f"read_model_back_everywhere({path!r}) asks every data-plane replica for a control-plane route. " + "The replicas are gateways and do not serve it; poll a data-plane path such as /v1/models " + "here, and read the control plane through the shared transport." + ) + readers: Final = { + url: self._body_reader(transport, path, response_type) + for url, transport in self._read_back_replicas().items() + } + outcome: Final = await_body_converged_everywhere( + readers, + predicate=predicate, + timeout=self.poll_timeout, + interval=self.poll_interval, + request_timeout=REQUEST_TIMEOUT, + now=time.monotonic, + sleep=time.sleep, + ) + match outcome: + case BodyConverged(bodies=bodies): + return bodies + case BodyNeverConvergedOn(replica=replica, last_result=last_result): + raise AssertionError( + f"GET {path} on {replica} never answered the expected body within " + f"{self.poll_timeout}s; last read: {last_result}" + ) + + def read_model_back[R: BaseModel](self, path: str, response_type: type[R], *, predicate: Callable[[R], bool]) -> R: + """GET `path` through the shared transport until the body satisfies `predicate`, + polling to poll_timeout, and return that body. + + The counterpart to `read_model_back_everywhere` for a control-plane route such as + /model/info: the stored row lives in one database behind one control plane, so + there is a single answer to converge on rather than one per gateway.""" + outcome: Final = await_body_converged_everywhere( + {CONTROL_PLANE_BASE_URL: self._body_reader(self.transport, path, response_type)}, + predicate=predicate, + timeout=self.poll_timeout, + interval=self.poll_interval, + request_timeout=REQUEST_TIMEOUT, + now=time.monotonic, + sleep=time.sleep, + ) + match outcome: + case BodyConverged(bodies=bodies): + return bodies[CONTROL_PLANE_BASE_URL] + case BodyNeverConvergedOn(last_result=last_result): + raise AssertionError( + f"GET {path} never answered the expected body within " + f"{self.poll_timeout}s; last read: {last_result}" + ) + + def _read_back_replicas(self) -> Mapping[str, Transport]: + return self.replicas or MappingProxyType({CONTROL_PLANE_BASE_URL: self.transport}) + + @staticmethod + def _body_reader[R: BaseModel](transport: Transport, path: str, response_type: type[R]) -> BodyReader[R]: + return lambda timeout: transport.get( + path, + headers=transport.master, + params=NoBody(), + response_type=response_type, + timeout=timeout, + ) + def delete_model(self, model_id: str) -> None: result = self.transport.post( "/model/delete", diff --git a/tests/e2e/test_proxy_client.py b/tests/e2e/test_proxy_client.py index 3b84a47e3cc..cbf7f5648d4 100644 --- a/tests/e2e/test_proxy_client.py +++ b/tests/e2e/test_proxy_client.py @@ -20,8 +20,12 @@ from typing import Final, cast import pytest from e2e_config import parse_replica_urls from e2e_http import Result, Success -from models import KeyInfo, KeyInfoResponse, ModelListEntry, ModelsListResponse +from models import KeyInfo, KeyInfoResponse, ModelInfoEntry, ModelInfoResponse, ModelListEntry, ModelsListResponse from proxy_client import ( + BodyReader, + BodyConverged, + BodyNeverConvergedOn, + await_body_converged_everywhere, ConvergeOutcome, Converged, EverywhereConverged, @@ -274,3 +278,54 @@ class TestReplicasFor: client: Final = ProxyClient(transport=_NO_TRANSPORTS, replicas={}, control_replicas={}) with pytest.raises(AssertionError, match="no replica is configured"): _ = client.replicas_for("/v1/models") + + +def _info(*model_names: str) -> Success[ModelInfoResponse]: + entries: Final = [ModelInfoEntry(model_name=model_name) for model_name in model_names] + return Success(status_code=200, data=ModelInfoResponse(data=entries)) + + +def _reader(results: Iterable[Success[ModelInfoResponse]]) -> BodyReader[ModelInfoResponse]: + it: Final = iter(results) + return lambda _timeout: next(it) + + +def _lists_model(body: ModelInfoResponse) -> bool: + return any(entry.model_name == MODEL for entry in body.data) + + +def _read_back( + readers: Mapping[str, BodyReader[ModelInfoResponse]], +) -> tuple[BodyConverged[ModelInfoResponse] | BodyNeverConvergedOn[ModelInfoResponse], FakeClock]: + clock: Final = FakeClock() + outcome: Final = await_body_converged_everywhere( + readers, + predicate=_lists_model, + timeout=TIMEOUT, + interval=INTERVAL, + request_timeout=5.0, + now=clock.now, + sleep=clock.sleep, + ) + return outcome, clock + + +class TestAwaitBodyConvergedEverywhere: + def test_waits_for_the_lagging_replica_and_returns_every_body(self) -> None: + readers: Final = { + "gateway-1": _reader(repeat(_info(MODEL))), + "gateway-2": _reader(chain(repeat(_info(), 2), repeat(_info(MODEL)))), + } + outcome, clock = _read_back(readers) + assert outcome == BodyConverged(bodies={"gateway-1": _info(MODEL).data, "gateway-2": _info(MODEL).data}) + assert clock.elapsed == 2 * INTERVAL + + @pytest.mark.parametrize("lagging", ["gateway-1", "gateway-2"]) + def test_fails_naming_the_replica_that_never_converges(self, lagging: str) -> None: + readers: Final = { + "gateway-1": _reader(repeat(_info(MODEL))), + "gateway-2": _reader(repeat(_info(MODEL))), + } | {lagging: _reader(repeat(_info()))} + outcome, clock = _read_back(readers) + assert outcome == BodyNeverConvergedOn(replica=lagging, last_result=_info()) + assert clock.elapsed >= TIMEOUT diff --git a/tests/e2e/transport.py b/tests/e2e/transport.py index 44fdbaa3e41..804e073a4a0 100644 --- a/tests/e2e/transport.py +++ b/tests/e2e/transport.py @@ -306,6 +306,7 @@ CONTROL_PLANE_PREFIXES: tuple[str, ...] = ( "/config", "/guardrails", "/openapi.json", + "/public/", ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index c02f886fc31..1376727e296 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -3115,9 +3115,6 @@ class TestUpdateDBModelClearPricing: """Sending an explicit `null` for a pricing field must remove it from both `litellm_params` and `model_info` (SPECIAL_MODEL_INFO_PARAMS are mirrored between the two by Deployment.__init__). - - Restricted to SPECIAL_MODEL_INFO_PARAMS so non-pricing fields (e.g. team_id) - cannot be cleared via this path. """ def test_clear_input_cost_removes_from_both_blobs(self): @@ -3193,10 +3190,10 @@ class TestUpdateDBModelClearPricing: assert params["input_cost_per_token"] == 0.000001 assert params["output_cost_per_token"] == 0.000007 - def test_null_on_non_pricing_field_does_not_clear(self): - """Security guard: only SPECIAL_MODEL_INFO_PARAMS can be cleared via null. - Privileged or unrelated model_info fields (e.g. team_id) must be unaffected - by the null-clearing path so a team admin can't ungate a team-scoped model. + def test_null_on_one_field_leaves_other_fields_alone(self): + """A null clears only the key it names: pricing the patch never mentions and + the ownership key team_id stay put, so a team admin can't ungate a + team-scoped model through the clear path. """ from litellm.proxy.management_endpoints.model_management_endpoints import ( update_db_model, @@ -3217,8 +3214,6 @@ class TestUpdateDBModelClearPricing: model_info=ModelInfo(id="dep-pricing-1", team_id="team-keep-me"), ) - # Patch sends a null for api_base (non-SPECIAL field). Must NOT clear team_id - # or any other non-pricing field from the merged dict. result = update_db_model( db_model=db_model, updated_patch=updateDeployment( @@ -3394,6 +3389,171 @@ class TestUpdateDBModelClearPricing: assert info["cache_creation_input_token_cost"] == 0.000003 +_PROTECTED_MODEL_INFO_VALUES = { + "team_id": "team-keep-me", + "team_public_model_name": "team-facing-name", + "access_groups": ["group-a"], + "created_at": "2026-01-01T00:00:00+00:00", + "created_by": "creator", + "updated_at": "2026-01-02T00:00:00+00:00", + "updated_by": "updater", + "blocked": True, +} + + +def _build_db_model_with_pinned_model_info(): + """Deployment whose stored blobs pin non-pricing keys an earlier save wrote, next to a + pricing override, so a clear can be checked key by key.""" + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + return Deployment( + model_name="pinned-gpt-4o-mini", + litellm_params=LiteLLM_Params( + model="gpt-4o-mini", input_cost_per_token=0.000001, max_input_tokens=4096 + ), + model_info=ModelInfo( + id="dep-pinned-0", + max_input_tokens=4096, + mode="chat", + supports_vision=True, + **_PROTECTED_MODEL_INFO_VALUES, + ), + ) + + +class TestUpdateDBModelNullClearsAnyKey: + """JSON Merge Patch on PATCH /model/{id}/update: a key sent as null is removed from the + stored blob it was sent in, whatever the key, except the identity and ownership keys, + whose nulls are ignored.""" + + def test_model_info_nulls_remove_pinned_non_pricing_keys(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + result = update_db_model( + db_model=_build_db_model_with_pinned_model_info(), + updated_patch=updateDeployment.model_validate( + {"model_info": {"max_input_tokens": None, "mode": None}} + ), + ) + + info = json.loads(result["model_info"]) + assert "max_input_tokens" not in info + assert "mode" not in info + assert info["supports_vision"] is True + assert info["input_cost_per_token"] == 0.000001 + + def test_litellm_params_null_removes_pinned_non_pricing_key(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + result = update_db_model( + db_model=_build_db_model_with_pinned_model_info(), + updated_patch=updateDeployment.model_validate( + {"litellm_params": {"max_input_tokens": None}} + ), + ) + + params = json.loads(result["litellm_params"]) + info = json.loads(result["model_info"]) + assert "max_input_tokens" not in params + assert params["model"] == "gpt-4o-mini" + assert params["input_cost_per_token"] == 0.000001 + assert info["max_input_tokens"] == 4096 + + def test_omitted_key_is_untouched_by_a_null_elsewhere(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + result = update_db_model( + db_model=_build_db_model_with_pinned_model_info(), + updated_patch=updateDeployment.model_validate( + {"model_info": {"mode": None, "supports_vision": False}} + ), + ) + + info = json.loads(result["model_info"]) + assert "mode" not in info + assert info["supports_vision"] is False + assert info["max_input_tokens"] == 4096 + + @pytest.mark.parametrize("field", sorted(_PROTECTED_MODEL_INFO_VALUES)) + def test_null_on_protected_key_is_ignored(self, field): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + result = update_db_model( + db_model=_build_db_model_with_pinned_model_info(), + updated_patch=updateDeployment.model_validate({"model_info": {field: None}}), + ) + + info = json.loads(result["model_info"]) + assert info[field] == _PROTECTED_MODEL_INFO_VALUES[field] + assert info["max_input_tokens"] == 4096 + + def test_echoing_the_read_back_blob_preserves_every_stored_key(self): + """The Admin UI edit form submits the whole /model/info row back, and that read reports + every key the deployment never stored as an explicit null. Those nulls have to stay + no-ops: a write drops None before storing, so a null in the echoed blob always names a + key the stored row does not carry. + """ + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + db_model = _build_db_model_with_pinned_model_info() + echoed = { + "id": "dep-pinned-0", + "max_input_tokens": 4096, + "mode": "chat", + "supports_vision": True, + "input_cost_per_token": 0.000001, + "team_id": "team-keep-me", + "base_model": None, + "tier": None, + "max_output_tokens": None, + "supports_function_calling": None, + "cache_read_input_token_cost": None, + } + + result = update_db_model( + db_model=db_model, + updated_patch=updateDeployment.model_validate({"model_info": echoed}), + ) + + info = json.loads(result["model_info"]) + assert info["max_input_tokens"] == 4096 + assert info["mode"] == "chat" + assert info["supports_vision"] is True + assert info["input_cost_per_token"] == 0.000001 + assert info["team_id"] == "team-keep-me" + for never_stored in ("base_model", "tier", "max_output_tokens", "supports_function_calling"): + assert never_stored not in info + + def test_null_on_pricing_key_still_clears_both_blobs(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + + result = update_db_model( + db_model=_build_db_model_with_pinned_model_info(), + updated_patch=updateDeployment.model_validate( + {"model_info": {"input_cost_per_token": None}} + ), + ) + + params = json.loads(result["litellm_params"]) + info = json.loads(result["model_info"]) + assert "input_cost_per_token" not in params + assert "input_cost_per_token" not in info + assert params["max_input_tokens"] == 4096 + assert info["max_input_tokens"] == 4096 + + class TestGetModelInfoWithIdBlocked: """`ProxyConfig.get_model_info_with_id` must propagate the DB-level `blocked` column into the in-memory `model_info` dict so the router filter can read it.""" diff --git a/tests/test_litellm/test_router_model_cost_isolation.py b/tests/test_litellm/test_router_model_cost_isolation.py index 30b265905f3..d22ec60e61a 100644 --- a/tests/test_litellm/test_router_model_cost_isolation.py +++ b/tests/test_litellm/test_router_model_cost_isolation.py @@ -220,6 +220,198 @@ def test_should_store_full_pricing_under_deployment_model_id(): assert entry["output_cost_per_token"] == 0.0 +def test_should_drop_a_price_the_deployment_no_longer_carries(): + """Re-registering a deployment must replace its model_id entry, not merge onto it. + + A merge left the old rate in the cost map after an operator cleared the override, so + the deployment kept billing at a price its config no longer had. + """ + backend_model = "vertex_ai/gemini-2.5-flash" + model_id = "deployment-cleared-price" + original = {model_id: litellm.model_cost.get(model_id)} + + try: + Router._register_deployment_in_model_cost( + model_id=model_id, + model_info={"input_cost_per_token": 0.005, "output_cost_per_token": 0.01}, + model=backend_model, + custom_llm_provider="vertex_ai", + ) + assert litellm.model_cost[model_id]["input_cost_per_token"] == 0.005 + + Router._register_deployment_in_model_cost( + model_id=model_id, + model_info={"mode": "chat"}, + model=backend_model, + custom_llm_provider="vertex_ai", + ) + + entry = litellm.model_cost[model_id] + assert entry.get("input_cost_per_token") != 0.005, ( + "the cleared override survived re-registration, so the deployment still bills at it" + ) + assert entry.get("output_cost_per_token") != 0.01 + finally: + _restore_model_cost_entries(original) + + +def test_should_not_strip_a_builtin_entry_when_a_deployment_id_collides_with_it(): + """Deployments are keyed into the same cost map as the built-in catalog, so a deployment + whose id happens to name a real model must not evict that model's entry. + + Stripping it would take the pricing and capability flags every other deployment of that + model reads, process-wide, until the next price-map reload. Registering twice, because + the first registration is what would mark the entry as this deployment's own. + """ + colliding_id = "gpt-4o" + original = {colliding_id: litellm.model_cost.get(colliding_id)} + builtin_max_tokens = litellm.model_cost[colliding_id]["max_tokens"] + + try: + for _ in range(2): + Router._register_deployment_in_model_cost( + model_id=colliding_id, + model_info={"id": colliding_id, "db_model": True, "mode": "chat"}, + model="gpt-4o-mini", + custom_llm_provider="openai", + ) + + entry = litellm.model_cost[colliding_id] + assert entry["max_tokens"] == builtin_max_tokens, ( + "registering a deployment under a catalog model's name wiped that model's context window" + ) + assert entry["litellm_provider"] == "openai" + assert entry["supports_vision"] is True + finally: + _restore_model_cost_entries(original) + + +def test_should_drop_a_stale_price_even_when_the_deployment_declares_a_provider(): + """A deployment may carry `litellm_provider` in its own model_info, which must not be + read as "this is a catalog entry" and stop the stale price from being dropped.""" + model_id = "deployment-provider-tagged" + original = {model_id: litellm.model_cost.get(model_id)} + + try: + Router._register_deployment_in_model_cost( + model_id=model_id, + model_info={"id": model_id, "litellm_provider": "openai", "input_cost_per_token": 0.005}, + model="gpt-4o-mini", + custom_llm_provider="openai", + ) + assert litellm.model_cost[model_id]["input_cost_per_token"] == 0.005 + + Router._register_deployment_in_model_cost( + model_id=model_id, + model_info={"id": model_id, "litellm_provider": "openai", "mode": "chat"}, + model="gpt-4o-mini", + custom_llm_provider="openai", + ) + + assert litellm.model_cost[model_id].get("input_cost_per_token") != 0.005, ( + "a deployment that declares its provider kept billing at the price it no longer carries" + ) + finally: + _restore_model_cost_entries(original) + + +def test_should_give_a_cost_map_key_back_when_the_deployment_is_deleted(): + """Deleting a deployment releases its claim on the shared cost-map key. + + Held forever, a later catalog refresh that starts publishing a model under that same + name would be treated as the deleted deployment's own entry and evicted. + """ + from litellm.router import _DEPLOYMENT_COST_MAP_KEYS + + model_id = "deployment-to-delete" + original = {model_id: litellm.model_cost.get(model_id)} + router = Router( + model_list=[ + { + "model_name": "to-delete", + "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, + "model_info": {"id": model_id, "input_cost_per_token": 0.005}, + } + ] + ) + + try: + assert model_id in _DEPLOYMENT_COST_MAP_KEYS + + assert router.delete_deployment(id=model_id) is not None + + assert model_id not in _DEPLOYMENT_COST_MAP_KEYS, ( + "a deleted deployment kept its claim on the shared cost-map key" + ) + finally: + _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) + _restore_model_cost_entries(original) + + +def test_should_keep_the_cost_map_key_while_another_router_still_serves_it(): + """Two live routers can serve the same deployment id, and the claim is process-wide. + + Releasing it when only one of them drops the deployment would put the survivor back on + merging, so the price it just cleared would keep billing. + """ + from litellm.router import _DEPLOYMENT_COST_MAP_KEYS + + model_id = "deployment-served-twice" + original = {model_id: litellm.model_cost.get(model_id)} + entry = { + "model_name": "served-twice", + "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, + "model_info": {"id": model_id, "input_cost_per_token": 0.005}, + } + first = Router(model_list=[entry]) + second = Router(model_list=[entry]) + + try: + assert model_id in _DEPLOYMENT_COST_MAP_KEYS + + assert first.delete_deployment(id=model_id) is not None + + assert model_id in _DEPLOYMENT_COST_MAP_KEYS, ( + "the claim was released while another router still served the deployment" + ) + + assert second.delete_deployment(id=model_id) is not None + assert model_id not in _DEPLOYMENT_COST_MAP_KEYS + finally: + _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) + _restore_model_cost_entries(original) + + +def test_should_keep_the_cost_map_key_while_a_dynamically_built_router_serves_it(): + """A router built with no model_list still serves whatever add_deployment gives it, so it + counts when deciding whether the shared cost-map claim can be released.""" + from litellm.router import _DEPLOYMENT_COST_MAP_KEYS + + model_id = "deployment-added-dynamically" + original = {model_id: litellm.model_cost.get(model_id)} + entry = { + "model_name": "added-dynamically", + "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, + "model_info": {"id": model_id, "input_cost_per_token": 0.005}, + } + configured = Router(model_list=[entry]) + dynamic = Router() + dynamic.add_deployment(deployment=Deployment(**entry)) + + try: + assert configured.delete_deployment(id=model_id) is not None + + assert model_id in _DEPLOYMENT_COST_MAP_KEYS, ( + "the claim was released while a dynamically built router still served the deployment" + ) + + assert dynamic.delete_deployment(id=model_id) is not None + assert model_id not in _DEPLOYMENT_COST_MAP_KEYS + finally: + _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) + _restore_model_cost_entries(original) + + def test_should_preserve_builtin_pricing_regardless_of_deployment_order(): """ The built-in pricing should be preserved no matter which deployment diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 83b0d58f2b2..93aaf3ca58c 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -9064,8 +9064,9 @@ export interface paths { * Patch Model * @description PATCH Endpoint for partial model updates. * - * Only updates the fields specified in the request while preserving other existing values. - * Follows proper PATCH semantics by only modifying provided fields. + * JSON Merge Patch semantics over `litellm_params` and `model_info`: a key absent from the + * body is unchanged, a key sent as null is removed from the stored row, and a key sent with a + * value is set (identity and ownership keys such as `id` and `team_id` ignore a null). * * Args: * model_id: The ID of the model to update From 25a6c2a2749ba802514fb3a5736b540c21a1e630 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 06:17:59 +0000 Subject: [PATCH 084/105] fix(cli): skip remote model cost map fetch in lite CLI processes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/get_model_cost_map.py | 14 ++++- .../test_get_model_cost_map.py | 25 +++++++++ .../proxy/client/cli/test_global_options.py | 56 ++++++++++++++++++- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index cdc4810ff04..7d3b67e58c8 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -2,6 +2,7 @@ Pulls the cost + context window + provider route for known models from https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json This can be disabled by setting the LITELLM_LOCAL_MODEL_COST_MAP environment variable to True. +The ``lite`` and ``litellm-proxy`` CLI entry points also use the bundled map without fetching. ``` export LITELLM_LOCAL_MODEL_COST_MAP=True @@ -13,11 +14,13 @@ import hashlib import json import os import random +import sys import time from collections.abc import Awaitable, Callable from dataclasses import dataclass, replace from datetime import datetime, timezone from importlib.resources import files +from pathlib import Path from typing import Final, Protocol import httpx @@ -33,6 +36,12 @@ from litellm.litellm_core_utils.fallback_generalizations import ( ) FALLBACK_GENERALIZATIONS_KEY: Final = "fallback_generalizations" +_CLI_ENTRYPOINT_NAMES: Final = frozenset({"lite", "litellm-proxy"}) + + +def _is_cli_process() -> bool: + return Path(sys.argv[0]).stem in _CLI_ENTRYPOINT_NAMES + # Reserved top-level keys that are not model entries. They must be excluded # from the model-count integrity check so a real upstream shrink can't be masked. @@ -531,7 +540,8 @@ def get_model_cost_map( """ Public entry point — returns the model cost map dict. - 1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set, uses the local backup only. + 1. If ``LITELLM_LOCAL_MODEL_COST_MAP`` is set or this is a ``lite`` / + ``litellm-proxy`` CLI process, uses the local backup only. 2. Otherwise fetches from ``url``, retrying transient HTTP errors (429/5xx/transport) with Retry-After-aware backoff, validates integrity, and falls back to the local backup on any failure. @@ -543,7 +553,7 @@ def get_model_cost_map( _cost_map_source_info.loaded_at = datetime.now(timezone.utc) # Note: can't use get_secret_bool here — this runs during litellm.__init__ # before litellm._key_management_settings is set. - if os.getenv("LITELLM_LOCAL_MODEL_COST_MAP", "").lower() == "true": + if os.getenv("LITELLM_LOCAL_MODEL_COST_MAP", "").lower() == "true" or _is_cli_process(): _cost_map_source_info.source = "local" _cost_map_source_info.url = None _cost_map_source_info.is_env_forced = True diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index 0495440c51c..d092b387259 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -6,6 +6,7 @@ count actual model entries, not reserved meta keys) and the extraction of the import json import os +import sys import pytest @@ -711,3 +712,27 @@ def test_boot_load_that_fails_the_integrity_check_reports_the_backup_not_the_rej assert source["etag"] is None assert source["source_revision"] == _bundled_blob_id() assert source["source_revision"] != git_blob_id(shrunk_body) + + +@pytest.mark.parametrize( + ("argv0", "request_count"), + [ + ("/some/venv/bin/lite", 0), + ("/some/venv/bin/lite.exe", 0), + ("/some/venv/bin/python", 1), + ], +) +def test_boot_load_skips_remote_fetch_for_cli_processes(monkeypatch, argv0, request_count): + monkeypatch.setattr(sys, "argv", [argv0, "--version"]) + monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) + client, calls = _mock_client([httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client) + + cost_map = get_model_cost_map(url=_URL, client=client) + + assert calls["count"] == request_count + assert cost_map + source = get_model_cost_map_source_info() + if request_count == 0: + assert source["source"] == "local" + else: + assert source["source"] == "remote" diff --git a/tests/test_litellm/proxy/client/cli/test_global_options.py b/tests/test_litellm/proxy/client/cli/test_global_options.py index 0dd388919a5..02b1cdf4296 100644 --- a/tests/test_litellm/proxy/client/cli/test_global_options.py +++ b/tests/test_litellm/proxy/client/cli/test_global_options.py @@ -1,14 +1,16 @@ # stdlib imports import json import os +import shutil +import subprocess +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from unittest.mock import Mock, patch import pytest from click.testing import CliRunner - - import litellm.proxy.client.cli from litellm._version import version as litellm_version from litellm.proxy.client.cli import cli @@ -35,6 +37,56 @@ def test_cli_version_flag(cli_runner): assert "LiteLLM Proxy Server Version: 1.2.3" in result.output +def test_lite_version_does_not_fetch_model_cost_map(): + lite_path = shutil.which("lite") + if lite_path is None: + pytest.skip("lite executable is unavailable") + + requests = [] + + class _CostMapHandler(BaseHTTPRequestHandler): + def do_GET(self): + requests.append(self.path) + body = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format, *args): + return + + server = ThreadingHTTPServer(("127.0.0.1", 0), _CostMapHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + env = {key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"} + source_root = str(Path(__file__).resolve().parents[5]) + env["PYTHONPATH"] = os.pathsep.join(filter(None, (source_root, env.get("PYTHONPATH")))) + env.update( + { + "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", + "LITELLM_PROXY_URL": "http://127.0.0.1:9", + } + ) + result = subprocess.run( + [lite_path, "--version"], + capture_output=True, + text=True, + timeout=120, + env=env, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=10) + + assert result.returncode == 0 + assert "LiteLLM Proxy CLI Version" in result.stdout + assert requests == [] + + def test_cli_source_is_ascii_only(): """Non-ASCII output (emoji, box-drawing chars) raises UnicodeEncodeError on legacy Windows consoles (cp1252), so the whole CLI package must stay ASCII-only.""" From ffae447649e173214a2081b4890410a11dfdf085 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 06:30:50 +0000 Subject: [PATCH 085/105] test(cli): type cost map bypass regression handlers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/test_get_model_cost_map.py | 4 +++- tests/test_litellm/proxy/client/cli/test_global_options.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py index d092b387259..6ab3f9a21af 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/test_litellm/litellm_core_utils/test_get_model_cost_map.py @@ -722,7 +722,9 @@ def test_boot_load_that_fails_the_integrity_check_reports_the_backup_not_the_rej ("/some/venv/bin/python", 1), ], ) -def test_boot_load_skips_remote_fetch_for_cli_processes(monkeypatch, argv0, request_count): +def test_boot_load_skips_remote_fetch_for_cli_processes( + monkeypatch: pytest.MonkeyPatch, argv0: str, request_count: int +) -> None: monkeypatch.setattr(sys, "argv", [argv0, "--version"]) monkeypatch.delenv("LITELLM_LOCAL_MODEL_COST_MAP", raising=False) client, calls = _mock_client([httpx.Response(200, content=_real_map_bytes())], client_cls=httpx.Client) diff --git a/tests/test_litellm/proxy/client/cli/test_global_options.py b/tests/test_litellm/proxy/client/cli/test_global_options.py index 02b1cdf4296..6de56573ffb 100644 --- a/tests/test_litellm/proxy/client/cli/test_global_options.py +++ b/tests/test_litellm/proxy/client/cli/test_global_options.py @@ -42,10 +42,10 @@ def test_lite_version_does_not_fetch_model_cost_map(): if lite_path is None: pytest.skip("lite executable is unavailable") - requests = [] + requests: list[str] = [] class _CostMapHandler(BaseHTTPRequestHandler): - def do_GET(self): + def do_GET(self) -> None: requests.append(self.path) body = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' self.send_response(200) @@ -54,7 +54,7 @@ def test_lite_version_does_not_fetch_model_cost_map(): self.end_headers() self.wfile.write(body) - def log_message(self, format, *args): + def log_message(self, format: str, *args: object) -> None: return server = ThreadingHTTPServer(("127.0.0.1", 0), _CostMapHandler) From ef3a3c16ae02bc6d83e14b09c437ef36081c0498 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:32:31 -0700 Subject: [PATCH 086/105] feat(guardrails): map each guardrail scan id to its guardrail, stage and provider (#40327) * feat(guardrails): map each guardrail scan id to its guardrail, stage and provider Adds the x-litellm-guardrail-scan-metadata response header, a JSON list of {guardrail, stage, provider, scan_id} entries, next to the existing comma-separated x-litellm-guardrail-scan-id header. Prisma AIRS records the execution stage for every scan and OpenAI Moderation now records its moderation id too. The new metadata key is internal: client-supplied values are stripped and it is exposed through the UI CORS allow list. Resolves LIT-6018 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(guardrails): cap the scan metadata response header at a configurable length Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(guardrails): hardcode the scan metadata header cap Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yucheng Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/constants.py | 2 + litellm/proxy/common_utils/callback_utils.py | 62 ++++++++- .../guardrail_hooks/openai/moderations.py | 10 +- .../panw_prisma_airs/panw_prisma_airs.py | 34 +++-- litellm/proxy/litellm_pre_call_utils.py | 2 + .../proxy/common_utils/test_callback_utils.py | 122 +++++++++++++++--- .../openai/test_moderations.py | 32 +++++ .../guardrail_hooks/test_panw_prisma_airs.py | 32 ++++- 8 files changed, 265 insertions(+), 31 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 6f2384c8c6c..108a914e9c1 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -143,6 +143,7 @@ DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD: Final = float( os.getenv("DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD", 0.3) ) MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH: Final = int(os.getenv("MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH", 150)) +MAX_GUARDRAIL_SCAN_METADATA_HEADER_LENGTH: Final = 2048 DEFAULT_AUTO_ROUTER_MAX_INPUT_CHARS: Final = 2000 @@ -197,6 +198,7 @@ LITELLM_UI_ALLOW_HEADERS: Final = [ "x-litellm-adaptive-router-model", "x-litellm-applied-guardrails", "x-litellm-guardrail-scan-id", + "x-litellm-guardrail-scan-metadata", "x-litellm-cache-key", ] diff --git a/litellm/proxy/common_utils/callback_utils.py b/litellm/proxy/common_utils/callback_utils.py index 770963a1f24..561a53409f4 100644 --- a/litellm/proxy/common_utils/callback_utils.py +++ b/litellm/proxy/common_utils/callback_utils.py @@ -1,10 +1,12 @@ import copy +import json import os from collections.abc import Callable, Iterable, Mapping from dataclasses import dataclass +from itertools import accumulate from typing import TYPE_CHECKING, Any, Final, Literal, NoReturn, Optional, TypeAlias -from typing_extensions import assert_never +from typing_extensions import ReadOnly, TypedDict, assert_never import litellm from litellm import get_secret @@ -12,6 +14,7 @@ from litellm._logging import verbose_proxy_logger from litellm.constants import ( CLIENT_OUTPUT_CEILING_METADATA_KEY, CONSUMED_REQUEST_TAGS_METADATA_KEY, + MAX_GUARDRAIL_SCAN_METADATA_HEADER_LENGTH, PRE_CALL_EXECUTED_GUARDRAILS_KEY, ROUTING_REQUEST_TAGS_METADATA_KEY, SESSION_DEPLOYMENT_AFFINITY_TTL_METADATA_KEY, @@ -28,6 +31,7 @@ from litellm.proxy.common_utils.encrypt_decrypt_utils import ( encrypt_value_helper, ) from litellm.proxy.types_utils.utils import get_instance_fn +from litellm.types.guardrails import GuardrailEventHooks from litellm.types.utils import ( StandardLoggingGuardrailInformation, StandardLoggingPayload, @@ -52,6 +56,15 @@ reset_color_code: Final = "\033[0m" TRUSTED_PILLAR_RESPONSE_HEADERS_METADATA_KEY: Final = "_pillar_response_headers_trusted" GUARDRAIL_SCAN_IDS_METADATA_KEY: Final = "guardrail_scan_ids" +GUARDRAIL_SCAN_METADATA_METADATA_KEY: Final = "guardrail_scan_metadata" + + +class GuardrailScanMetadata(TypedDict): + guardrail: ReadOnly[str | None] + stage: ReadOnly[str] + provider: ReadOnly[str] + scan_id: ReadOnly[str] + if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging @@ -450,6 +463,16 @@ def get_remaining_tokens_and_requests_from_request_data(data: dict) -> dict[str, return headers +def _serialize_scan_metadata_header(entries: Iterable[object], *, max_length: int) -> str | None: + """Compact JSON list of scan metadata entries, dropping trailing entries so the header fits in max_length.""" + encoded: Final = tuple(json.dumps(entry, separators=(",", ":")) for entry in entries) + lengths: Final = tuple(accumulate(len(item) + 1 for item in encoded)) + kept: Final = sum(1 for length in lengths if length + 1 <= max_length) + if kept == 0: + return None + return f"[{','.join(encoded[:kept])}]" + + def get_logging_caching_headers(request_data: dict) -> dict | None: _metadata: Final[dict] = {} metadata_bucket: Final = request_data.get("metadata") @@ -468,6 +491,15 @@ def get_logging_caching_headers(request_data: dict) -> dict | None: if scan_ids: headers["x-litellm-guardrail-scan-id"] = ",".join(scan_ids) + scan_metadata: Final = _metadata.get(GUARDRAIL_SCAN_METADATA_METADATA_KEY) + scan_metadata_header: Final = ( + _serialize_scan_metadata_header(scan_metadata, max_length=MAX_GUARDRAIL_SCAN_METADATA_HEADER_LENGTH) + if isinstance(scan_metadata, (list, tuple)) + else None + ) + if scan_metadata_header: + headers["x-litellm-guardrail-scan-metadata"] = scan_metadata_header + if "applied_policies" in _metadata: headers["x-litellm-applied-policies"] = ",".join(_metadata["applied_policies"]) @@ -501,6 +533,7 @@ LITELLM_PROXY_INTERNAL_METADATA_KEYS: Final = frozenset( "applied_policies", "applied_guardrails", GUARDRAIL_SCAN_IDS_METADATA_KEY, + GUARDRAIL_SCAN_METADATA_METADATA_KEY, "policy_sources", "guardrails", "guardrail_config", @@ -565,21 +598,40 @@ def add_guardrail_to_applied_guardrails_header(request_data: dict, guardrail_nam _metadata["applied_guardrails"] = [guardrail_name] -def add_guardrail_scan_id(request_data: dict, scan_id: str | None) -> None: +def add_guardrail_scan_id( + request_data: dict[str, object], + scan_id: str | None, + *, + guardrail_name: str | None, + provider: str, + stage: GuardrailEventHooks, +) -> None: """ - Record a provider scan id so it can be surfaced to the caller. + Record a provider scan id, keyed to the guardrail execution that produced it, so it can be surfaced to the caller. Guardrails only return scan details to the client when they block, so allowed requests carry no - audit trail. Ids recorded here become the x-litellm-guardrail-scan-id response header. + audit trail. Ids recorded here become the x-litellm-guardrail-scan-id response header, and the + (guardrail, stage, provider, scan_id) entries become the x-litellm-guardrail-scan-metadata header. """ if not scan_id: return _, _metadata = get_or_create_metadata_bucket(request_data) existing: Final = _metadata.get(GUARDRAIL_SCAN_IDS_METADATA_KEY) - scan_ids: Final = tuple(existing) if isinstance(existing, (list, tuple)) else () + scan_ids: Final[tuple[object, ...]] = tuple(existing) if isinstance(existing, (list, tuple)) else () if scan_id not in scan_ids: _metadata[GUARDRAIL_SCAN_IDS_METADATA_KEY] = (*scan_ids, scan_id) + entry: Final[GuardrailScanMetadata] = { + "guardrail": guardrail_name, + "stage": stage.value, + "provider": provider, + "scan_id": scan_id, + } + existing_entries: Final = _metadata.get(GUARDRAIL_SCAN_METADATA_METADATA_KEY) + entries: Final[tuple[object, ...]] = tuple(existing_entries) if isinstance(existing_entries, (list, tuple)) else () + if entry not in entries: + _metadata[GUARDRAIL_SCAN_METADATA_METADATA_KEY] = (*entries, entry) + def add_policy_to_applied_policies_header(request_data: dict, policy_name: str | None): """ diff --git a/litellm/proxy/guardrails/guardrail_hooks/openai/moderations.py b/litellm/proxy/guardrails/guardrail_hooks/openai/moderations.py index 10683550f85..c22d35509c1 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/openai/moderations.py +++ b/litellm/proxy/guardrails/guardrail_hooks/openai/moderations.py @@ -17,7 +17,8 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, ) -from litellm.types.guardrails import GuardrailEventHooks +from litellm.proxy.common_utils.callback_utils import add_guardrail_scan_id +from litellm.types.guardrails import GuardrailEventHooks, SupportedGuardrailIntegrations from litellm.types.utils import ( GenericGuardrailAPIInputs, GuardrailStatus, @@ -218,6 +219,13 @@ class OpenAIModerationGuardrail(OpenAIGuardrailBase, CustomGuardrail): metadata: Final = request_data.get("metadata") or {} request_data["metadata"] = metadata metadata["_openai_moderation_response"] = moderation_response.model_dump() + add_guardrail_scan_id( + request_data=request_data, + scan_id=moderation_response.id, + guardrail_name=self.guardrail_name, + provider=SupportedGuardrailIntegrations.OPENAI_MODERATION.value, + stage=GuardrailEventHooks.post_call if input_type == "response" else GuardrailEventHooks.pre_call, + ) # Check if content is flagged and raise exception if needed self._check_moderation_result(moderation_response) diff --git a/litellm/proxy/guardrails/guardrail_hooks/panw_prisma_airs/panw_prisma_airs.py b/litellm/proxy/guardrails/guardrail_hooks/panw_prisma_airs/panw_prisma_airs.py index b73d3adb99e..3bc0dfabefc 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/panw_prisma_airs/panw_prisma_airs.py +++ b/litellm/proxy/guardrails/guardrail_hooks/panw_prisma_airs/panw_prisma_airs.py @@ -721,10 +721,18 @@ class PanwPrismaAirsHandler(CustomGuardrail): } } - def _record_scan_id(self, request_data: dict[str, object], scan_result: Mapping[str, object]) -> None: + def _record_scan_id( + self, request_data: dict[str, object], scan_result: Mapping[str, object], stage: GuardrailEventHooks + ) -> None: """Surface the AIRS scan id on the response, so allowed calls are auditable too.""" scan_id: Final = scan_result.get("scan_id") - add_guardrail_scan_id(request_data=request_data, scan_id=str(scan_id) if scan_id else None) + add_guardrail_scan_id( + request_data=request_data, + scan_id=str(scan_id) if scan_id else None, + guardrail_name=self.guardrail_name, + provider=self._PROVIDER_NAME, + stage=stage, + ) def _handle_api_error_with_logging( self, @@ -948,7 +956,7 @@ class PanwPrismaAirsHandler(CustomGuardrail): event_type=GuardrailEventHooks.post_call, ) add_guardrail_to_applied_guardrails_header(request_data=request_data, guardrail_name=self.guardrail_name) - self._record_scan_id(request_data, scan_result) + self._record_scan_id(request_data, scan_result, GuardrailEventHooks.post_call) def _check_and_mark_scanned(self, data: dict, scan_type: str) -> bool: """ @@ -1078,7 +1086,7 @@ class PanwPrismaAirsHandler(CustomGuardrail): duration=(end_time - start_time).total_seconds(), event_type=GuardrailEventHooks.pre_call, ) - self._record_scan_id(data, scan_result) + self._record_scan_id(data, scan_result, GuardrailEventHooks.pre_call) action: Final = scan_result.get("action", "block") category: Final = scan_result.get("category", "unknown") @@ -1199,7 +1207,7 @@ class PanwPrismaAirsHandler(CustomGuardrail): duration=(end_time - start_time).total_seconds(), event_type=GuardrailEventHooks.post_call, ) - self._record_scan_id(data, scan_result) + self._record_scan_id(data, scan_result, GuardrailEventHooks.post_call) action: Final = scan_result.get("action", "block") category: Final = scan_result.get("category", "unknown") @@ -1401,7 +1409,7 @@ class PanwPrismaAirsHandler(CustomGuardrail): duration=(end_time - start_time).total_seconds(), event_type=GuardrailEventHooks.post_call, ) - self._record_scan_id(request_data, scan_result) + self._record_scan_id(request_data, scan_result, GuardrailEventHooks.post_call) # Add guardrail to applied guardrails header for observability add_guardrail_to_applied_guardrails_header( @@ -1475,7 +1483,11 @@ class PanwPrismaAirsHandler(CustomGuardrail): ) continue - self._record_scan_id(request_data, scan_result) + self._record_scan_id( + request_data, + scan_result, + GuardrailEventHooks.post_call if is_response else GuardrailEventHooks.pre_call, + ) action = scan_result.get("action", "block") masked_args = self._masked_tool_call_arguments( @@ -1829,7 +1841,11 @@ class PanwPrismaAirsHandler(CustomGuardrail): new_texts.append(text) continue - self._record_scan_id(request_data, scan_result) + self._record_scan_id( + request_data, + scan_result, + GuardrailEventHooks.post_call if is_response else GuardrailEventHooks.pre_call, + ) action = scan_result.get("action", "block") masked_text = self._get_masked_text(scan_result, is_response=is_response) @@ -1901,7 +1917,7 @@ class PanwPrismaAirsHandler(CustomGuardrail): ) # If we reach here, fallback_on_error="allow" else: - self._record_scan_id(request_data, mcp_scan_result) + self._record_scan_id(request_data, mcp_scan_result, GuardrailEventHooks.pre_call) action = mcp_scan_result.get("action", "block") masked_text = self._get_masked_text(mcp_scan_result, is_response=False) if action == "allow": diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 924f84be5f4..3250ae5cca9 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -235,6 +235,7 @@ _UNTRUSTED_ROOT_CONTROL_FIELDS: Final = ( "applied_policies", "policy_sources", "guardrail_scan_ids", + "guardrail_scan_metadata", "routing_decision", GATEWAY_INJECTED_CACHE_METADATA_KEY, "pillar_response_headers", @@ -291,6 +292,7 @@ _UNTRUSTED_METADATA_CONTROL_FIELDS: Final = ( "applied_policies", "policy_sources", "guardrail_scan_ids", + "guardrail_scan_metadata", "routing_decision", GATEWAY_INJECTED_CACHE_METADATA_KEY, SESSION_DEPLOYMENT_AFFINITY_TTL_METADATA_KEY, diff --git a/tests/test_litellm/proxy/common_utils/test_callback_utils.py b/tests/test_litellm/proxy/common_utils/test_callback_utils.py index 66f77db6da9..ecb2375d495 100644 --- a/tests/test_litellm/proxy/common_utils/test_callback_utils.py +++ b/tests/test_litellm/proxy/common_utils/test_callback_utils.py @@ -1,30 +1,33 @@ import copy +import json import sys from types import ModuleType, SimpleNamespace +from typing import Final +from unittest.mock import patch import pytest - +import litellm +from litellm.caching.caching import DualCache +from litellm.constants import MAX_GUARDRAIL_SCAN_METADATA_HEADER_LENGTH +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.common_utils.callback_utils import ( + _serialize_scan_metadata_header, add_guardrail_scan_id, add_policy_to_applied_policies_header, decrypt_callback_vars, encrypt_callback_vars, get_logging_caching_headers, - initialize_callbacks_on_proxy, get_remaining_tokens_and_requests_from_request_data, + initialize_callbacks_on_proxy, normalize_callback_names, + process_callback, sanitize_openai_provider_metadata, strip_callback_config, ) -import litellm -from litellm.caching.caching import DualCache -from litellm.integrations.custom_logger import CustomLogger -from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.utils import ProxyLogging - -from unittest.mock import patch -from litellm.proxy.common_utils.callback_utils import process_callback +from litellm.types.guardrails import GuardrailEventHooks def test_get_remaining_tokens_and_requests_from_request_data(): @@ -189,20 +192,109 @@ def test_get_logging_caching_headers_merges_metadata_and_litellm_metadata(): assert headers["x-litellm-policy-sources"] == "global-baseline=team_default" +def _record( + request_data: dict[str, object], + scan_id: str | None, + guardrail_name: str = "airs", + provider: str = "panw_prisma_airs", + stage: GuardrailEventHooks = GuardrailEventHooks.pre_call, +) -> None: + add_guardrail_scan_id( + request_data=request_data, scan_id=scan_id, guardrail_name=guardrail_name, provider=provider, stage=stage + ) + + def test_add_guardrail_scan_id_dedupes_and_becomes_response_header(): request_data = {"litellm_metadata": {}} - add_guardrail_scan_id(request_data=request_data, scan_id="scan-1") - add_guardrail_scan_id(request_data=request_data, scan_id="scan-1") - add_guardrail_scan_id(request_data=request_data, scan_id="scan-2") - add_guardrail_scan_id(request_data=request_data, scan_id=None) + _record(request_data, "scan-1") + _record(request_data, "scan-1") + _record(request_data, "scan-2") + _record(request_data, None) assert request_data["litellm_metadata"]["guardrail_scan_ids"] == ("scan-1", "scan-2") assert get_logging_caching_headers(request_data)["x-litellm-guardrail-scan-id"] == "scan-1,scan-2" -def test_get_logging_caching_headers_omits_scan_id_header_without_scans(): - assert "x-litellm-guardrail-scan-id" not in get_logging_caching_headers({"litellm_metadata": {}}) +def test_scan_metadata_header_maps_each_id_to_its_guardrail_stage_and_provider(): + request_data: Final[dict[str, object]] = {"litellm_metadata": {}} + + _record( + request_data, "scan-1", guardrail_name="airs", provider="panw_prisma_airs", stage=GuardrailEventHooks.pre_call + ) + _record( + request_data, "mod-1", guardrail_name="mod", provider="openai_moderation", stage=GuardrailEventHooks.pre_call + ) + _record( + request_data, "scan-2", guardrail_name="airs", provider="panw_prisma_airs", stage=GuardrailEventHooks.post_call + ) + _record( + request_data, "scan-2", guardrail_name="airs", provider="panw_prisma_airs", stage=GuardrailEventHooks.post_call + ) + _record(request_data, None, guardrail_name="mod", provider="openai_moderation", stage=GuardrailEventHooks.post_call) + + headers: Final = get_logging_caching_headers(request_data) + assert headers is not None + assert headers["x-litellm-guardrail-scan-id"] == "scan-1,mod-1,scan-2" + assert json.loads(headers["x-litellm-guardrail-scan-metadata"]) == [ + {"guardrail": "airs", "stage": "pre_call", "provider": "panw_prisma_airs", "scan_id": "scan-1"}, + {"guardrail": "mod", "stage": "pre_call", "provider": "openai_moderation", "scan_id": "mod-1"}, + {"guardrail": "airs", "stage": "post_call", "provider": "panw_prisma_airs", "scan_id": "scan-2"}, + ] + + +def test_scan_metadata_keeps_same_id_reused_across_stages(): + request_data: Final[dict[str, object]] = {"metadata": {}} + + _record(request_data, "scan-1", stage=GuardrailEventHooks.pre_call) + _record(request_data, "scan-1", stage=GuardrailEventHooks.post_call) + + headers: Final = get_logging_caching_headers(request_data) + assert headers is not None + assert headers["x-litellm-guardrail-scan-id"] == "scan-1" + assert [entry["stage"] for entry in json.loads(headers["x-litellm-guardrail-scan-metadata"])] == [ + "pre_call", + "post_call", + ] + + +def test_scan_metadata_header_drops_trailing_entries_to_stay_within_length_limit(): + request_data: Final[dict[str, object]] = {"litellm_metadata": {}} + scan_ids: Final = tuple(f"0f9c4b7e-3d2a-4c1b-9e8f-{index:012d}" for index in range(40)) + for scan_id in scan_ids: + _record(request_data, scan_id, stage=GuardrailEventHooks.post_call) + + headers: Final = get_logging_caching_headers(request_data) + assert headers is not None + assert headers["x-litellm-guardrail-scan-id"] == ",".join(scan_ids) + header: Final = headers["x-litellm-guardrail-scan-metadata"] + assert len(header) <= MAX_GUARDRAIL_SCAN_METADATA_HEADER_LENGTH + kept: Final = json.loads(header) + assert 1 < len(kept) < len(scan_ids) + assert [entry["scan_id"] for entry in kept] == list(scan_ids[: len(kept)]) + + +def test_serialize_scan_metadata_header_keeps_exactly_the_entries_that_fit(): + entries: Final = ({"scan_id": "a"}, {"scan_id": "b"}, {"scan_id": "c"}) + two_entries: Final = '[{"scan_id":"a"},{"scan_id":"b"}]' + + assert _serialize_scan_metadata_header(entries, max_length=len(two_entries)) == two_entries + assert _serialize_scan_metadata_header(entries, max_length=len(two_entries) - 1) == '[{"scan_id":"a"}]' + assert _serialize_scan_metadata_header(entries, max_length=len(two_entries) + 1) == two_entries + assert _serialize_scan_metadata_header(entries, max_length=1000) == json.dumps(entries, separators=(",", ":")) + assert _serialize_scan_metadata_header(entries, max_length=5) is None + assert _serialize_scan_metadata_header((), max_length=1000) is None + + +def test_scan_metadata_is_an_internal_metadata_key(): + assert sanitize_openai_provider_metadata({"guardrail_scan_metadata": "x", "keep": "y"}) == {"keep": "y"} + + +def test_get_logging_caching_headers_omits_scan_headers_without_scans(): + headers: Final = get_logging_caching_headers({"litellm_metadata": {}}) + assert headers is not None + assert "x-litellm-guardrail-scan-id" not in headers + assert "x-litellm-guardrail-scan-metadata" not in headers def test_initialize_callbacks_on_proxy_instantiates_compression_interception( diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/openai/test_moderations.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/openai/test_moderations.py index 2b43720a126..615d06b0f42 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/openai/test_moderations.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/openai/test_moderations.py @@ -3,14 +3,19 @@ Test OpenAI Moderation Guardrail """ +import json import os +from typing import Final from unittest.mock import MagicMock, patch +import httpx import pytest +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.common_utils.callback_utils import get_logging_caching_headers from litellm.proxy.guardrails.guardrail_hooks.openai.moderations import ( OpenAIModerationGuardrail, ) @@ -989,3 +994,30 @@ async def test_openai_moderation_initialize_guardrail_forwards_streaming_flags() assert guardrail.streaming_sampling_rate == 2 finally: litellm.logging_callback_manager._reset_all_callbacks() + + +@pytest.mark.asyncio +@pytest.mark.parametrize(("input_type", "stage"), [("request", "pre_call"), ("response", "post_call")]) +async def test_openai_moderation_records_moderation_id_as_scan_metadata(input_type: str, stage: str): + """Each moderation call's id is exposed with the guardrail name, stage and provider that produced it.""" + payload: Final = { + "id": f"modr-{stage}", + "model": "omni-moderation-latest", + "results": [{"flagged": False, "categories": {}, "category_scores": {}, "category_applied_input_types": {}}], + } + http_client: Final = AsyncHTTPHandler() + http_client.client = httpx.AsyncClient(transport=httpx.MockTransport(lambda _: httpx.Response(200, json=payload))) + + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}): + guardrail: Final = OpenAIModerationGuardrail(guardrail_name="openai-mod") + guardrail.async_handler = http_client + request_data: Final[dict[str, object]] = {"metadata": {}} + + await guardrail.apply_guardrail(inputs={"texts": ["hello"]}, request_data=request_data, input_type=input_type) + + headers: Final = get_logging_caching_headers(request_data) + assert headers is not None + assert headers["x-litellm-guardrail-scan-id"] == f"modr-{stage}" + assert json.loads(headers["x-litellm-guardrail-scan-metadata"]) == [ + {"guardrail": "openai-mod", "stage": stage, "provider": "openai_moderation", "scan_id": f"modr-{stage}"} + ] diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_panw_prisma_airs.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_panw_prisma_airs.py index 8f29ba66814..3d7c6e06d94 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_panw_prisma_airs.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_panw_prisma_airs.py @@ -12,6 +12,7 @@ This test file follows LiteLLM's testing patterns and covers: import copy import json from datetime import datetime +from typing import Final from unittest.mock import AsyncMock, MagicMock, patch import httpx @@ -5785,7 +5786,14 @@ class TestPanwAirsScanIdExposure: headers = get_logging_caching_headers(data) assert headers["x-litellm-guardrail-scan-id"] == "scan-abc-123" - assert "x-litellm-guardrail-scan-metadata" not in headers + assert json.loads(headers["x-litellm-guardrail-scan-metadata"]) == [ + { + "guardrail": handler.guardrail_name, + "stage": "pre_call", + "provider": "panw_prisma_airs", + "scan_id": "scan-abc-123", + } + ] @pytest.mark.asyncio async def test_request_and_response_scan_ids_are_both_exposed(self, user_api_key_dict): @@ -5809,6 +5817,26 @@ class TestPanwAirsScanIdExposure: headers = get_logging_caching_headers(data) assert headers["x-litellm-guardrail-scan-id"] == "scan-abc-123,scan-response-456" + assert [(e["stage"], e["scan_id"]) for e in json.loads(headers["x-litellm-guardrail-scan-metadata"])] == [ + ("pre_call", "scan-abc-123"), + ("post_call", "scan-response-456"), + ] + + @pytest.mark.asyncio + async def test_apply_guardrail_response_scan_is_tagged_post_call(self): + from litellm.proxy.common_utils.callback_utils import get_logging_caching_headers + + handler: Final = self._handler(self.ALLOW_SCAN_RESULT) + request_data: Final[dict[str, object]] = {"litellm_call_id": "test-call-id", "model": "gpt-4", "metadata": {}} + + await handler.apply_guardrail( + inputs={"texts": ["Hello world"]}, request_data=request_data, input_type="response" + ) + + headers: Final = get_logging_caching_headers(request_data) + assert headers is not None + entries: Final = json.loads(headers["x-litellm-guardrail-scan-metadata"]) + assert [(e["stage"], e["provider"]) for e in entries] == [("post_call", "panw_prisma_airs")] @pytest.mark.asyncio async def test_repeated_scan_id_is_not_duplicated(self, user_api_key_dict): @@ -5850,6 +5878,8 @@ class TestPanwAirsScanIdExposure: assert "guardrail_scan_ids" in _UNTRUSTED_METADATA_CONTROL_FIELDS assert "guardrail_scan_ids" in _UNTRUSTED_ROOT_CONTROL_FIELDS + assert "guardrail_scan_metadata" in _UNTRUSTED_METADATA_CONTROL_FIELDS + assert "guardrail_scan_metadata" in _UNTRUSTED_ROOT_CONTROL_FIELDS class TestPanwAirsBlockedErrorDetailPassthrough: """Regression tests for the full AIRS scan response on blocks. From 0c0a1dd76f31b56059508dd674fee1764c83828f Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 06:34:36 +0000 Subject: [PATCH 087/105] test(cli): avoid mutable request tracking Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/client/cli/test_global_options.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_litellm/proxy/client/cli/test_global_options.py b/tests/test_litellm/proxy/client/cli/test_global_options.py index 6de56573ffb..9ed9f91ab9f 100644 --- a/tests/test_litellm/proxy/client/cli/test_global_options.py +++ b/tests/test_litellm/proxy/client/cli/test_global_options.py @@ -37,16 +37,17 @@ def test_cli_version_flag(cli_runner): assert "LiteLLM Proxy Server Version: 1.2.3" in result.output -def test_lite_version_does_not_fetch_model_cost_map(): +def test_lite_version_does_not_fetch_model_cost_map(tmp_path: Path) -> None: lite_path = shutil.which("lite") if lite_path is None: pytest.skip("lite executable is unavailable") - requests: list[str] = [] + request_log: Path = tmp_path / "requests.log" class _CostMapHandler(BaseHTTPRequestHandler): def do_GET(self) -> None: - requests.append(self.path) + with request_log.open("a", encoding="utf-8") as log_file: + log_file.write(f"{self.path}\n") body = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' self.send_response(200) self.send_header("Content-Type", "application/json") @@ -84,7 +85,8 @@ def test_lite_version_does_not_fetch_model_cost_map(): assert result.returncode == 0 assert "LiteLLM Proxy CLI Version" in result.stdout - assert requests == [] + request_count: int = request_log.read_text(encoding="utf-8").count("\n") if request_log.exists() else 0 + assert request_count == 0 def test_cli_source_is_ascii_only(): From 9d90a544916c6f38397862dd18b6a4c12de98827 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 9 Sep 2026 00:06:26 -0700 Subject: [PATCH 088/105] revert(model-management): roll back #40047 This reverts commit e8e3172d7d70558929f32f057ebe4c7471c8c352 Restore the previous model update and router cost registration behavior while pricing compatibility is investigated --- .../model_management_endpoints.py | 70 +--- litellm/router.py | 26 +- tests/e2e/coverage_registry/mgmt.yaml | 2 - .../management/test_model_lifecycle_e2e.py | 365 ------------------ tests/e2e/models.py | 74 +--- tests/e2e/proxy_client.py | 199 +--------- tests/e2e/test_proxy_client.py | 57 +-- tests/e2e/transport.py | 1 - .../test_model_management_endpoints.py | 178 +-------- .../test_router_model_cost_isolation.py | 192 --------- ui/litellm-dashboard/src/lib/http/schema.d.ts | 5 +- 11 files changed, 39 insertions(+), 1130 deletions(-) delete mode 100644 tests/e2e/management/test_model_lifecycle_e2e.py diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 742b9d9817f..0f19e9ce149 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -119,7 +119,6 @@ from litellm.types.router import ( SPECIAL_MODEL_INFO_PARAMS, Deployment, GenericLiteLLMParams, - LiteLLM_Params, ModelInfo, updateDeployment, ) @@ -729,44 +728,6 @@ def _ptu_priced_deployment(model_params: Deployment) -> Deployment: ) -_OWNERSHIP_FIELDS: Final = frozenset( - { - "db_model", - "team_id", - "team_public_model_name", - "access_groups", - "created_at", - "created_by", - "updated_at", - "updated_by", - "blocked", - } -) - -_STORED_REQUIRED_FIELDS: Final = frozenset( - name for model in (LiteLLM_Params, ModelInfo) for name, field in model.model_fields.items() if field.is_required() -) - -_NULL_CLEAR_IGNORED_FIELDS: Final = _OWNERSHIP_FIELDS | _STORED_REQUIRED_FIELDS | frozenset(PTU_MODEL_INFO_FIELDS) - - -def _explicitly_cleared_fields(patch: BaseModel | None) -> frozenset[str]: - """The keys a patch sends as an explicit null, which update_db_model removes from the - stored blob (JSON Merge Patch). Ownership keys are left alone, as are the keys the stored - models require, since clearing one writes a row no reload can rebuild through - LiteLLM_Params / ModelInfo. The PTU keys are handled by _explicitly_cleared_ptu_fields, - whose clear is gated on the feature flag. Applied after both blobs merge, so a model_info blob - the UI echoes back cannot resurrect a pricing key the litellm_params patch clears. - """ - if patch is None: - return frozenset() - return frozenset( - field - for field in patch.model_fields_set - if field not in _NULL_CLEAR_IGNORED_FIELDS and getattr(patch, field) is None - ) - - def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> PrismaCompatibleUpdateDBModel: if updated_patch.model_info is not None: _raise_if_ptu_cost_attribution_disabled(updated_patch.model_info.model_dump(exclude_none=True)) @@ -787,15 +748,25 @@ def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> Pr if updated_patch.model_info: merged_model_info.update(updated_patch.model_info.model_dump(exclude_none=True)) - for field in _explicitly_cleared_fields(updated_patch.litellm_params): - merged_litellm_params.pop(field, None) - if field in SPECIAL_MODEL_INFO_PARAMS: - merged_model_info.pop(field, None) - for field in _explicitly_cleared_fields(updated_patch.model_info): - merged_model_info.pop(field, None) - if field in SPECIAL_MODEL_INFO_PARAMS: - merged_litellm_params.pop(field, None) + # Honor explicit-null clears LAST, after both merges, so a model_info blob the UI + # passes through (which today re-sends the OLD pricing on every save) cannot + # silently undo a litellm_params clear via .update(). + # + # Restricted to SPECIAL_MODEL_INFO_PARAMS (input/output cost per token/character + # and cache read/write costs) so this path cannot be used to null out privileged + # model_info fields like team_id or access groups. SPECIAL_MODEL_INFO_PARAMS are + # mirrored between litellm_params and model_info by Deployment.__init__, so the + # clear propagates to both blobs. + if updated_patch.litellm_params: + for field in updated_patch.litellm_params.model_fields_set: + if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.litellm_params, field) is None: + merged_litellm_params.pop(field, None) + merged_model_info.pop(field, None) if updated_patch.model_info: + for field in updated_patch.model_info.model_fields_set: + if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.model_info, field) is None: + merged_model_info.pop(field, None) + merged_litellm_params.pop(field, None) for field in _explicitly_cleared_ptu_fields(updated_patch.model_info): merged_model_info.pop(field, None) @@ -845,9 +816,8 @@ async def patch_model( """ PATCH Endpoint for partial model updates. - JSON Merge Patch semantics over `litellm_params` and `model_info`: a key absent from the - body is unchanged, a key sent as null is removed from the stored row, and a key sent with a - value is set (identity and ownership keys such as `id` and `team_id` ignore a null). + Only updates the fields specified in the request while preserving other existing values. + Follows proper PATCH semantics by only modifying provided fields. Args: model_id: The ID of the model to update diff --git a/litellm/router.py b/litellm/router.py index 1252d7e7487..934a4ac86a9 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -628,15 +628,6 @@ RETRY_BREADCRUMB_EXCLUDED_KWARGS: Final = frozenset( RETRY_BREADCRUMB_LIMIT: Final = 4 -# Cost-map keys created by _register_deployment_in_model_cost, which shares one flat -# namespace with the built-in model catalog. Only a key it created may be evicted, or a -# deployment whose id names a real model would strip that model's pricing and -# capabilities for every other deployment of it. delete_deployment gives a key back once no -# live router still serves that id, so a later catalog refresh that starts serving the name -# is not treated as a deployment's own. -_DEPLOYMENT_COST_MAP_KEYS: Final[set[str]] = set() # mutable-ok: ownership of shared cost-map keys - - class FallbackAwareStreamWrapper(CustomStreamWrapper): """Base for the Router's chat-completion stream wrappers, which are built around the attempt the Router picked first and have to repoint themselves when a fallback takes over.""" @@ -9770,7 +9761,6 @@ class Router: """ idx: Final = len(self.model_list) self.model_list.append(model) - _live_routers.add(self) # mutable-ok: track dynamic routers without extending their lifetimes self._invalidate_model_group_info_cache() self._invalidate_access_groups_cache() @@ -9939,12 +9929,7 @@ class Router: """Write a deployment's metadata into ``litellm.model_cost``. Runs when a deployment is added and again after a price data reload, so - the entries a refresh rebuilds are the ones a fresh boot would produce. An - entry this function created is replaced rather than merged, so a price cleared - from the deployment does not linger from an earlier registration and keep - billing at the old rate. An entry it did not create is left to merge, because - a deployment id that collides with a catalog model name shares that model's - entry with every other deployment of it. + the entries a refresh rebuilds are the ones a fresh boot would produce. Nothing is recorded for replay: a refresh walks the live routers instead, so a deleted, repointed or never-added deployment, and a discarded router, drop out of the rebuild on their own. @@ -9961,10 +9946,6 @@ class Router: } if model_id is not None: - if model_id in _DEPLOYMENT_COST_MAP_KEYS: - litellm.model_cost.pop(model_id, None) # mutable-ok: remove cleared prices from the shared entry - elif model_id not in litellm.model_cost: - _DEPLOYMENT_COST_MAP_KEYS.add(model_id) # mutable-ok: retain shared ownership across reloads litellm.register_model( model_cost={model_id: model_info}, persist_across_reloads=False, @@ -10061,11 +10042,6 @@ class Router: _budget_limiter: Final = self._get_router_deployment_budget_limiter() if _budget_limiter is not None: _budget_limiter.unregister_deployment_budget(model_id=id) - if not any( - router is not self and id in router.model_id_to_deployment_index_map - for router in tuple(_live_routers) - ): - _DEPLOYMENT_COST_MAP_KEYS.discard(id) # mutable-ok: the last owning router released this key try: self._unregister_pre_routing_strategy_for_deployment( deployment=item if isinstance(item, Deployment) else Deployment(**item) diff --git a/tests/e2e/coverage_registry/mgmt.yaml b/tests/e2e/coverage_registry/mgmt.yaml index 83f1711a245..c8d7037d2fd 100644 --- a/tests/e2e/coverage_registry/mgmt.yaml +++ b/tests/e2e/coverage_registry/mgmt.yaml @@ -76,8 +76,6 @@ - {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)"} - {id: mgmt.credential.new.serves_request, module: mgmt, tier: P1, surface: api, assertions: [serves_request], source: "credential_endpoints/endpoints.py:42", rationale: "Stored credential resolves into a deployment and serves a live /messages request"} - {id: mgmt.model.test_connection.happy_path, module: mgmt, tier: P0, surface: api, assertions: [happy_path], source: "_health_endpoints.py:1785", rationale: "Test Connection for a responses-mode Bedrock Mantle deployment reaches the live provider and reports success; this exact shape 500ed on an acompletion partial before v1.91.0", fail_before_fix: proven} -- {id: mgmt.model.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "model_management_endpoints.py:731", rationale: "A partial PATCH changes only the keys it names; every other stored key reads back byte-for-byte, and the new rate reaches billing"} -- {id: mgmt.model.update.clear_persists, module: mgmt, tier: P0, surface: api, assertions: [clear_persists], source: "model_management_endpoints.py:731", fail_before_fix: proven, rationale: "An explicit null on PATCH removes the key from the stored row and billing falls back to the cost map; before the fix only mirrored pricing keys could be cleared"} - {id: mgmt.mcp_server.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1577", rationale: "Every field of an admin-created MCP server reads back verbatim, by id and in the list, on every replica"} - {id: mgmt.mcp_server.list.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1112", rationale: "The MCP page grid lists a created server with the same field values its detail view reports"} - {id: mgmt.mcp_server.update.preserves_unrelated_fields, module: mgmt, tier: P0, surface: api, assertions: [preserves_unrelated_fields], source: "mcp_management_endpoints.py:2665", rationale: "A dashboard edit of one field leaves the others intact and is visible on every replica after one save; edits that took several saves to stick were a customer defect"} diff --git a/tests/e2e/management/test_model_lifecycle_e2e.py b/tests/e2e/management/test_model_lifecycle_e2e.py deleted file mode 100644 index 99044efeb46..00000000000 --- a/tests/e2e/management/test_model_lifecycle_e2e.py +++ /dev/null @@ -1,365 +0,0 @@ -"""Live e2e: the lifecycle of a DB-stored deployment through the model management -routes, read back on every gateway replica. - -Each test registers its own gpt-4o-mini mock deployment through /model/new (deleted -on teardown) with non-default pricing, context window, mode, and api_base pinned, then -walks the lifecycle up to the step it proves: the create reads back field for field, -a partial PATCH changes only the key it names, an explicit null on PATCH removes the -key from the stored row (JSON Merge Patch), a call after the price clear is billed at -the cost map's rate rather than the cleared override, and a delete removes the -deployment from /model/info and makes the model name unknown to /chat/completions. - -The stored row is read back from /model/info, a control-plane route with one answer -behind it. What every gateway must agree on is which models it serves, so the create -and delete steps poll /v1/models on every URL in PROXY_REPLICA_URLS through -ProxyClient.read_model_back_everywhere, failing by name on the gateway that never converged. -""" - -from __future__ import annotations - -import math -import time -from collections.abc import Callable -from dataclasses import dataclass -from typing import Final - -import pytest -from pydantic import BaseModel - -from e2e_config import unique_marker -from e2e_http import unwrap -from lifecycle import ResourceManager -from management_client import ManagementClient -from models import ( - ChatBody, - ChatMessage, - Clear, - LiteLLMParamsBody, - LiteLLMParamsPatch, - ModelInfoBody, - ModelInfoEntry, - ModelInfoResponse, - ModelNewBody, - ModelPatchBody, - ModelsListResponse, - SpendLogRow, -) - -pytestmark = pytest.mark.e2e - -BACKEND_MODEL: Final = "gpt-4o-mini" -PINNED_API_BASE: Final = "https://pinned.example.invalid/v1" -PINNED_MAX_INPUT_TOKENS: Final = 4096 -PINNED_INPUT_RATE: Final = 1e-05 -UPDATED_INPUT_RATE: Final = 2e-05 -PINNED_OUTPUT_RATE: Final = 3e-05 - -# A PATCH lands on the control plane, and each gateway picks it up on its own config -# reload, so the first call after the write can still be billed at the old rate. There -# is no price on the gateway's data-plane surface to poll, so the billing steps drive -# calls until the new rate shows up in the spend row and let the deadline be what fails. -BILLING_CONVERGENCE_TIMEOUT: Final = 90.0 -BILLING_CONVERGENCE_INTERVAL: Final = 5.0 - - -@dataclass(frozen=True, slots=True) -class Registered: - model_name: str - model_id: str - - -class _ErrorDetail(BaseModel): - message: str - - -class _ErrorEnvelope(BaseModel): - error: _ErrorDetail - - -def _register(client: ManagementClient, resources: ResourceManager) -> Registered: - """Register a mock gpt-4o-mini deployment with every field under test pinned to a - non-default value, deleted on teardown. max_input_tokens is pinned in - litellm_params only: a value in model_info is copied into the shared cost-map - entry for the backend model, which would leak into every other gpt-4o-mini - deployment on the proxy.""" - model_name: Final = f"e2e-lifecycle-{unique_marker()}" - model_id: Final = client.proxy.register_model( - ModelNewBody( - model_name=model_name, - litellm_params=LiteLLMParamsBody( - model=BACKEND_MODEL, - mock_response="ok", - api_base=PINNED_API_BASE, - input_cost_per_token=PINNED_INPUT_RATE, - output_cost_per_token=PINNED_OUTPUT_RATE, - max_input_tokens=PINNED_MAX_INPUT_TOKENS, - ), - model_info=ModelInfoBody(mode="chat"), - ) - ) - resources.defer(lambda: client.proxy.delete_model(model_id)) - return Registered(model_name=model_name, model_id=model_id) - - -def _entry(body: ModelInfoResponse, model_name: str) -> ModelInfoEntry | None: - return next((entry for entry in body.data if entry.model_name == model_name), None) - - -def _stored_entry( - client: ManagementClient, - model_name: str, - *, - converged: Callable[[ModelInfoEntry], bool], -) -> ModelInfoEntry: - """The stored /model/info row for `model_name`, once it satisfies `converged`. - - /model/info is a control-plane route: the gateways named in PROXY_REPLICA_URLS - serve the LLM surface only, so the stored row has one answer, not one per - gateway. What every gateway must agree on is which models it serves, and - `_assert_served_everywhere` / `_assert_absent_everywhere` poll /v1/models for - that.""" - - def has_converged(body: ModelInfoResponse) -> bool: - entry: Final = _entry(body, model_name) - return entry is not None and converged(entry) - - body: Final = client.proxy.read_model_back("/model/info", ModelInfoResponse, predicate=has_converged) - entry: Final = _entry(body, model_name) - assert entry is not None, f"/model/info stopped listing {model_name!r} between the poll and the read" - return entry - - -def _serves(body: ModelsListResponse, model_name: str) -> bool: - return any(entry.id == model_name for entry in body.data) - - -def _assert_served_everywhere(client: ManagementClient, model_name: str) -> None: - _ = client.proxy.read_model_back_everywhere( - "/v1/models", ModelsListResponse, predicate=lambda body: _serves(body, model_name) - ) - - -def _assert_absent_everywhere(client: ManagementClient, model_name: str) -> None: - _ = client.proxy.read_model_back_everywhere( - "/v1/models", ModelsListResponse, predicate=lambda body: not _serves(body, model_name) - ) - - -def _assert_untouched_keys_as_created(entry: ModelInfoEntry, replica: str) -> None: - """The keys no later step names read back byte-for-byte as /model/new wrote them.""" - params: Final = entry.litellm_params - assert params.model == BACKEND_MODEL, f"{replica}: litellm_params.model {params.model!r} != {BACKEND_MODEL!r}" - assert params.api_base == PINNED_API_BASE, f"{replica}: api_base {params.api_base!r} != {PINNED_API_BASE!r}" - assert params.output_cost_per_token == PINNED_OUTPUT_RATE, ( - f"{replica}: output_cost_per_token {params.output_cost_per_token} != {PINNED_OUTPUT_RATE}" - ) - assert entry.model_info.mode == "chat", f"{replica}: model_info.mode {entry.model_info.mode!r} != 'chat'" - - -def _approx_equal(actual: float, expected: float) -> bool: - return math.isclose(actual, expected, rel_tol=1e-2, abs_tol=1e-9) - - -def _priced(rows: list[SpendLogRow]) -> bool: - return any(row.metadata and row.metadata.cost_breakdown and row.metadata.cost_breakdown.input_cost for row in rows) - - -def _billed_input_cost(client: ManagementClient, model_name: str, key: str) -> tuple[int, float]: - """Drive one chat completion through `model_name` and return the prompt tokens and - input cost its spend row recorded, so a test can assert the rate the gateway actually - billed rather than only the rate it stored.""" - chat: Final = unwrap( - client.proxy.chat( - key, - ChatBody( - model=model_name, - messages=[ChatMessage(role="user", content=f"reply with one word {unique_marker()}")], - max_tokens=16, - ), - ) - ) - assert chat.id is not None, f"chat completion carried no id to find its spend row by: {chat}" - - rows: Final = client.proxy.poll_logs_for_request_id(chat.id, predicate=_priced) - row: Final = next((row for row in rows if row.request_id == chat.id), None) - assert row is not None and row.metadata and row.metadata.cost_breakdown, ( - f"no priced spend row for request {chat.id} before the deadline: {rows}" - ) - prompt_tokens: Final = row.prompt_tokens or 0 - input_cost: Final = row.metadata.cost_breakdown.input_cost - assert prompt_tokens > 0 and input_cost is not None, f"spend row logged no prompt tokens or input cost: {row}" - return prompt_tokens, input_cost - - -def _await_billed_input_cost( - client: ManagementClient, model_name: str, key: str, *, expected_rate: float -) -> tuple[int, float]: - """Drive calls through `model_name` until one is billed at `expected_rate`, and - return the prompt tokens and input cost of the last spend row either way. - - Only the deadline ends the wait unsatisfied: a rate that never reaches the gateway - comes back as the stale cost for the caller to assert on, so the rate the caller - expects is still what decides the test.""" - deadline: Final = time.monotonic() + BILLING_CONVERGENCE_TIMEOUT - while True: - prompt_tokens, input_cost = _billed_input_cost(client, model_name, key) - if _approx_equal(input_cost, prompt_tokens * expected_rate) or time.monotonic() >= deadline: - return prompt_tokens, input_cost - time.sleep(BILLING_CONVERGENCE_INTERVAL) - - -class TestModelLifecycle: - @pytest.mark.covers("mgmt.model.add.persists") - def test_create_reads_back_every_field_and_serves_on_every_replica( - self, client: ManagementClient, resources: ResourceManager - ) -> None: - registered = _register(client, resources) - - entry = _stored_entry(client, registered.model_name, converged=lambda _entry: True) - stored = "/model/info" - - _assert_untouched_keys_as_created(entry, stored) - assert entry.litellm_params.input_cost_per_token == PINNED_INPUT_RATE, ( - f"{stored}: input_cost_per_token {entry.litellm_params.input_cost_per_token} != {PINNED_INPUT_RATE}" - ) - assert entry.litellm_params.max_input_tokens == PINNED_MAX_INPUT_TOKENS, ( - f"{stored}: max_input_tokens {entry.litellm_params.max_input_tokens} != {PINNED_MAX_INPUT_TOKENS}" - ) - assert entry.model_info.id == registered.model_id, ( - f"{stored}: model_info.id {entry.model_info.id!r} != {registered.model_id!r}" - ) - - _assert_served_everywhere(client, registered.model_name) - - @pytest.mark.covers("mgmt.model.update.preserves_unrelated_fields") - def test_partial_update_changes_only_the_named_key( - self, client: ManagementClient, resources: ResourceManager, scoped_key: str - ) -> None: - registered = _register(client, resources) - - stored = client.proxy.patch_model( - registered.model_id, - ModelPatchBody(litellm_params=LiteLLMParamsPatch(input_cost_per_token=UPDATED_INPUT_RATE)), - ) - assert stored.litellm_params.input_cost_per_token == UPDATED_INPUT_RATE, ( - f"PATCH response stores input_cost_per_token {stored.litellm_params.input_cost_per_token}, " - f"sent {UPDATED_INPUT_RATE}" - ) - - entry = _stored_entry( - client, - registered.model_name, - converged=lambda entry: entry.litellm_params.input_cost_per_token == UPDATED_INPUT_RATE, - ) - stored = "/model/info" - - _assert_untouched_keys_as_created(entry, stored) - assert entry.litellm_params.max_input_tokens == PINNED_MAX_INPUT_TOKENS, ( - f"{stored}: max_input_tokens {entry.litellm_params.max_input_tokens} != {PINNED_MAX_INPUT_TOKENS}" - ) - assert entry.model_info.input_cost_per_token == UPDATED_INPUT_RATE, ( - f"{stored}: model_info.input_cost_per_token {entry.model_info.input_cost_per_token} " - f"did not mirror the updated {UPDATED_INPUT_RATE}" - ) - - prompt_tokens, input_cost = _await_billed_input_cost( - client, registered.model_name, scoped_key, expected_rate=UPDATED_INPUT_RATE - ) - assert _approx_equal(input_cost, prompt_tokens * UPDATED_INPUT_RATE), ( - f"input_cost {input_cost} != {prompt_tokens} tokens * updated rate {UPDATED_INPUT_RATE} " - f"= {prompt_tokens * UPDATED_INPUT_RATE}; the partial update did not reach billing" - ) - - @pytest.mark.covers("mgmt.model.update.clear_persists") - def test_explicit_null_removes_the_key_from_the_stored_row( - self, client: ManagementClient, resources: ResourceManager - ) -> None: - registered = _register(client, resources) - - stored = client.proxy.patch_model( - registered.model_id, - ModelPatchBody(litellm_params=LiteLLMParamsPatch(max_input_tokens=Clear(), input_cost_per_token=Clear())), - ) - stored_params = stored.litellm_params.model_fields_set - assert "max_input_tokens" not in stored_params, ( - f"stored litellm_params still carries max_input_tokens " - f"{stored.litellm_params.max_input_tokens} after an explicit null" - ) - assert "input_cost_per_token" not in stored_params, ( - f"stored litellm_params still carries input_cost_per_token " - f"{stored.litellm_params.input_cost_per_token} after an explicit null" - ) - assert "max_input_tokens" not in stored.model_info.model_fields_set, ( - f"stored model_info carries max_input_tokens {stored.model_info.max_input_tokens} after the clear" - ) - assert "input_cost_per_token" not in stored.model_info.model_fields_set, ( - f"stored model_info still mirrors input_cost_per_token {stored.model_info.input_cost_per_token}" - ) - - cost_map_input_rate = client.proxy.model_cost_map()[BACKEND_MODEL].input_cost_per_token - assert cost_map_input_rate is not None, f"cost map has no input rate for {BACKEND_MODEL}" - entry = _stored_entry( - client, - registered.model_name, - converged=lambda entry: "max_input_tokens" not in entry.litellm_params.model_fields_set, - ) - stored = "/model/info" - - _assert_untouched_keys_as_created(entry, stored) - served = entry.litellm_params.model_fields_set - assert "max_input_tokens" not in served, ( - f"{stored}: litellm_params still serves max_input_tokens {entry.litellm_params.max_input_tokens}" - ) - assert "input_cost_per_token" not in served, ( - f"{stored}: litellm_params still serves input_cost_per_token {entry.litellm_params.input_cost_per_token}" - ) - assert entry.model_info.input_cost_per_token == cost_map_input_rate, ( - f"{stored}: model_info.input_cost_per_token {entry.model_info.input_cost_per_token} is not the " - f"cost map's {cost_map_input_rate}; the cleared override {PINNED_INPUT_RATE} still resolves" - ) - - @pytest.mark.covers("mgmt.model.update.clear_persists") - def test_cleared_price_is_billed_at_the_cost_map_rate( - self, client: ManagementClient, resources: ResourceManager, scoped_key: str - ) -> None: - registered = _register(client, resources) - _ = client.proxy.patch_model( - registered.model_id, - ModelPatchBody(litellm_params=LiteLLMParamsPatch(max_input_tokens=Clear(), input_cost_per_token=Clear())), - ) - _ = _stored_entry( - client, - registered.model_name, - converged=lambda entry: "input_cost_per_token" not in entry.litellm_params.model_fields_set, - ) - cost_map_input_rate = client.proxy.model_cost_map()[BACKEND_MODEL].input_cost_per_token - assert cost_map_input_rate is not None, f"cost map has no input rate for {BACKEND_MODEL}" - - prompt_tokens, input_cost = _await_billed_input_cost( - client, registered.model_name, scoped_key, expected_rate=cost_map_input_rate - ) - - assert _approx_equal(input_cost, prompt_tokens * cost_map_input_rate), ( - f"input_cost {input_cost} != {prompt_tokens} tokens * cost map rate {cost_map_input_rate} " - f"= {prompt_tokens * cost_map_input_rate}" - ) - assert not _approx_equal(input_cost, prompt_tokens * PINNED_INPUT_RATE), ( - f"input_cost {input_cost} is still billed at the cleared override {PINNED_INPUT_RATE}" - ) - - @pytest.mark.covers("mgmt.model.delete.persists") - def test_delete_removes_the_deployment_everywhere( - self, client: ManagementClient, resources: ResourceManager, scoped_key: str - ) -> None: - registered = _register(client, resources) - _ = _stored_entry(client, registered.model_name, converged=lambda _entry: True) - - client.delete_model_strict(registered.model_id) - - _assert_absent_everywhere(client, registered.model_name) - refused = client.chat_status(scoped_key, registered.model_name, "hi this is a test") - assert refused.status_code == 400, ( - f"chat against the deleted model must be rejected 400, got {refused.status_code}: {refused.body[:300]}" - ) - envelope = _ErrorEnvelope.model_validate_json(refused.body) - assert envelope.error.message, f"400 body must carry an error message: {refused.body[:300]}" diff --git a/tests/e2e/models.py b/tests/e2e/models.py index 8db37bd25a5..62810e6cfd9 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -655,11 +655,6 @@ class OcrResponse(BaseModel): # ---------- spend logs ---------- -class CostBreakdown(BaseModel): - input_cost: float | None = None - output_cost: float | None = None - - class GuardrailEntityMatch(BaseModel): entity_type: str score: float @@ -677,7 +672,6 @@ class GuardrailRunRecord(BaseModel): class SpendLogMetadata(BaseModel): - cost_breakdown: CostBreakdown | None = None applied_guardrails: list[str] | None = None guardrail_information: list[GuardrailRunRecord] | None = None @@ -821,42 +815,15 @@ class CustomPricing(BaseModel): return prompt_tokens * self.input_cost_per_token + completion_tokens * self.output_cost_per_token -class DeploymentParams(CustomPricing): - """The litellm_params half of a /model/info row: the stored deployment as written, - credentials scrubbed. Unlike model_info it is never back-filled from the cost map, - so a key the store dropped is absent here (check `model_fields_set`).""" - - model: str | None = None - api_base: str | None = None - max_input_tokens: int | None = None - - -class DeploymentModelInfo(CustomPricing): - id: str | None = None - max_input_tokens: int | None = None - - class ModelInfoEntry(BaseModel): """One /model/info row. `litellm_params` is the configured deployment (carries any custom-pricing override); `model_info` is the price the proxy resolved for - it - the override merged over the cost-map defaults, so a key cleared from the - stored blob reads as the cost-map default here.""" + it - the override merged over the cost-map defaults.""" model_config = ConfigDict(protected_namespaces=()) model_name: str - litellm_params: DeploymentParams = DeploymentParams() - model_info: DeploymentModelInfo = DeploymentModelInfo() - - -class StoredDeployment(BaseModel): - """PATCH /model/{model_id}/update answers with the row as stored: both blobs raw, - nothing back-filled, so a cleared key is absent from `model_fields_set` of the - blob it was cleared from.""" - - model_config = ConfigDict(protected_namespaces=()) - model_name: str - litellm_params: DeploymentParams - model_info: DeploymentModelInfo + litellm_params: CustomPricing = CustomPricing() + model_info: CustomPricing = CustomPricing() class ModelInfoResponse(BaseModel): @@ -957,10 +924,9 @@ class LiteLLMParamsBody(BaseModel): timeout: float | None = None tpm: int | None = None weight: int | None = None - max_input_tokens: int | None = None -ModelMode = Literal["chat", "batch", "realtime", "image_generation"] +ModelMode = Literal["batch", "realtime", "image_generation"] class ModelInfoBody(BaseModel): @@ -970,7 +936,6 @@ class ModelInfoBody(BaseModel): # constraint when a prior run's teardown had not removed the row. id: str | None = None mode: ModelMode | None = None - max_input_tokens: int | None = None access_groups: list[str] | None = None team_id: str | None = None allowed_fails_policy: dict[str, int] | None = None @@ -999,37 +964,6 @@ class ModelUpdateBody(BaseModel): model_info: ModelInfoBody -class Clear(BaseModel): - """Serializes to JSON null. The transport dumps every body with exclude_none, so a - field set to this is how a patch carries the explicit null that removes a stored key.""" - - @model_serializer - def _as_null(self) -> None: - return None - - -class LiteLLMParamsPatch(BaseModel): - api_base: str | Clear | None = None - max_input_tokens: int | Clear | None = None - input_cost_per_token: float | Clear | None = None - output_cost_per_token: float | Clear | None = None - - -class ModelInfoPatch(BaseModel): - mode: ModelMode | Clear | None = None - max_input_tokens: int | Clear | None = None - - -class ModelPatchBody(BaseModel): - """PATCH /model/{model_id}/update body, JSON Merge Patch over the stored deployment: - a field left None is dropped from the body and unchanged, a field set to `Clear()` - is sent as null and removed, a field with a value is set.""" - - model_config = ConfigDict(protected_namespaces=()) - litellm_params: LiteLLMParamsPatch | None = None - model_info: ModelInfoPatch | None = None - - class ModelListEntry(BaseModel): id: str diff --git a/tests/e2e/proxy_client.py b/tests/e2e/proxy_client.py index fa1b06fe7ed..1bac5116a9d 100644 --- a/tests/e2e/proxy_client.py +++ b/tests/e2e/proxy_client.py @@ -10,10 +10,10 @@ from __future__ import annotations import time import warnings -from collections.abc import Callable, Iterator, Mapping +from collections.abc import Callable, Mapping from dataclasses import dataclass -from datetime import datetime from functools import reduce +from datetime import datetime from types import MappingProxyType from typing import Final @@ -62,7 +62,6 @@ from models import ( ModelMode, ModelNewBody, ModelNewResponse, - ModelPatchBody, ModelsListParams, ModelsListResponse, ModelUpdateBody, @@ -73,7 +72,6 @@ from models import ( SpendLogsPage, SpendLogsPageParams, SpendLogsParams, - StoredDeployment, ToolsetCreateBody, ToolsetRow, ToolsetUpdateBody, @@ -134,103 +132,6 @@ class NotServableOn: last_result: Result[ModelsListResponse] | None -type BodyReader[R: BaseModel] = Callable[[float], Result[R]] - - -@dataclass(frozen=True, slots=True) -class BodyNotConverged[R: BaseModel]: - """The deadline passed without a read the predicate accepted; `last_result` is the - final read, so the caller can tell a body that never matched from a read that - failed.""" - - last_result: Result[R] | None - - -@dataclass(frozen=True, slots=True) -class BodyConverged[R: BaseModel]: - """Every replica answered a body the predicate accepted; `bodies` is the last read - per replica.""" - - bodies: Mapping[str, R] - - -@dataclass(frozen=True, slots=True) -class BodyNeverConvergedOn[R: BaseModel]: - """`BodyNotConverged` labeled with the replica whose reads never satisfied the predicate.""" - - replica: str - last_result: Result[R] | None - - -def await_body_converged[R: BaseModel]( - read: BodyReader[R], - *, - predicate: Callable[[R], bool], - timeout: float, - interval: float, - request_timeout: float, - now: Callable[[], float], - sleep: Callable[[float], None], -) -> Success[R] | BodyNotConverged[R]: - """Poll `read` until it answers a body `predicate` accepts, or `timeout` passes. - - Each read's request timeout is clamped to the remaining budget, and the sleep - between reads to the time left, so the last read before the deadline is never - skipped. Clock and sleep are injected.""" - deadline: Final = now() + timeout - - def reads() -> Iterator[Result[R]]: - while (remaining := deadline - now()) > 0: - yield read(min(request_timeout, remaining)) - sleep(min(interval, max(deadline - now(), 0.0))) - - def attempts() -> Iterator[Success[R] | BodyNotConverged[R]]: - for result in reads(): - if isinstance(result, Success) and predicate(result.data): - yield result - return - yield BodyNotConverged(last_result=result) - - initial: Final[Success[R] | BodyNotConverged[R]] = BodyNotConverged(last_result=None) - return reduce(lambda _previous, result: result, attempts(), initial) - - -def await_body_converged_everywhere[R: BaseModel]( - readers: Mapping[str, BodyReader[R]], - *, - predicate: Callable[[R], bool], - timeout: float, - interval: float, - request_timeout: float, - now: Callable[[], float], - sleep: Callable[[float], None], -) -> BodyConverged[R] | BodyNeverConvergedOn[R]: - """`await_body_converged` against every replica in turn, each with the full budget, so a - write counts as landed only once every replica serves it.""" - def read_replica( - outcome: BodyConverged[R] | BodyNeverConvergedOn[R], - item: tuple[str, BodyReader[R]], - ) -> BodyConverged[R] | BodyNeverConvergedOn[R]: - if isinstance(outcome, BodyNeverConvergedOn): - return outcome - replica, read = item - match await_body_converged( - read, - predicate=predicate, - timeout=timeout, - interval=interval, - request_timeout=request_timeout, - now=now, - sleep=sleep, - ): - case Success(data=data): - return BodyConverged(bodies=MappingProxyType({**outcome.bodies, replica: data})) - case BodyNotConverged(last_result=last_result): - return BodyNeverConvergedOn(replica=replica, last_result=last_result) - initial: Final[BodyConverged[R] | BodyNeverConvergedOn[R]] = BodyConverged(bodies=MappingProxyType({})) - return reduce(read_replica, readers.items(), initial) - - def await_servable( list_models: Callable[[float], Result[ModelsListResponse]], *, @@ -757,102 +658,6 @@ class ProxyClient: ) ) - def patch_model(self, model_id: str, body: ModelPatchBody) -> StoredDeployment: - """JSON Merge Patch the deployment `model_id` via PATCH /model/{model_id}/update: - a field the body omits is unchanged, one sent as null is removed from the stored - row, one sent with a value is set. See ModelPatchBody for how a null is sent. - Returns the row as stored after the write.""" - return unwrap( - self.transport.patch( - f"/model/{model_id}/update", - headers=self.transport.master, - json=body, - response_type=StoredDeployment, - ) - ) - - def read_model_back_everywhere[R: BaseModel]( - self, path: str, response_type: type[R], *, predicate: Callable[[R], bool] - ) -> Mapping[str, R]: - """GET `path` on every replica until each answers a body `predicate` accepts, - polling to poll_timeout, and return the last body per replica. - - Fails naming the replica that never converged, so a write that reached one - gateway but not the others is caught instead of passing on whichever gateway - the balancer answered from. Falls back to the single proxy address when no - replica list is configured. - - `path` must be a data-plane route. The replicas are gateways, which serve only - the LLM surface, so a control-plane path answers on exactly one service and - 404s on every replica in a split deployment: asking each replica for one is - never the question the caller means. Read those through `self.transport` - instead, which routes them to the control plane.""" - if is_control_plane_path(path): - raise AssertionError( - f"read_model_back_everywhere({path!r}) asks every data-plane replica for a control-plane route. " - "The replicas are gateways and do not serve it; poll a data-plane path such as /v1/models " - "here, and read the control plane through the shared transport." - ) - readers: Final = { - url: self._body_reader(transport, path, response_type) - for url, transport in self._read_back_replicas().items() - } - outcome: Final = await_body_converged_everywhere( - readers, - predicate=predicate, - timeout=self.poll_timeout, - interval=self.poll_interval, - request_timeout=REQUEST_TIMEOUT, - now=time.monotonic, - sleep=time.sleep, - ) - match outcome: - case BodyConverged(bodies=bodies): - return bodies - case BodyNeverConvergedOn(replica=replica, last_result=last_result): - raise AssertionError( - f"GET {path} on {replica} never answered the expected body within " - f"{self.poll_timeout}s; last read: {last_result}" - ) - - def read_model_back[R: BaseModel](self, path: str, response_type: type[R], *, predicate: Callable[[R], bool]) -> R: - """GET `path` through the shared transport until the body satisfies `predicate`, - polling to poll_timeout, and return that body. - - The counterpart to `read_model_back_everywhere` for a control-plane route such as - /model/info: the stored row lives in one database behind one control plane, so - there is a single answer to converge on rather than one per gateway.""" - outcome: Final = await_body_converged_everywhere( - {CONTROL_PLANE_BASE_URL: self._body_reader(self.transport, path, response_type)}, - predicate=predicate, - timeout=self.poll_timeout, - interval=self.poll_interval, - request_timeout=REQUEST_TIMEOUT, - now=time.monotonic, - sleep=time.sleep, - ) - match outcome: - case BodyConverged(bodies=bodies): - return bodies[CONTROL_PLANE_BASE_URL] - case BodyNeverConvergedOn(last_result=last_result): - raise AssertionError( - f"GET {path} never answered the expected body within " - f"{self.poll_timeout}s; last read: {last_result}" - ) - - def _read_back_replicas(self) -> Mapping[str, Transport]: - return self.replicas or MappingProxyType({CONTROL_PLANE_BASE_URL: self.transport}) - - @staticmethod - def _body_reader[R: BaseModel](transport: Transport, path: str, response_type: type[R]) -> BodyReader[R]: - return lambda timeout: transport.get( - path, - headers=transport.master, - params=NoBody(), - response_type=response_type, - timeout=timeout, - ) - def delete_model(self, model_id: str) -> None: result = self.transport.post( "/model/delete", diff --git a/tests/e2e/test_proxy_client.py b/tests/e2e/test_proxy_client.py index cbf7f5648d4..3b84a47e3cc 100644 --- a/tests/e2e/test_proxy_client.py +++ b/tests/e2e/test_proxy_client.py @@ -20,12 +20,8 @@ from typing import Final, cast import pytest from e2e_config import parse_replica_urls from e2e_http import Result, Success -from models import KeyInfo, KeyInfoResponse, ModelInfoEntry, ModelInfoResponse, ModelListEntry, ModelsListResponse +from models import KeyInfo, KeyInfoResponse, ModelListEntry, ModelsListResponse from proxy_client import ( - BodyReader, - BodyConverged, - BodyNeverConvergedOn, - await_body_converged_everywhere, ConvergeOutcome, Converged, EverywhereConverged, @@ -278,54 +274,3 @@ class TestReplicasFor: client: Final = ProxyClient(transport=_NO_TRANSPORTS, replicas={}, control_replicas={}) with pytest.raises(AssertionError, match="no replica is configured"): _ = client.replicas_for("/v1/models") - - -def _info(*model_names: str) -> Success[ModelInfoResponse]: - entries: Final = [ModelInfoEntry(model_name=model_name) for model_name in model_names] - return Success(status_code=200, data=ModelInfoResponse(data=entries)) - - -def _reader(results: Iterable[Success[ModelInfoResponse]]) -> BodyReader[ModelInfoResponse]: - it: Final = iter(results) - return lambda _timeout: next(it) - - -def _lists_model(body: ModelInfoResponse) -> bool: - return any(entry.model_name == MODEL for entry in body.data) - - -def _read_back( - readers: Mapping[str, BodyReader[ModelInfoResponse]], -) -> tuple[BodyConverged[ModelInfoResponse] | BodyNeverConvergedOn[ModelInfoResponse], FakeClock]: - clock: Final = FakeClock() - outcome: Final = await_body_converged_everywhere( - readers, - predicate=_lists_model, - timeout=TIMEOUT, - interval=INTERVAL, - request_timeout=5.0, - now=clock.now, - sleep=clock.sleep, - ) - return outcome, clock - - -class TestAwaitBodyConvergedEverywhere: - def test_waits_for_the_lagging_replica_and_returns_every_body(self) -> None: - readers: Final = { - "gateway-1": _reader(repeat(_info(MODEL))), - "gateway-2": _reader(chain(repeat(_info(), 2), repeat(_info(MODEL)))), - } - outcome, clock = _read_back(readers) - assert outcome == BodyConverged(bodies={"gateway-1": _info(MODEL).data, "gateway-2": _info(MODEL).data}) - assert clock.elapsed == 2 * INTERVAL - - @pytest.mark.parametrize("lagging", ["gateway-1", "gateway-2"]) - def test_fails_naming_the_replica_that_never_converges(self, lagging: str) -> None: - readers: Final = { - "gateway-1": _reader(repeat(_info(MODEL))), - "gateway-2": _reader(repeat(_info(MODEL))), - } | {lagging: _reader(repeat(_info()))} - outcome, clock = _read_back(readers) - assert outcome == BodyNeverConvergedOn(replica=lagging, last_result=_info()) - assert clock.elapsed >= TIMEOUT diff --git a/tests/e2e/transport.py b/tests/e2e/transport.py index 804e073a4a0..44fdbaa3e41 100644 --- a/tests/e2e/transport.py +++ b/tests/e2e/transport.py @@ -306,7 +306,6 @@ CONTROL_PLANE_PREFIXES: tuple[str, ...] = ( "/config", "/guardrails", "/openapi.json", - "/public/", ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index 1376727e296..c02f886fc31 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -3115,6 +3115,9 @@ class TestUpdateDBModelClearPricing: """Sending an explicit `null` for a pricing field must remove it from both `litellm_params` and `model_info` (SPECIAL_MODEL_INFO_PARAMS are mirrored between the two by Deployment.__init__). + + Restricted to SPECIAL_MODEL_INFO_PARAMS so non-pricing fields (e.g. team_id) + cannot be cleared via this path. """ def test_clear_input_cost_removes_from_both_blobs(self): @@ -3190,10 +3193,10 @@ class TestUpdateDBModelClearPricing: assert params["input_cost_per_token"] == 0.000001 assert params["output_cost_per_token"] == 0.000007 - def test_null_on_one_field_leaves_other_fields_alone(self): - """A null clears only the key it names: pricing the patch never mentions and - the ownership key team_id stay put, so a team admin can't ungate a - team-scoped model through the clear path. + def test_null_on_non_pricing_field_does_not_clear(self): + """Security guard: only SPECIAL_MODEL_INFO_PARAMS can be cleared via null. + Privileged or unrelated model_info fields (e.g. team_id) must be unaffected + by the null-clearing path so a team admin can't ungate a team-scoped model. """ from litellm.proxy.management_endpoints.model_management_endpoints import ( update_db_model, @@ -3214,6 +3217,8 @@ class TestUpdateDBModelClearPricing: model_info=ModelInfo(id="dep-pricing-1", team_id="team-keep-me"), ) + # Patch sends a null for api_base (non-SPECIAL field). Must NOT clear team_id + # or any other non-pricing field from the merged dict. result = update_db_model( db_model=db_model, updated_patch=updateDeployment( @@ -3389,171 +3394,6 @@ class TestUpdateDBModelClearPricing: assert info["cache_creation_input_token_cost"] == 0.000003 -_PROTECTED_MODEL_INFO_VALUES = { - "team_id": "team-keep-me", - "team_public_model_name": "team-facing-name", - "access_groups": ["group-a"], - "created_at": "2026-01-01T00:00:00+00:00", - "created_by": "creator", - "updated_at": "2026-01-02T00:00:00+00:00", - "updated_by": "updater", - "blocked": True, -} - - -def _build_db_model_with_pinned_model_info(): - """Deployment whose stored blobs pin non-pricing keys an earlier save wrote, next to a - pricing override, so a clear can be checked key by key.""" - from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo - - return Deployment( - model_name="pinned-gpt-4o-mini", - litellm_params=LiteLLM_Params( - model="gpt-4o-mini", input_cost_per_token=0.000001, max_input_tokens=4096 - ), - model_info=ModelInfo( - id="dep-pinned-0", - max_input_tokens=4096, - mode="chat", - supports_vision=True, - **_PROTECTED_MODEL_INFO_VALUES, - ), - ) - - -class TestUpdateDBModelNullClearsAnyKey: - """JSON Merge Patch on PATCH /model/{id}/update: a key sent as null is removed from the - stored blob it was sent in, whatever the key, except the identity and ownership keys, - whose nulls are ignored.""" - - def test_model_info_nulls_remove_pinned_non_pricing_keys(self): - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - result = update_db_model( - db_model=_build_db_model_with_pinned_model_info(), - updated_patch=updateDeployment.model_validate( - {"model_info": {"max_input_tokens": None, "mode": None}} - ), - ) - - info = json.loads(result["model_info"]) - assert "max_input_tokens" not in info - assert "mode" not in info - assert info["supports_vision"] is True - assert info["input_cost_per_token"] == 0.000001 - - def test_litellm_params_null_removes_pinned_non_pricing_key(self): - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - result = update_db_model( - db_model=_build_db_model_with_pinned_model_info(), - updated_patch=updateDeployment.model_validate( - {"litellm_params": {"max_input_tokens": None}} - ), - ) - - params = json.loads(result["litellm_params"]) - info = json.loads(result["model_info"]) - assert "max_input_tokens" not in params - assert params["model"] == "gpt-4o-mini" - assert params["input_cost_per_token"] == 0.000001 - assert info["max_input_tokens"] == 4096 - - def test_omitted_key_is_untouched_by_a_null_elsewhere(self): - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - result = update_db_model( - db_model=_build_db_model_with_pinned_model_info(), - updated_patch=updateDeployment.model_validate( - {"model_info": {"mode": None, "supports_vision": False}} - ), - ) - - info = json.loads(result["model_info"]) - assert "mode" not in info - assert info["supports_vision"] is False - assert info["max_input_tokens"] == 4096 - - @pytest.mark.parametrize("field", sorted(_PROTECTED_MODEL_INFO_VALUES)) - def test_null_on_protected_key_is_ignored(self, field): - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - result = update_db_model( - db_model=_build_db_model_with_pinned_model_info(), - updated_patch=updateDeployment.model_validate({"model_info": {field: None}}), - ) - - info = json.loads(result["model_info"]) - assert info[field] == _PROTECTED_MODEL_INFO_VALUES[field] - assert info["max_input_tokens"] == 4096 - - def test_echoing_the_read_back_blob_preserves_every_stored_key(self): - """The Admin UI edit form submits the whole /model/info row back, and that read reports - every key the deployment never stored as an explicit null. Those nulls have to stay - no-ops: a write drops None before storing, so a null in the echoed blob always names a - key the stored row does not carry. - """ - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - db_model = _build_db_model_with_pinned_model_info() - echoed = { - "id": "dep-pinned-0", - "max_input_tokens": 4096, - "mode": "chat", - "supports_vision": True, - "input_cost_per_token": 0.000001, - "team_id": "team-keep-me", - "base_model": None, - "tier": None, - "max_output_tokens": None, - "supports_function_calling": None, - "cache_read_input_token_cost": None, - } - - result = update_db_model( - db_model=db_model, - updated_patch=updateDeployment.model_validate({"model_info": echoed}), - ) - - info = json.loads(result["model_info"]) - assert info["max_input_tokens"] == 4096 - assert info["mode"] == "chat" - assert info["supports_vision"] is True - assert info["input_cost_per_token"] == 0.000001 - assert info["team_id"] == "team-keep-me" - for never_stored in ("base_model", "tier", "max_output_tokens", "supports_function_calling"): - assert never_stored not in info - - def test_null_on_pricing_key_still_clears_both_blobs(self): - from litellm.proxy.management_endpoints.model_management_endpoints import ( - update_db_model, - ) - - result = update_db_model( - db_model=_build_db_model_with_pinned_model_info(), - updated_patch=updateDeployment.model_validate( - {"model_info": {"input_cost_per_token": None}} - ), - ) - - params = json.loads(result["litellm_params"]) - info = json.loads(result["model_info"]) - assert "input_cost_per_token" not in params - assert "input_cost_per_token" not in info - assert params["max_input_tokens"] == 4096 - assert info["max_input_tokens"] == 4096 - - class TestGetModelInfoWithIdBlocked: """`ProxyConfig.get_model_info_with_id` must propagate the DB-level `blocked` column into the in-memory `model_info` dict so the router filter can read it.""" diff --git a/tests/test_litellm/test_router_model_cost_isolation.py b/tests/test_litellm/test_router_model_cost_isolation.py index d22ec60e61a..30b265905f3 100644 --- a/tests/test_litellm/test_router_model_cost_isolation.py +++ b/tests/test_litellm/test_router_model_cost_isolation.py @@ -220,198 +220,6 @@ def test_should_store_full_pricing_under_deployment_model_id(): assert entry["output_cost_per_token"] == 0.0 -def test_should_drop_a_price_the_deployment_no_longer_carries(): - """Re-registering a deployment must replace its model_id entry, not merge onto it. - - A merge left the old rate in the cost map after an operator cleared the override, so - the deployment kept billing at a price its config no longer had. - """ - backend_model = "vertex_ai/gemini-2.5-flash" - model_id = "deployment-cleared-price" - original = {model_id: litellm.model_cost.get(model_id)} - - try: - Router._register_deployment_in_model_cost( - model_id=model_id, - model_info={"input_cost_per_token": 0.005, "output_cost_per_token": 0.01}, - model=backend_model, - custom_llm_provider="vertex_ai", - ) - assert litellm.model_cost[model_id]["input_cost_per_token"] == 0.005 - - Router._register_deployment_in_model_cost( - model_id=model_id, - model_info={"mode": "chat"}, - model=backend_model, - custom_llm_provider="vertex_ai", - ) - - entry = litellm.model_cost[model_id] - assert entry.get("input_cost_per_token") != 0.005, ( - "the cleared override survived re-registration, so the deployment still bills at it" - ) - assert entry.get("output_cost_per_token") != 0.01 - finally: - _restore_model_cost_entries(original) - - -def test_should_not_strip_a_builtin_entry_when_a_deployment_id_collides_with_it(): - """Deployments are keyed into the same cost map as the built-in catalog, so a deployment - whose id happens to name a real model must not evict that model's entry. - - Stripping it would take the pricing and capability flags every other deployment of that - model reads, process-wide, until the next price-map reload. Registering twice, because - the first registration is what would mark the entry as this deployment's own. - """ - colliding_id = "gpt-4o" - original = {colliding_id: litellm.model_cost.get(colliding_id)} - builtin_max_tokens = litellm.model_cost[colliding_id]["max_tokens"] - - try: - for _ in range(2): - Router._register_deployment_in_model_cost( - model_id=colliding_id, - model_info={"id": colliding_id, "db_model": True, "mode": "chat"}, - model="gpt-4o-mini", - custom_llm_provider="openai", - ) - - entry = litellm.model_cost[colliding_id] - assert entry["max_tokens"] == builtin_max_tokens, ( - "registering a deployment under a catalog model's name wiped that model's context window" - ) - assert entry["litellm_provider"] == "openai" - assert entry["supports_vision"] is True - finally: - _restore_model_cost_entries(original) - - -def test_should_drop_a_stale_price_even_when_the_deployment_declares_a_provider(): - """A deployment may carry `litellm_provider` in its own model_info, which must not be - read as "this is a catalog entry" and stop the stale price from being dropped.""" - model_id = "deployment-provider-tagged" - original = {model_id: litellm.model_cost.get(model_id)} - - try: - Router._register_deployment_in_model_cost( - model_id=model_id, - model_info={"id": model_id, "litellm_provider": "openai", "input_cost_per_token": 0.005}, - model="gpt-4o-mini", - custom_llm_provider="openai", - ) - assert litellm.model_cost[model_id]["input_cost_per_token"] == 0.005 - - Router._register_deployment_in_model_cost( - model_id=model_id, - model_info={"id": model_id, "litellm_provider": "openai", "mode": "chat"}, - model="gpt-4o-mini", - custom_llm_provider="openai", - ) - - assert litellm.model_cost[model_id].get("input_cost_per_token") != 0.005, ( - "a deployment that declares its provider kept billing at the price it no longer carries" - ) - finally: - _restore_model_cost_entries(original) - - -def test_should_give_a_cost_map_key_back_when_the_deployment_is_deleted(): - """Deleting a deployment releases its claim on the shared cost-map key. - - Held forever, a later catalog refresh that starts publishing a model under that same - name would be treated as the deleted deployment's own entry and evicted. - """ - from litellm.router import _DEPLOYMENT_COST_MAP_KEYS - - model_id = "deployment-to-delete" - original = {model_id: litellm.model_cost.get(model_id)} - router = Router( - model_list=[ - { - "model_name": "to-delete", - "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, - "model_info": {"id": model_id, "input_cost_per_token": 0.005}, - } - ] - ) - - try: - assert model_id in _DEPLOYMENT_COST_MAP_KEYS - - assert router.delete_deployment(id=model_id) is not None - - assert model_id not in _DEPLOYMENT_COST_MAP_KEYS, ( - "a deleted deployment kept its claim on the shared cost-map key" - ) - finally: - _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) - _restore_model_cost_entries(original) - - -def test_should_keep_the_cost_map_key_while_another_router_still_serves_it(): - """Two live routers can serve the same deployment id, and the claim is process-wide. - - Releasing it when only one of them drops the deployment would put the survivor back on - merging, so the price it just cleared would keep billing. - """ - from litellm.router import _DEPLOYMENT_COST_MAP_KEYS - - model_id = "deployment-served-twice" - original = {model_id: litellm.model_cost.get(model_id)} - entry = { - "model_name": "served-twice", - "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, - "model_info": {"id": model_id, "input_cost_per_token": 0.005}, - } - first = Router(model_list=[entry]) - second = Router(model_list=[entry]) - - try: - assert model_id in _DEPLOYMENT_COST_MAP_KEYS - - assert first.delete_deployment(id=model_id) is not None - - assert model_id in _DEPLOYMENT_COST_MAP_KEYS, ( - "the claim was released while another router still served the deployment" - ) - - assert second.delete_deployment(id=model_id) is not None - assert model_id not in _DEPLOYMENT_COST_MAP_KEYS - finally: - _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) - _restore_model_cost_entries(original) - - -def test_should_keep_the_cost_map_key_while_a_dynamically_built_router_serves_it(): - """A router built with no model_list still serves whatever add_deployment gives it, so it - counts when deciding whether the shared cost-map claim can be released.""" - from litellm.router import _DEPLOYMENT_COST_MAP_KEYS - - model_id = "deployment-added-dynamically" - original = {model_id: litellm.model_cost.get(model_id)} - entry = { - "model_name": "added-dynamically", - "litellm_params": {"model": "gpt-4o-mini", "mock_response": "ok"}, - "model_info": {"id": model_id, "input_cost_per_token": 0.005}, - } - configured = Router(model_list=[entry]) - dynamic = Router() - dynamic.add_deployment(deployment=Deployment(**entry)) - - try: - assert configured.delete_deployment(id=model_id) is not None - - assert model_id in _DEPLOYMENT_COST_MAP_KEYS, ( - "the claim was released while a dynamically built router still served the deployment" - ) - - assert dynamic.delete_deployment(id=model_id) is not None - assert model_id not in _DEPLOYMENT_COST_MAP_KEYS - finally: - _DEPLOYMENT_COST_MAP_KEYS.discard(model_id) - _restore_model_cost_entries(original) - - def test_should_preserve_builtin_pricing_regardless_of_deployment_order(): """ The built-in pricing should be preserved no matter which deployment diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 93aaf3ca58c..83b0d58f2b2 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -9064,9 +9064,8 @@ export interface paths { * Patch Model * @description PATCH Endpoint for partial model updates. * - * JSON Merge Patch semantics over `litellm_params` and `model_info`: a key absent from the - * body is unchanged, a key sent as null is removed from the stored row, and a key sent with a - * value is set (identity and ownership keys such as `id` and `team_id` ignore a null). + * Only updates the fields specified in the request while preserving other existing values. + * Follows proper PATCH semantics by only modifying provided fields. * * Args: * model_id: The ID of the model to update From a1588c260232dd6ed22605fdd4db24983bb1fcee Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Wed, 9 Sep 2026 08:05:15 -0700 Subject: [PATCH 089/105] fix(mcp): preserve stream failures across transports --- litellm/experimental_mcp_client/client.py | 7 +- .../mcp_server/rest_endpoints.py | 25 +- .../test_mcp_client.py | 240 ++++++++++++++++++ .../mcp_server/test_rest_endpoints.py | 48 ++++ 4 files changed, 313 insertions(+), 7 deletions(-) diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index cabcc6c03ba..5cdc4efdfef 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -451,7 +451,7 @@ class MCPClient: async def receive_message( message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception, ) -> None: - if not isinstance(message, ValueError): + if not isinstance(message, (ValueError, httpx.RequestError, OSError)): return if not stream_error.done(): stream_error.set_result(message) @@ -472,7 +472,7 @@ class MCPClient: read_stream, write_stream, read_timeout_seconds=timedelta(seconds=self.timeout), - message_handler=receive_message if self.transport_type == MCPTransport.http else None, + message_handler=receive_message, **session_kwargs, ) session: Final = await session_ctx.__aenter__() @@ -525,8 +525,7 @@ class MCPClient: read_timeout: Final = _as_read_timeout(e) if read_timeout is not None: verbose_logger.warning( - "MCP client timed out after %ss waiting for %s to answer; the server accepted the " - "request and ended its response stream without a JSON-RPC reply", + "MCP client timed out after %ss waiting for a valid MCP response from %s", self.timeout, self.server_url or "stdio", ) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index c0ffeaeac62..63badc6f703 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -114,7 +114,8 @@ def _known_connection_error_message(exc: BaseException, url: str | None, timeout return str(exc.detail) if isinstance(exc, TimeoutError): return ( - f"Failed to connect to MCP server: no response from {_redact_mcp_resource_url(url) or 'the server'} " + "Failed to connect to MCP server: no valid MCP response received from " + f"{_redact_mcp_resource_url(url) or 'the server'} " f"within {timeout_seconds:.0f}s. Check that the LiteLLM proxy can reach this URL " "from its network (DNS, egress rules, firewalls) and that the server answers MCP requests." ) @@ -131,6 +132,11 @@ def _known_connection_error_message(exc: BaseException, url: str | None, timeout return "Failed to connect to MCP server: the connection timed out." if isinstance(exc, httpx.HTTPStatusError): return f"Failed to connect to MCP server: it returned HTTP {exc.response.status_code}." + if isinstance(exc, (httpx.NetworkError, httpx.RemoteProtocolError, ConnectionError)): + return ( + "Failed to connect to MCP server: the connection was interrupted. " + "Check the server and network connection, then retry." + ) if isinstance(exc, ValueError) and str(exc).startswith("Unexpected content type:"): return ( "Failed to connect to MCP server: the endpoint returned an unsupported content type. " @@ -142,6 +148,11 @@ def _known_connection_error_message(exc: BaseException, url: str | None, timeout "Check the MCP endpoint URL and the server's protocol implementation." ) if MCP_AVAILABLE and isinstance(exc, McpError): + if exc.error.code == -32000 and exc.error.message == "Connection closed": + return ( + "Failed to connect to MCP server: the connection was closed before the request completed. " + "Check that the server stays running and returns a complete MCP response, then retry." + ) if exc.error.code == 32600 and exc.error.message == "Session terminated": return ( "Failed to connect to MCP server: the MCP session was terminated. " @@ -159,7 +170,7 @@ if MCP_AVAILABLE: from mcp.shared.exceptions import McpError from mcp.types import Tool as MCPTool - from litellm.experimental_mcp_client.client import MCPClient + from litellm.experimental_mcp_client.client import MCPClient, _as_read_timeout from litellm.llms.litellm_proxy.skills.skill_search import ( DEFAULT_SKILL_SEARCH_TOP_K, ) @@ -1396,10 +1407,18 @@ if MCP_AVAILABLE: except (KeyboardInterrupt, SystemExit, asyncio.CancelledError): raise except BaseException as e: + effective_timeout: Final = ( + min(request.timeout or MCP_CLIENT_TIMEOUT, timeout_seconds) + if any( + isinstance(cause, McpError) and _as_read_timeout(cause) is not None + for cause in iter_exception_tree(e) + ) + else timeout_seconds + ) return { "status": "error", "error": True, - "message": _connection_error_message(e, request.url, timeout_seconds), + "message": _connection_error_message(e, request.url, effective_timeout), } async def _preview_openapi_tools(spec_path: str) -> dict: diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index 90292b9b162..a1e781d17b7 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -3,6 +3,7 @@ import base64 import json import os import sys +from collections.abc import AsyncIterator from importlib import metadata from pathlib import Path from typing import Final @@ -18,6 +19,7 @@ from pydantic import ValidationError from mcp.shared.message import SessionMessage from mcp.types import ( LATEST_PROTOCOL_VERSION, + CallToolResult, ErrorData, Implementation, InitializeResult, @@ -36,6 +38,7 @@ from litellm.experimental_mcp_client.client import ( MCPClient, _as_read_timeout, _first_non_cancelled_cause, + _TransportContext, missing_streamable_http_client_error, strip_auth_scheme, ) @@ -1296,6 +1299,7 @@ def test_a_differently_cased_injected_header_cannot_shadow_the_slot() -> None: [ ("text/html", b"secret-page", ValueError), ("application/json", b"secret-invalid-json", ValidationError), + ("application/json", b"", ValidationError), ("application/json", b'{"secret":"invalid-rpc"}', ValidationError), ("application/json", b'{"jsonrpc":"2.0","id":0,"result":{"secret":"invalid-schema"}}', ValidationError), ], @@ -1446,3 +1450,239 @@ async def test_invalid_tool_list_schema_is_identified_as_an_upstream_response() message: Final = _connection_error_message(caught.value, client.server_url, 30) assert "invalid MCP response" in message assert "secret" not in message + + +class _DiagnosticSSEStream(httpx.AsyncByteStream): + def __init__(self, messages: asyncio.Queue[bytes | Exception | None]) -> None: + self.messages = messages + + async def __aiter__(self) -> AsyncIterator[bytes]: + yield b"event: endpoint\ndata: /messages\n\n" + while True: + message: Final = await self.messages.get() + if message is None: + return + if isinstance(message, Exception): + raise message + yield b"event: message\ndata: " + message + b"\n\n" + + +_DIAGNOSTIC_STDIO_SERVER: Final = """ +import json, sys +mode, failure_method = sys.argv[1:] +for line in sys.stdin: + request = json.loads(line) + if "method" not in request or "id" not in request: + continue + if request["method"] == failure_method: + if mode == "bad-json": + print("secret-invalid-json", flush=True) + continue + if mode == "closed": + sys.exit(0) + if mode == "silent": + print(json.dumps({"jsonrpc": "2.0", "method": "notifications/message", "params": {"level": "info", "data": "Waiting"}}), flush=True) + continue + if request["method"] == "initialize": + result = {"protocolVersion": request["params"]["protocolVersion"], "capabilities": {"tools": {}, "logging": {}}, "serverInfo": {"name": "diagnostic", "version": "1"}} + elif request["method"] == "tools/list": + print(json.dumps({"jsonrpc": "2.0", "method": "notifications/message", "params": {"level": "info", "data": "Listing tools"}}), flush=True) + print(json.dumps({"jsonrpc": "2.0", "id": "unmatched", "result": {}}), flush=True) + print(json.dumps({"jsonrpc": "2.0", "id": "server-ping", "method": "ping"}), flush=True) + result = {"tools": [{"name": "ping", "inputSchema": {"type": "object"}}]} + else: + result = {"content": [{"type": "text", "text": "pong"}], "isError": False} + print(json.dumps({"jsonrpc": "2.0", "id": request["id"], "result": result}), flush=True) +""" + + +def _diagnostic_transport(transport: MCPTransport, mode: str, failure_method: str) -> _TransportContext: + from mcp import StdioServerParameters + from mcp.client.sse import sse_client + from mcp.client.stdio import stdio_client + + if transport == MCPTransport.stdio: + return stdio_client( + StdioServerParameters( + command=sys.executable, args=["-u", "-c", _DIAGNOSTIC_STDIO_SERVER, mode, failure_method] + ) + ) + messages: Final[asyncio.Queue[bytes | Exception | None]] = asyncio.Queue() + + async def respond(request: httpx.Request) -> httpx.Response: + if request.method == "GET": + return httpx.Response( + 200, headers={"Content-Type": "text/event-stream"}, stream=_DiagnosticSSEStream(messages) + ) + payload: Final = json.loads(request.content) + if "method" not in payload or "id" not in payload: + return httpx.Response(202) + if payload["method"] == failure_method and mode != "ok": + if mode == "bad-json": + await messages.put(b"secret-invalid-json") + elif mode == "io-error": + await messages.put(httpx.ReadError("secret-read-error")) + elif mode == "closed": + await messages.put(None) + elif mode == "silent": + await messages.put( + b'{"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","data":"Waiting"}}' + ) + return httpx.Response(202) + if payload["method"] == "tools/list": + for message in ( + { + "jsonrpc": "2.0", + "method": "notifications/message", + "params": {"level": "info", "data": "Listing tools"}, + }, + {"jsonrpc": "2.0", "id": "unmatched", "result": {}}, + {"jsonrpc": "2.0", "id": "server-ping", "method": "ping"}, + ): + await messages.put(json.dumps(message).encode()) + result: Final = ( + { + "protocolVersion": LATEST_PROTOCOL_VERSION, + "capabilities": {"tools": {}, "logging": {}}, + "serverInfo": {"name": "diagnostic", "version": "1"}, + } + if payload["method"] == "initialize" + else {"tools": [{"name": "ping", "inputSchema": {"type": "object"}}]} + if payload["method"] == "tools/list" + else {"content": [{"type": "text", "text": "pong"}], "isError": False} + ) + await messages.put(json.dumps({"jsonrpc": "2.0", "id": payload["id"], "result": result}).encode()) + return httpx.Response(202) + + def factory( + headers: dict[str, str] | None = None, + timeout: httpx.Timeout | None = None, + auth: httpx.Auth | None = None, + ) -> httpx.AsyncClient: + return httpx.AsyncClient(transport=httpx.MockTransport(respond), headers=headers, timeout=timeout, auth=auth) + + return sse_client("https://example.com/sse", httpx_client_factory=factory) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("transport", [MCPTransport.sse, MCPTransport.stdio]) +@pytest.mark.parametrize("failure_method", ["initialize", "tools/list"]) +async def test_transport_parsing_failure_is_preserved(transport: MCPTransport, failure_method: str) -> None: + client: Final = MCPClient(server_url="https://example.com/sse", transport_type=transport, timeout=0.2) + with pytest.raises(ValidationError): + await asyncio.wait_for( + client._execute_session_operation( + _diagnostic_transport(transport, "bad-json", failure_method), lambda session: session.list_tools() + ), + timeout=3, + ) + + +@pytest.mark.asyncio +async def test_sse_read_failure_is_preserved() -> None: + client: Final = MCPClient(server_url="https://example.com/sse", transport_type=MCPTransport.sse, timeout=0.2) + with pytest.raises(httpx.ReadError, match="secret-read-error"): + await asyncio.wait_for( + client._execute_session_operation( + _diagnostic_transport(MCPTransport.sse, "io-error", "tools/list"), lambda session: session.list_tools() + ), + timeout=3, + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("transport", [MCPTransport.sse, MCPTransport.stdio]) +@pytest.mark.parametrize("mode", ["ok", "closed", "silent"]) +async def test_transport_completion_and_normal_messages(transport: MCPTransport, mode: str) -> None: + from mcp import ClientSession + from litellm.proxy._experimental.mcp_server.rest_endpoints import _connection_error_message + + logging_callback: Final = AsyncMock() + client: Final = MCPClient( + server_url="https://example.com/sse", transport_type=transport, timeout=0.2, logging_callback=logging_callback + ) + + async def operation(session: ClientSession) -> CallToolResult: + tools: Final = await session.list_tools() + assert [tool.name for tool in tools.tools] == ["ping"] + return await session.call_tool("ping", {}) + + pending: Final = client._execute_session_operation(_diagnostic_transport(transport, mode, "tools/list"), operation) + if mode == "ok": + result: Final = await asyncio.wait_for(pending, timeout=3) + assert result.isError is False + assert result.content[0].text == "pong" + logging_callback.assert_awaited_once_with(LoggingMessageNotificationParams(level="info", data="Listing tools")) + else: + with pytest.raises(McpError) as caught: + await asyncio.wait_for(pending, timeout=3) + if mode == "closed": + assert "connection was closed" in _connection_error_message(caught.value, client.server_url, 0.2) + else: + assert isinstance(_as_read_timeout(caught.value), TimeoutError) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("transport", [MCPTransport.sse, MCPTransport.stdio]) +async def test_transport_cancellation_cleans_up_a_pending_request(transport: MCPTransport) -> None: + ready: Final = asyncio.Event() + + async def on_log(message: LoggingMessageNotificationParams) -> None: + if message.data == "Waiting": + ready.set() + + client: Final = MCPClient( + server_url="https://example.com/sse", transport_type=transport, timeout=30, logging_callback=on_log + ) + task: Final = asyncio.create_task( + client._execute_session_operation( + _diagnostic_transport(transport, "silent", "tools/list"), lambda session: session.list_tools() + ) + ) + try: + await asyncio.wait_for(ready.wait(), timeout=3) + finally: + task.cancel() + with pytest.raises(asyncio.CancelledError): + await asyncio.wait_for(task, timeout=3) + + +class _InterruptedHTTPBody(httpx.AsyncByteStream): + async def __aiter__(self) -> AsyncIterator[bytes]: + yield b'{"jsonrpc":' + raise httpx.RemoteProtocolError("secret-incomplete-response") + + +@pytest.mark.asyncio +async def test_interrupted_http_response_preserves_the_transport_failure() -> None: + def respond(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, headers={"Content-Type": "application/json"}, stream=_InterruptedHTTPBody()) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=30) + with pytest.raises(httpx.RemoteProtocolError, match="secret-incomplete-response"): + await asyncio.wait_for( + client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), + lambda session: session.list_tools(), + ), + timeout=3, + ) + + +@pytest.mark.asyncio +async def test_empty_http_event_stream_uses_the_existing_request_deadline() -> None: + def respond(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, headers={"Content-Type": "text/event-stream"}, content=b"") + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client: Final = MCPClient(server_url="https://example.com/mcp", timeout=0.2) + with pytest.raises(McpError) as caught: + await asyncio.wait_for( + client._execute_session_operation( + streamable_http_client(client.server_url, http_client=http_client), + lambda session: session.list_tools(), + ), + timeout=3, + ) + assert isinstance(_as_read_timeout(caught.value), TimeoutError) 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 73734131130..101b02e6ffd 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 @@ -3427,6 +3427,54 @@ class TestConnectionErrorMessage: message = rest_endpoints._connection_error_message(exc, "https://example.com", 30.0) assert "503" in message + @pytest.mark.parametrize( + "error_type", [httpx.ReadError, httpx.WriteError, httpx.RemoteProtocolError, ConnectionResetError] + ) + def test_interrupted_connection_message_is_safe(self, error_type: type[Exception]) -> None: + message: Final = rest_endpoints._connection_error_message( + error_type("secret-transport-detail"), "https://example.com/?token=secret-query", 30 + ) + assert "connection was interrupted" in message + assert "secret" not in message + + def test_closed_connection_explains_incomplete_request(self) -> None: + from mcp import McpError + from mcp.types import ErrorData + + message: Final = rest_endpoints._connection_error_message( + McpError(ErrorData(code=-32000, message="Connection closed", data="secret-data")), None, 30 + ) + assert "connection was closed before the request completed" in message + assert "secret" not in message + + def test_timeout_does_not_claim_the_server_sent_nothing(self) -> None: + message: Final = rest_endpoints._connection_error_message(TimeoutError(), None, 30) + assert "no valid MCP response received" in message + + @pytest.mark.asyncio + @pytest.mark.parametrize("sdk_timeout", [True, False]) + async def test_timeout_message_uses_the_deadline_that_expired(self, sdk_timeout: bool) -> None: + from mcp import McpError + from mcp.types import ErrorData + + async def operation(client: rest_endpoints.MCPClient) -> dict[str, object]: + try: + raise TimeoutError("secret-timeout") + except TimeoutError as elapsed: + if not sdk_timeout: + raise + try: + raise McpError(ErrorData(code=408, message="secret-sdk-timeout")) from elapsed + except McpError as sdk_error: + raise TimeoutError() from sdk_error + + payload: Final = NewMCPServerRequest( + server_name="timeout", url="https://example.com", auth_type=MCPAuth.none, timeout=1 + ) + result: Final = await rest_endpoints._execute_with_mcp_client(payload, operation, timeout_seconds=30) + assert ("within 1s" if sdk_timeout else "within 30s") in result["message"] + assert "secret" not in result["message"] + def test_unknown_error_falls_back_to_generic(self): message = rest_endpoints._connection_error_message(RuntimeError("weird"), "https://example.com", 30.0) assert "weird" not in message From 1fc1aaabdaa3542cd2d410103ee01ebc94025961 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 16:00:35 +0000 Subject: [PATCH 090/105] test(cli): drop lite --version subprocess regression Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/client/cli/test_global_options.py | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/tests/test_litellm/proxy/client/cli/test_global_options.py b/tests/test_litellm/proxy/client/cli/test_global_options.py index 9ed9f91ab9f..b73d1acc6e3 100644 --- a/tests/test_litellm/proxy/client/cli/test_global_options.py +++ b/tests/test_litellm/proxy/client/cli/test_global_options.py @@ -1,10 +1,6 @@ # stdlib imports import json import os -import shutil -import subprocess -import threading -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from unittest.mock import Mock, patch @@ -37,58 +33,6 @@ def test_cli_version_flag(cli_runner): assert "LiteLLM Proxy Server Version: 1.2.3" in result.output -def test_lite_version_does_not_fetch_model_cost_map(tmp_path: Path) -> None: - lite_path = shutil.which("lite") - if lite_path is None: - pytest.skip("lite executable is unavailable") - - request_log: Path = tmp_path / "requests.log" - - class _CostMapHandler(BaseHTTPRequestHandler): - def do_GET(self) -> None: - with request_log.open("a", encoding="utf-8") as log_file: - log_file.write(f"{self.path}\n") - body = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(body))) - self.end_headers() - self.wfile.write(body) - - def log_message(self, format: str, *args: object) -> None: - return - - server = ThreadingHTTPServer(("127.0.0.1", 0), _CostMapHandler) - thread = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() - try: - env = {key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"} - source_root = str(Path(__file__).resolve().parents[5]) - env["PYTHONPATH"] = os.pathsep.join(filter(None, (source_root, env.get("PYTHONPATH")))) - env.update( - { - "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", - "LITELLM_PROXY_URL": "http://127.0.0.1:9", - } - ) - result = subprocess.run( - [lite_path, "--version"], - capture_output=True, - text=True, - timeout=120, - env=env, - ) - finally: - server.shutdown() - server.server_close() - thread.join(timeout=10) - - assert result.returncode == 0 - assert "LiteLLM Proxy CLI Version" in result.stdout - request_count: int = request_log.read_text(encoding="utf-8").count("\n") if request_log.exists() else 0 - assert request_count == 0 - - def test_cli_source_is_ascii_only(): """Non-ASCII output (emoji, box-drawing chars) raises UnicodeEncodeError on legacy Windows consoles (cp1252), so the whole CLI package must stay ASCII-only.""" From 9b6c7c8bf0e2e0e3218caa8a18b29b17bb8bf566 Mon Sep 17 00:00:00 2001 From: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:17:14 -0700 Subject: [PATCH 091/105] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/proxy/_experimental/mcp_server/rest_endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 63badc6f703..31252479445 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -1408,7 +1408,7 @@ if MCP_AVAILABLE: raise except BaseException as e: effective_timeout: Final = ( - min(request.timeout or MCP_CLIENT_TIMEOUT, timeout_seconds) + min(request.timeout if request.timeout is not None else MCP_CLIENT_TIMEOUT, timeout_seconds) if any( isinstance(cause, McpError) and _as_read_timeout(cause) is not None for cause in iter_exception_tree(e) From dbf94902294cf5fa4fe5af6681fcd83ffd743231 Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:44:14 -0700 Subject: [PATCH 092/105] fix(mcp): expose shared SDK timeout normalization --- litellm/experimental_mcp_client/client.py | 6 +++--- .../_experimental/mcp_server/rest_endpoints.py | 4 ++-- .../experimental_mcp_client/test_mcp_client.py | 18 +++++++++--------- .../mcp_server/test_rest_endpoints.py | 7 ++++--- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index 5cdc4efdfef..8c7f4557992 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -150,8 +150,8 @@ _SDK_READ_TIMEOUT_CODE: Final = int(httpx.codes.REQUEST_TIMEOUT) otherwise carries JSON-RPC error codes.""" -def _as_read_timeout(exc: BaseException) -> TimeoutError | None: - """The session read timeout elapsing, re-expressed as a ``TimeoutError``, or ``None``. +def as_mcp_read_timeout(exc: BaseException) -> TimeoutError | None: + """Normalize an MCP SDK read timeout for client and gateway diagnostics, or return ``None``. The SDK reports its own elapsed read timeout as ``McpError`` carrying an HTTP status code in a field that otherwise holds JSON-RPC error codes, and it relays an upstream's JSON-RPC error @@ -522,7 +522,7 @@ class MCPClient: transport_ctx, http_client = self._create_transport_context() return await self._execute_session_operation(transport_ctx, operation) except Exception as e: - read_timeout: Final = _as_read_timeout(e) + read_timeout: Final = as_mcp_read_timeout(e) if read_timeout is not None: verbose_logger.warning( "MCP client timed out after %ss waiting for a valid MCP response from %s", diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index 31252479445..5b74caee3a2 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -170,7 +170,7 @@ if MCP_AVAILABLE: from mcp.shared.exceptions import McpError from mcp.types import Tool as MCPTool - from litellm.experimental_mcp_client.client import MCPClient, _as_read_timeout + from litellm.experimental_mcp_client.client import MCPClient, as_mcp_read_timeout from litellm.llms.litellm_proxy.skills.skill_search import ( DEFAULT_SKILL_SEARCH_TOP_K, ) @@ -1410,7 +1410,7 @@ if MCP_AVAILABLE: effective_timeout: Final = ( min(request.timeout if request.timeout is not None else MCP_CLIENT_TIMEOUT, timeout_seconds) if any( - isinstance(cause, McpError) and _as_read_timeout(cause) is not None + isinstance(cause, McpError) and as_mcp_read_timeout(cause) is not None for cause in iter_exception_tree(e) ) else timeout_seconds diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index a1e781d17b7..b07e5876e8b 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -36,9 +36,9 @@ import litellm.experimental_mcp_client.client as mcp_client_module from litellm.experimental_mcp_client.client import ( MCP_STREAMABLE_HTTP_REQUIREMENT, MCPClient, - _as_read_timeout, _first_non_cancelled_cause, _TransportContext, + as_mcp_read_timeout, missing_streamable_http_client_error, strip_auth_scheme, ) @@ -867,25 +867,25 @@ def _raise_mcp_error_while_handling_a_timeout(code: int, message: str) -> McpErr return raised -def test_as_read_timeout_separates_the_sdk_timeout_from_a_relayed_upstream_error(): +def test_as_mcp_read_timeout_separates_the_sdk_timeout_from_a_relayed_upstream_error(): """Neither signal alone is enough. The code alone cannot separate the SDK's own timeout from an upstream JSON-RPC error that happens to use 408, and the context chain alone cannot separate it from any other relayed error that surfaces while a timeout is being handled, so both must hold. """ timeout_code = int(httpx.codes.REQUEST_TIMEOUT) - translated = _as_read_timeout(_raise_mcp_error_while_handling_a_timeout(timeout_code, "Timed out while waiting")) + translated = as_mcp_read_timeout(_raise_mcp_error_while_handling_a_timeout(timeout_code, "Timed out while waiting")) assert isinstance(translated, TimeoutError) assert str(translated) == "Timed out while waiting" relayed_408 = McpError(ErrorData(code=timeout_code, message="upstream said 408")) - assert _as_read_timeout(relayed_408) is None, "an upstream 408 with no elapsed timeout is not our timeout" + assert as_mcp_read_timeout(relayed_408) is None, "an upstream 408 with no elapsed timeout is not our timeout" relayed_other = _raise_mcp_error_while_handling_a_timeout(-32603, "upstream internal error") - assert _as_read_timeout(relayed_other) is None, "a non-timeout code is not our timeout, whatever the chain" + assert as_mcp_read_timeout(relayed_other) is None, "a non-timeout code is not our timeout, whatever the chain" - assert _as_read_timeout(McpError(ErrorData(code=-32603, message="boom"))) is None - assert _as_read_timeout(RuntimeError("not an McpError")) is None + assert as_mcp_read_timeout(McpError(ErrorData(code=-32603, message="boom"))) is None + assert as_mcp_read_timeout(RuntimeError("not an McpError")) is None @pytest.mark.asyncio @@ -1619,7 +1619,7 @@ async def test_transport_completion_and_normal_messages(transport: MCPTransport, if mode == "closed": assert "connection was closed" in _connection_error_message(caught.value, client.server_url, 0.2) else: - assert isinstance(_as_read_timeout(caught.value), TimeoutError) + assert isinstance(as_mcp_read_timeout(caught.value), TimeoutError) @pytest.mark.asyncio @@ -1685,4 +1685,4 @@ async def test_empty_http_event_stream_uses_the_existing_request_deadline() -> N ), timeout=3, ) - assert isinstance(_as_read_timeout(caught.value), TimeoutError) + assert isinstance(as_mcp_read_timeout(caught.value), TimeoutError) 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 101b02e6ffd..3487f634251 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 @@ -3453,7 +3453,8 @@ class TestConnectionErrorMessage: @pytest.mark.asyncio @pytest.mark.parametrize("sdk_timeout", [True, False]) - async def test_timeout_message_uses_the_deadline_that_expired(self, sdk_timeout: bool) -> None: + @pytest.mark.parametrize("read_timeout", [0, 1]) + async def test_timeout_message_uses_the_deadline_that_expired(self, sdk_timeout: bool, read_timeout: int) -> None: from mcp import McpError from mcp.types import ErrorData @@ -3469,10 +3470,10 @@ class TestConnectionErrorMessage: raise TimeoutError() from sdk_error payload: Final = NewMCPServerRequest( - server_name="timeout", url="https://example.com", auth_type=MCPAuth.none, timeout=1 + server_name="timeout", url="https://example.com", auth_type=MCPAuth.none, timeout=read_timeout ) result: Final = await rest_endpoints._execute_with_mcp_client(payload, operation, timeout_seconds=30) - assert ("within 1s" if sdk_timeout else "within 30s") in result["message"] + assert (f"within {read_timeout}s" if sdk_timeout else "within 30s") in result["message"] assert "secret" not in result["message"] def test_unknown_error_falls_back_to_generic(self): From 1183b2abc6645a92f9a852daa7843662b6fa6f20 Mon Sep 17 00:00:00 2001 From: yujonglee Date: Wed, 9 Sep 2026 09:46:37 -0700 Subject: [PATCH 093/105] fix(integrations): pass original request object to post-call guardrail hooks (#40414) --- litellm/integrations/custom_guardrail.py | 30 ++++--- .../integrations/test_custom_guardrail.py | 90 ++++++++++++++++++- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 37d6a7e793d..77bf4820a1a 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -850,20 +850,24 @@ class CustomGuardrail(CustomLogger): if self.should_run_guardrail(data=request_data, event_type=GuardrailEventHooks.post_call) is not True: return None - # CHECK IF GUARDRAIL REJECTS THE REQUEST target: Final = self._deployment_hook_target() - hook_request_data: Final = {**request_data, "guardrail_to_apply": self} if target is not self else request_data - result: Final = await target.async_post_call_success_hook( - user_api_key_dict=UserAPIKeyAuth( - user_id=request_data.get("user_api_key_user_id"), - team_id=request_data.get("user_api_key_team_id"), - end_user_id=request_data.get("user_api_key_end_user_id"), - api_key=request_data.get("user_api_key_hash"), - request_route=request_data.get("user_api_key_request_route"), - ), - data=hook_request_data, - response=response, - ) + try: + if target is not self: + request_data["guardrail_to_apply"] = self # rebind-ok: dispatch consumes this key + result: Final = await target.async_post_call_success_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id=request_data.get("user_api_key_user_id"), + team_id=request_data.get("user_api_key_team_id"), + end_user_id=request_data.get("user_api_key_end_user_id"), + api_key=request_data.get("user_api_key_hash"), + request_route=request_data.get("user_api_key_request_route"), + ), + data=request_data, + response=response, + ) + finally: + if target is not self: + request_data.pop("guardrail_to_apply", None) if not self._is_valid_response_type(result): return None diff --git a/tests/test_litellm/integrations/test_custom_guardrail.py b/tests/test_litellm/integrations/test_custom_guardrail.py index cd8d609cf71..ddc8439a83a 100644 --- a/tests/test_litellm/integrations/test_custom_guardrail.py +++ b/tests/test_litellm/integrations/test_custom_guardrail.py @@ -1842,12 +1842,14 @@ class _ApplyStyleGuardrail(CustomGuardrail): self.block = block self.apply_called = False self.seen_texts = None + self.seen_request_data = None async def apply_guardrail(self, inputs, request_data, input_type, logging_obj=None): from fastapi import HTTPException self.apply_called = True self.seen_texts = inputs.get("texts") + self.seen_request_data = request_data if self.block: raise HTTPException(status_code=400, detail={"error": "Violated moderation policy"}) return inputs @@ -2646,6 +2648,91 @@ class TestCustomGuardrailPostCallSuccessDeploymentHook: which starved every later callback in litellm.callbacks (notably the lazily-appended VectorStorePreCallHook that attaches provider_specific_fields["search_results"]).""" + @pytest.mark.asyncio + async def test_apply_guardrail_retains_request_identity(self) -> None: + from litellm.types.guardrails import GuardrailEventHooks + from litellm.types.utils import Choices, Message, ModelResponse + + guardrail: Final = _ApplyStyleGuardrail(block=False) + guardrail.event_hook = GuardrailEventHooks.post_call + request_data: Final = {"guardrails": ["apply-style-guardrail"]} + response: Final = ModelResponse(choices=[Choices(message=Message(content="review me"))]) + + await guardrail.async_post_call_success_deployment_hook( + request_data=request_data, response=response, call_type=CallTypes.acompletion + ) + + assert guardrail.seen_request_data is request_data + assert guardrail.seen_texts == ["review me"] + assert "guardrail_to_apply" not in request_data + + @pytest.mark.asyncio + @pytest.mark.parametrize("call_type", (None, CallTypes.acompletion)) + async def test_apply_guardrail_masks_response_and_records_metadata(self, call_type: CallTypes | None) -> None: + from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import ( + ContentFilterGuardrail, + ) + from litellm.types.guardrails import BlockedWord, ContentFilterAction, GuardrailEventHooks + from litellm.types.utils import Choices, Message, ModelResponse + + guardrail: Final = ContentFilterGuardrail( + guardrail_name="response-filter", + event_hook=GuardrailEventHooks.post_call, + blocked_words=[BlockedWord(keyword="secret", action=ContentFilterAction.MASK)], + ) + request_data: Final = {"guardrails": ["response-filter"]} + response: Final = ModelResponse(choices=[Choices(message=Message(content="a secret"))]) + + result: Final = await guardrail.async_post_call_success_deployment_hook( + request_data=request_data, response=response, call_type=call_type + ) + + assert isinstance(result, ModelResponse) + assert result.choices[0].message.content == f"a {guardrail.keyword_redaction_tag}" + entries: Final = _guardrail_entries(request_data) + assert len(entries) == 1 + assert entries[0]["guardrail_name"] == "response-filter" + assert entries[0]["guardrail_mode"] == "post_call" + assert "guardrail_to_apply" not in request_data + + @pytest.mark.asyncio + @pytest.mark.parametrize("error_type", (None, RuntimeError, asyncio.CancelledError)) + async def test_dispatch_cleans_up_request_on_every_exit(self, error_type: type[BaseException] | None) -> None: + from contextlib import nullcontext + + from litellm.integrations.custom_logger import CustomLogger + from litellm.types.utils import LLMResponseTypes, ModelResponse + + error: Final = error_type("dispatch interrupted") if error_type is not None else None + + class Dispatch(CustomLogger): + request_data: dict[str, object] | None = None + + async def async_post_call_success_hook( + self, data: dict[str, object], user_api_key_dict: UserAPIKeyAuth, response: LLMResponseTypes + ) -> LLMResponseTypes: + self.request_data = data + if error is not None: + raise error + return response + + dispatch: Final = Dispatch() + + class Guardrail(_ApplyStyleGuardrail): + def _deployment_hook_target(self) -> CustomLogger: + return dispatch + + guardrail: Final = Guardrail(block=False) + guardrail.event_hook = GuardrailEventHooks.post_call + request_data: Final = {"guardrails": ["apply-style-guardrail"]} + with pytest.raises(error_type) if error_type is not None else nullcontext(): + await guardrail.async_post_call_success_deployment_hook( + request_data=request_data, response=ModelResponse(), call_type=CallTypes.acompletion + ) + + assert dispatch.request_data is request_data + assert "guardrail_to_apply" not in request_data + @pytest.mark.asyncio async def test_returns_none_when_request_has_no_guardrails(self): from litellm.types.utils import ModelResponse @@ -2740,4 +2827,5 @@ class TestCustomGuardrailPostCallSuccessDeploymentHook: assert result is response assert response.choices[0].message.content == "filtered response" - assert request_data == {"guardrails": ["test-guardrail"]} + assert "guardrail_to_apply" not in request_data + assert len(_guardrail_entries(request_data)) == 1 From 36f3ca95d8bdb6b03ab4632982cdc7c0cef99c6f Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Wed, 9 Sep 2026 09:57:38 -0700 Subject: [PATCH 094/105] fix(proxy): report /cost/estimate rates from the call that billed them The estimate looked the reported per-token rates up a second time, with the provider this endpoint resolved rather than the one completion_cost infers. The provider decides whether a token tier threshold is inclusive, so an unrouted xai model sitting exactly on 200k billed at the tier rate and reported the base rate, half of it. completion_cost now hands back the rates its own lines were billed at, and the endpoint reports those. Claude-Session: https://claude.ai/code/session_01RLKy5DMi3XCBUJ37WzfNi1 --- litellm/cost_calculator.py | 6 ++ litellm/litellm_core_utils/litellm_logging.py | 5 ++ .../litellm_core_utils/llm_cost_calc/utils.py | 60 ++++++++++--------- .../cost_tracking_settings.py | 29 ++++----- .../llm_cost_calc/test_llm_cost_calc_utils.py | 52 +++++++++++++--- .../test_cost_tracking_settings.py | 39 ++++++++++++ tests/test_litellm/test_cost_calculator.py | 56 +++++++++++++++++ 7 files changed, 195 insertions(+), 52 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index a1181ee4124..b4be023540f 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -25,6 +25,7 @@ from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import TranscriptionUsageObjectTransformation, ) from litellm.litellm_core_utils.llm_cost_calc.utils import ( + BilledTokenRates, CostCalculatorUtils, _generic_cost_per_character, _get_regional_uplift_multiplier, @@ -1122,6 +1123,7 @@ def _store_cost_breakdown_in_logging_obj( service_tier: str | None = None, data_residency: str | None = None, vertex_location: str | None = None, + billed_token_rates: BilledTokenRates | None = None, ) -> None: """ Helper function to store cost breakdown in the logging object. @@ -1166,6 +1168,7 @@ def _store_cost_breakdown_in_logging_obj( service_tier=service_tier, data_residency=data_residency, vertex_location=vertex_location, + billed_token_rates=billed_token_rates, ) except Exception as breakdown_error: @@ -1735,6 +1738,7 @@ def completion_cost( _reasoning_cost: float | None = None _cache_read_cost: float | None = None _cache_creation_cost: float | None = None + _billed_token_rates: BilledTokenRates | None = None if cost_per_token_usage_object is not None and model: _breakdown_provider: str | None = ( custom_llm_provider if isinstance(custom_llm_provider, str) else None @@ -1751,6 +1755,7 @@ def completion_cost( _reasoning_cost = _token_type_breakdown.reasoning_cost _cache_read_cost = _token_type_breakdown.cache_read_cost _cache_creation_cost = _token_type_breakdown.cache_creation_cost + _billed_token_rates = _token_type_breakdown.rates _store_cost_breakdown_in_logging_obj( litellm_logging_obj=litellm_logging_obj, prompt_tokens_cost_usd_dollar=prompt_tokens_cost_usd_dollar, @@ -1770,6 +1775,7 @@ def completion_cost( service_tier=service_tier, data_residency=data_residency, vertex_location=vertex_location, + billed_token_rates=_billed_token_rates, ) return _final_cost diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index ca2cca5360f..a9f3c0e9325 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -201,6 +201,7 @@ if TYPE_CHECKING: from mcp.types import EmbeddedResource, ImageContent, TextContent from litellm.integrations.otel.logger import OpenTelemetryV2 + from litellm.litellm_core_utils.llm_cost_calc.utils import BilledTokenRates from litellm.llms.base_llm.passthrough.transformation import BasePassthroughConfig try: from litellm_enterprise.enterprise_callbacks.callback_controls import ( @@ -581,6 +582,7 @@ class Logging(LiteLLMLoggingBaseClass): # Initialize cost breakdown field self.cost_breakdown: CostBreakdown | None = None + self.billed_token_rates: BilledTokenRates | None = None # Init Caching related details self.caching_details: CachingDetails | None = None @@ -1585,6 +1587,7 @@ class Logging(LiteLLMLoggingBaseClass): service_tier: str | None = None, data_residency: str | None = None, vertex_location: str | None = None, + billed_token_rates: "BilledTokenRates | None" = None, ) -> None: """ Helper method to store cost breakdown in the logging object. @@ -1604,8 +1607,10 @@ class Logging(LiteLLMLoggingBaseClass): service_tier: Tier the costs above were priced on, already resolved data_residency: Region uplift the costs above were priced on, already resolved vertex_location: Vertex AI location the costs above were priced on, already resolved + billed_token_rates: Per-token rates the costs above were billed at, already resolved """ + self.billed_token_rates = billed_token_rates self.cost_breakdown = CostBreakdown( input_cost=input_cost, output_cost=output_cost, diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index e2168528e6b..42f88bbb425 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -1302,34 +1302,6 @@ def _coerce_token_count(value: object) -> int: return value if isinstance(value, int) and value > 0 else 0 -@dataclass(frozen=True, slots=True) -class TokenTypeCostBreakdown: - reasoning_cost: float - cache_read_cost: float - cache_creation_cost: float - - -def _reasoning_token_count(usage: Usage) -> int: - parsed: Final = ( - parse_completion_tokens_details(usage)["reasoning_tokens"] if usage.completion_tokens_details is not None else 0 - ) - return parsed or _coerce_token_count(getattr(usage, "reasoning_tokens", 0)) - - -def _cache_token_counts(usage: Usage) -> tuple[int, int, CacheCreationTokenDetails | None]: - """(cache read tokens, cache creation tokens, cache creation details): read from prompt_tokens_details - first, then the private top-level counters the Usage constructor mirrors cache tokens onto for - providers/callers that bypass the details.""" - parsed: Final = parse_prompt_tokens_details(usage) if usage.prompt_tokens_details is not None else None - parsed_read: Final = parsed["cache_hit_tokens"] if parsed is not None else 0 - parsed_creation: Final = parsed["cache_creation_tokens"] if parsed is not None else 0 - return ( - parsed_read or _coerce_token_count(getattr(usage, "_cache_read_input_tokens", 0)), - parsed_creation or _coerce_token_count(getattr(usage, "_cache_creation_input_tokens", 0)), - parsed["cache_creation_token_details"] if parsed is not None else None, - ) - - @dataclass(frozen=True, slots=True) class BilledTokenRates: """Per-token rates one request's usage bills at, after token tiers, off-peak windows and the @@ -1355,6 +1327,37 @@ class BilledTokenRates: ) +@dataclass(frozen=True, slots=True) +class TokenTypeCostBreakdown: + reasoning_cost: float + cache_read_cost: float + cache_creation_cost: float + rates: BilledTokenRates | None = None + """Rates these lines were billed at, so a caller reporting both cannot resolve them a second, + differently-argued way. None when the model's pricing could not be resolved.""" + + +def _reasoning_token_count(usage: Usage) -> int: + parsed: Final = ( + parse_completion_tokens_details(usage)["reasoning_tokens"] if usage.completion_tokens_details is not None else 0 + ) + return parsed or _coerce_token_count(getattr(usage, "reasoning_tokens", 0)) + + +def _cache_token_counts(usage: Usage) -> tuple[int, int, CacheCreationTokenDetails | None]: + """(cache read tokens, cache creation tokens, cache creation details): read from prompt_tokens_details + first, then the private top-level counters the Usage constructor mirrors cache tokens onto for + providers/callers that bypass the details.""" + parsed: Final = parse_prompt_tokens_details(usage) if usage.prompt_tokens_details is not None else None + parsed_read: Final = parsed["cache_hit_tokens"] if parsed is not None else 0 + parsed_creation: Final = parsed["cache_creation_tokens"] if parsed is not None else 0 + return ( + parsed_read or _coerce_token_count(getattr(usage, "_cache_read_input_tokens", 0)), + parsed_creation or _coerce_token_count(getattr(usage, "_cache_creation_input_tokens", 0)), + parsed["cache_creation_token_details"] if parsed is not None else None, + ) + + def _custom_pricing_rates(custom_cost_per_token: CostPerToken) -> BilledTokenRates: """Flat custom pricing has no tiers, uplifts or reasoning rate: cache tokens bill at the configured cache rates (else the input rate) and reasoning at the output rate, as _cost_per_token_custom_pricing_helper does.""" @@ -1497,6 +1500,7 @@ def get_token_type_cost_breakdown( reasoning_cost=float(_reasoning_token_count(usage)) * rates.output_cost_per_reasoning_token, cache_read_cost=float(cache_read_tokens) * rates.cache_read_input_token_cost, cache_creation_cost=cache_creation_cost, + rates=rates, ) diff --git a/litellm/proxy/management_endpoints/cost_tracking_settings.py b/litellm/proxy/management_endpoints/cost_tracking_settings.py index 1faa66584d5..493f75008c3 100644 --- a/litellm/proxy/management_endpoints/cost_tracking_settings.py +++ b/litellm/proxy/management_endpoints/cost_tracking_settings.py @@ -21,7 +21,6 @@ import litellm from litellm._internal_context import current_billing_time, pinned_billing_time from litellm._logging import verbose_proxy_logger from litellm.cost_calculator import completion_cost -from litellm.litellm_core_utils.llm_cost_calc.utils import get_billed_token_rates from litellm.proxy._types import ( CommonProxyErrors, CostEstimateRequest, @@ -85,9 +84,9 @@ def _extract_custom_pricing( ) -def _lookup_model_info(model: str) -> ModelInfo | None: +def _lookup_model_info(model: str, custom_llm_provider: str | None = None) -> ModelInfo | None: try: - return litellm.get_model_info(model=model) + return litellm.get_model_info(model=model, custom_llm_provider=custom_llm_provider) except Exception: return None @@ -122,7 +121,7 @@ def _resolve_model_for_cost_lookup(model: str) -> ResolvedCostModel: if resolved_model: verbose_proxy_logger.debug("Resolved model '%s' to '%s' from router", model, resolved_model) custom_cost_per_token: Final = _extract_custom_pricing( - litellm_params, model_info, _lookup_model_info(str(resolved_model)) + litellm_params, model_info, _lookup_model_info(str(resolved_model), provider) ) return ResolvedCostModel(str(resolved_model), provider, custom_cost_per_token) except Exception as e: @@ -632,10 +631,9 @@ async def estimate_cost( function_id="cost-estimate", ) - # The totals, the per-token-type lines and the reported rates each resolve pricing on their - # own path. Pinning one moment keeps an off-peak window that opens mid-quote from splitting them. - billed_at: Final = current_billing_time() - with pinned_billing_time(billed_at): + # Pinning one moment keeps an off-peak window that opens mid-quote from pricing the totals on + # one side of it and the reported rates on the other. + with pinned_billing_time(current_billing_time()): # Use completion_cost which handles all the logic including margins/discounts try: cost_per_request: Final = completion_cost( @@ -653,19 +651,16 @@ async def estimate_cost( }, ) - rates: Final = get_billed_token_rates( - model=resolved_model, - custom_llm_provider=resolved_provider, - usage=usage, - custom_cost_per_token=resolved.custom_cost_per_token, - current_time=billed_at, - ) - + # The rates come back from the pricing call itself rather than a second lookup, so they are the + # ones the cost lines above billed at even when completion_cost infers a provider this endpoint + # never resolved (an unrouted "xai/grok-4" prices on xai's inclusive tier thresholds; a lookup + # here without that provider would report the sub-200k rate for a line billed above it). + rates: Final = litellm_logging_obj.billed_token_rates per_request: Final = _cost_lines(cost_per_request, litellm_logging_obj.cost_breakdown) daily: Final = per_request.times(request.num_requests_per_day) monthly: Final = per_request.times(request.num_requests_per_month) - model_info: Final = _lookup_model_info(resolved_model) + model_info: Final = _lookup_model_info(resolved_model, resolved_provider) mapped_provider: Final = model_info.get("litellm_provider") if model_info is not None else None custom_llm_provider: Final = mapped_provider if mapped_provider is not None else resolved_provider diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 90178428018..5a19ed0277a 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -33,7 +33,6 @@ from litellm.litellm_core_utils.llm_cost_calc.utils import ( CostCalculatorUtils, PromptTokensDetailsResult, TokenRates, - TokenTypeCostBreakdown, _calculate_input_cost, _get_token_base_cost, _is_off_peak, @@ -4023,6 +4022,49 @@ def test_a_pinned_billing_time_prices_the_totals_and_the_reported_rates_at_one_m assert peak_completion_cost == pytest.approx(500 * peak_rates.output_cost_per_token) +def test_the_token_type_breakdown_carries_the_rates_it_billed_at(monkeypatch): + """Callers that report both the lines and the rates read the rates off the breakdown rather than + resolving them a second time, so the breakdown has to hand back exactly what it billed at.""" + monkeypatch.setitem( + litellm.model_cost, + "xai/tiered-model", + { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + "input_cost_per_token_above_200k_tokens": 6e-6, + "output_cost_per_token_above_200k_tokens": 3e-5, + "cache_read_input_token_cost_above_200k_tokens": 6e-7, + "litellm_provider": "xai", + "mode": "chat", + }, + ) + usage = Usage( + prompt_tokens=200_000, + completion_tokens=1_000, + total_tokens=201_000, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=100_000), + ) + + breakdown = get_token_type_cost_breakdown(model="xai/tiered-model", custom_llm_provider="xai", usage=usage) + + assert breakdown.rates == get_billed_token_rates( + model="xai/tiered-model", custom_llm_provider="xai", usage=usage + ) + assert breakdown.rates.cache_read_input_token_cost == pytest.approx(6e-7) + assert breakdown.cache_read_cost == pytest.approx(100_000 * breakdown.rates.cache_read_input_token_cost) + + +def test_the_token_type_breakdown_reports_no_rates_for_an_unpriced_model(): + usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15) + + breakdown = get_token_type_cost_breakdown( + model="no-such-model-anywhere", custom_llm_provider="openai", usage=usage + ) + + assert breakdown.rates is None + + def test_billed_token_rates_are_none_for_an_unpriced_model(): usage = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15) @@ -4036,9 +4078,7 @@ def test_token_type_cost_breakdown_zero_without_special_tokens(_local_model_cost model="gpt-4o", custom_llm_provider="openai", usage=usage ) - assert breakdown == TokenTypeCostBreakdown( - reasoning_cost=0.0, cache_read_cost=0.0, cache_creation_cost=0.0 - ) + assert (breakdown.reasoning_cost, breakdown.cache_read_cost, breakdown.cache_creation_cost) == (0.0, 0.0, 0.0) @pytest.mark.parametrize( @@ -4110,9 +4150,7 @@ def test_token_type_cost_breakdown_handles_unknown_model_gracefully(): completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=5), ), ) - assert breakdown == TokenTypeCostBreakdown( - reasoning_cost=0.0, cache_read_cost=0.0, cache_creation_cost=0.0 - ) + assert (breakdown.reasoning_cost, breakdown.cache_read_cost, breakdown.cache_creation_cost) == (0.0, 0.0, 0.0) def test_token_type_cost_breakdown_applies_regional_uplift(_local_model_cost_map): diff --git a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py index e9485f3a044..7ece35ceedf 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cost_tracking_settings.py @@ -1139,6 +1139,45 @@ class TestEstimateCostCacheAndReasoningTokens: assert response.output_cost_per_request == pytest.approx(OUTPUT_TOKENS * response.output_cost_per_token) + @pytest.mark.asyncio + async def test_an_unrouted_model_reports_the_rates_of_the_provider_the_calculator_inferred(self, monkeypatch): + """The cost calculator infers a provider this endpoint never resolved, and the provider decides + whether a tier threshold is inclusive. xai bills a request sitting exactly on the 200k threshold + at the tier rate, so the reported rates have to be the tier's rather than the sub-tier base.""" + an_xai_model = "xai/tiered-model" + monkeypatch.setitem( + litellm.model_cost, + an_xai_model, + { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + "input_cost_per_token_above_200k_tokens": 6e-6, + "output_cost_per_token_above_200k_tokens": 3e-5, + "cache_read_input_token_cost_above_200k_tokens": 6e-7, + "litellm_provider": "xai", + "mode": "chat", + }, + ) + + response = await _estimate( + None, + model=an_xai_model, + input_tokens=200_000, + cache_read_input_tokens=100_000, + output_tokens=1_000, + ) + + assert response.input_cost_per_token == pytest.approx(6e-6) + assert response.output_cost_per_token == pytest.approx(3e-5) + assert response.cache_read_input_token_cost == pytest.approx(6e-7) + assert response.cache_read_cost_per_request == pytest.approx(100_000 * response.cache_read_input_token_cost) + assert response.input_cost_per_request == pytest.approx( + 100_000 * response.input_cost_per_token + response.cache_read_cost_per_request + ) + assert response.output_cost_per_request == pytest.approx(1_000 * response.output_cost_per_token) + + class TestCostEstimateRequestTokenSubsets: def test_cache_tokens_beyond_the_input_tokens_are_rejected(self): with pytest.raises(ValidationError, match="cannot exceed input_tokens"): diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 910587d3dc5..8f8a7640c08 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3736,6 +3736,62 @@ def test_completion_cost_logs_reasoning_and_cache_breakdown(_local_model_cost_ma assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx(100 * 3e-08) +def test_completion_cost_logs_the_rates_it_billed_at(monkeypatch): + """A caller reporting the cost lines beside their per-token rates reads both off this one call. + completion_cost infers the provider, and xai's inclusive tier thresholds put a request sitting + exactly on 200k at the tier rate, which a lookup made without that inferred provider would miss. + """ + from datetime import datetime + + from litellm.litellm_core_utils.litellm_logging import Logging + + monkeypatch.setitem( + litellm.model_cost, + "xai/tiered-model", + { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + "input_cost_per_token_above_200k_tokens": 6e-6, + "output_cost_per_token_above_200k_tokens": 3e-5, + "cache_read_input_token_cost_above_200k_tokens": 6e-7, + "litellm_provider": "xai", + "mode": "chat", + }, + ) + logging_obj = Logging( + model="xai/tiered-model", + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.now(), + litellm_call_id="billed-rates", + function_id="f", + ) + usage = Usage( + prompt_tokens=200_000, + completion_tokens=1_000, + total_tokens=201_000, + prompt_tokens_details=PromptTokensDetailsWrapper(cached_tokens=100_000), + ) + + litellm.completion_cost( + completion_response=ModelResponse(model="xai/tiered-model", usage=usage), + model="xai/tiered-model", + custom_llm_provider=None, + litellm_logging_obj=logging_obj, + ) + + rates = logging_obj.billed_token_rates + assert rates is not None + assert rates.input_cost_per_token == pytest.approx(6e-6) + assert rates.cache_read_input_token_cost == pytest.approx(6e-7) + assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx( + 100_000 * rates.cache_read_input_token_cost + ) + assert logging_obj.cost_breakdown["output_cost"] == pytest.approx(1_000 * rates.output_cost_per_token) + + def test_completion_cost_logs_cache_and_reasoning_breakdown_for_custom_pricing(): """ A custom-priced deployment bills cache tokens at its custom cache rates, but the From 2ece8735384e063b88ca91cf9d2f8f6d2e94b30a Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 17:05:38 +0000 Subject: [PATCH 095/105] test(e2e): lite CLI never fetches the model cost map Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/e2e/coverage_registry/other.yaml | 2 + tests/e2e/other/test_cli_cost_map_e2e.py | 109 +++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 tests/e2e/other/test_cli_cost_map_e2e.py diff --git a/tests/e2e/coverage_registry/other.yaml b/tests/e2e/coverage_registry/other.yaml index 814ebae2e0b..8ac4fc0762c 100644 --- a/tests/e2e/coverage_registry/other.yaml +++ b/tests/e2e/coverage_registry/other.yaml @@ -2,6 +2,8 @@ # 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.cli.model_cost_map.version_skips_fetch, module: other, tier: P1, area: cli, assertions: [version_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite version command succeeds without requesting the remote model-cost map"} +- {id: other.cli.model_cost_map.models_list_skips_fetch, module: other, tier: P1, area: cli, assertions: [models_list_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite models list command uses the proxy without requesting the remote model-cost map"} - {id: other.auth.llm_chat.missing_header_denied, module: other, tier: P0, area: auth, assertions: [missing_header_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Chat with no Authorization header is 401/403"} - {id: other.auth.llm_chat.invalid_bearer_denied, module: other, tier: P0, area: auth, assertions: [invalid_bearer_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Bearer invalid_token on chat is 401/403"} - {id: other.auth.llm_chat.no_bearer_prefix_denied, module: other, tier: P0, area: auth, assertions: [no_bearer_prefix_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Token without Bearer scheme on chat is 401/403"} diff --git a/tests/e2e/other/test_cli_cost_map_e2e.py b/tests/e2e/other/test_cli_cost_map_e2e.py new file mode 100644 index 00000000000..6022d5a0a4c --- /dev/null +++ b/tests/e2e/other/test_cli_cost_map_e2e.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +import os +import shutil +import subprocess +import threading +from collections.abc import Mapping +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Final + +import pytest +from e2e_config import MASTER_KEY, PROXY_BASE_URL +from proxy_client import ProxyClient + +pytestmark = pytest.mark.e2e + + +def _start_cost_map_server(request_log: Path) -> tuple[ThreadingHTTPServer, threading.Thread]: + class CostMapHandler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + with request_log.open("a", encoding="utf-8") as log_file: + log_file.write(f"{self.path}\n") + body: Final = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format: str, *args: object) -> None: + return + + server: Final = ThreadingHTTPServer(("127.0.0.1", 0), CostMapHandler) + thread: Final = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + return server, thread + + +def _run_lite( + args: tuple[str, ...], + server: ThreadingHTTPServer, + env: Mapping[str, str], +) -> subprocess.CompletedProcess[str]: + lite_path: Final = shutil.which("lite") + assert lite_path is not None, "the installed lite executable is required for e2e coverage" + try: + return subprocess.run( + [lite_path, *args], + capture_output=True, + text=True, + timeout=60, + env=env, + ) + finally: + server.shutdown() + server.server_close() + + +def _request_count(request_log: Path) -> int: + return request_log.read_text(encoding="utf-8").count("\n") if request_log.exists() else 0 + + +class TestLiteCliCostMapFetch: + @pytest.mark.covers("other.cli.model_cost_map.version_skips_fetch") + def test_lite_version_makes_no_cost_map_request(self, tmp_path: Path) -> None: + request_log: Final = tmp_path / "requests.log" + server, thread = _start_cost_map_server(request_log) + source_root: Final = str(Path(__file__).resolve().parents[3]) + pythonpath: Final = os.pathsep.join(filter(None, (source_root, os.environ.get("PYTHONPATH")))) + env: Final = { + **{key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"}, + "PYTHONPATH": pythonpath, + "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", + "LITELLM_PROXY_URL": PROXY_BASE_URL, + } + try: + result: Final = _run_lite(("--version",), server, env) + finally: + thread.join(timeout=10) + + assert result.returncode == 0 + assert "LiteLLM Proxy CLI Version" in result.stdout + assert _request_count(request_log) == 0 + + @pytest.mark.covers("other.cli.model_cost_map.models_list_skips_fetch") + def test_lite_models_list_uses_proxy_not_cost_map(self, tmp_path: Path, proxy: ProxyClient) -> None: + model_names: Final = tuple(entry.model_name for entry in proxy.model_info()) + assert model_names + request_log: Final = tmp_path / "requests.log" + server, thread = _start_cost_map_server(request_log) + source_root: Final = str(Path(__file__).resolve().parents[3]) + pythonpath: Final = os.pathsep.join(filter(None, (source_root, os.environ.get("PYTHONPATH")))) + env: Final = { + **{key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"}, + "PYTHONPATH": pythonpath, + "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", + "LITELLM_PROXY_URL": PROXY_BASE_URL, + "LITELLM_PROXY_API_KEY": MASTER_KEY, + } + try: + result: Final = _run_lite(("models", "list"), server, env) + finally: + thread.join(timeout=10) + + assert result.returncode == 0 + assert result.stdout.strip() + assert any(model_name in result.stdout for model_name in model_names) + assert _request_count(request_log) == 0 From 6e71b90a888b91fe3aa281fa14da58f258b9f056 Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 17:07:34 +0000 Subject: [PATCH 096/105] test(e2e): dedupe lite env setup Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/e2e/coverage_registry/other.yaml | 4 ++-- tests/e2e/other/test_cli_cost_map_e2e.py | 29 ++++++++++-------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/e2e/coverage_registry/other.yaml b/tests/e2e/coverage_registry/other.yaml index 8ac4fc0762c..a7ba0dbb8cb 100644 --- a/tests/e2e/coverage_registry/other.yaml +++ b/tests/e2e/coverage_registry/other.yaml @@ -2,8 +2,6 @@ # 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.cli.model_cost_map.version_skips_fetch, module: other, tier: P1, area: cli, assertions: [version_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite version command succeeds without requesting the remote model-cost map"} -- {id: other.cli.model_cost_map.models_list_skips_fetch, module: other, tier: P1, area: cli, assertions: [models_list_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite models list command uses the proxy without requesting the remote model-cost map"} - {id: other.auth.llm_chat.missing_header_denied, module: other, tier: P0, area: auth, assertions: [missing_header_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Chat with no Authorization header is 401/403"} - {id: other.auth.llm_chat.invalid_bearer_denied, module: other, tier: P0, area: auth, assertions: [invalid_bearer_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Bearer invalid_token on chat is 401/403"} - {id: other.auth.llm_chat.no_bearer_prefix_denied, module: other, tier: P0, area: auth, assertions: [no_bearer_prefix_denied], source: "vendor testing strategy §11.1 / LIT-4778", rationale: "Token without Bearer scheme on chat is 401/403"} @@ -50,3 +48,5 @@ - {id: other.a2a.message_send.bridge_invokes, module: other, tier: P1, area: a2a, assertions: [bridge_invokes], source: "a2a_protocol/litellm_completion_bridge/handler.py", rationale: "A2A message/send routes through the completion bridge to a real provider and logs an asend_message spend row"} - {id: other.a2a.version.serves_pinned_0_3, module: other, tier: P1, area: a2a, assertions: [serves_pinned_0_3], source: "agent_endpoints/a2a_endpoints.py _served_version", rationale: "An agent pinning 0.3 returns the flat 0.3 message shape (parts on the result)"} - {id: other.a2a.version.serves_pinned_1_0, module: other, tier: P1, area: a2a, assertions: [serves_pinned_1_0], source: "agent_endpoints/a2a_endpoints.py _served_version", rationale: "An agent pinning 1.0 returns the nested 1.0 message shape (result.message with ROLE_AGENT)"} +- {id: other.cli.model_cost_map.version_skips_fetch, module: other, tier: P1, area: cli, assertions: [version_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite version command succeeds without requesting the remote model-cost map"} +- {id: other.cli.model_cost_map.models_list_skips_fetch, module: other, tier: P1, area: cli, assertions: [models_list_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite models list command uses the proxy without requesting the remote model-cost map"} diff --git a/tests/e2e/other/test_cli_cost_map_e2e.py b/tests/e2e/other/test_cli_cost_map_e2e.py index 6022d5a0a4c..62cd3d6c3f5 100644 --- a/tests/e2e/other/test_cli_cost_map_e2e.py +++ b/tests/e2e/other/test_cli_cost_map_e2e.py @@ -37,6 +37,16 @@ def _start_cost_map_server(request_log: Path) -> tuple[ThreadingHTTPServer, thre return server, thread +def _lite_env(server: ThreadingHTTPServer, api_key: str | None) -> dict[str, str]: + base_env: Final = {key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"} + return { + **base_env, + "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", + "LITELLM_PROXY_URL": PROXY_BASE_URL, + **({"LITELLM_PROXY_API_KEY": api_key} if api_key is not None else {}), + } + + def _run_lite( args: tuple[str, ...], server: ThreadingHTTPServer, @@ -66,14 +76,7 @@ class TestLiteCliCostMapFetch: def test_lite_version_makes_no_cost_map_request(self, tmp_path: Path) -> None: request_log: Final = tmp_path / "requests.log" server, thread = _start_cost_map_server(request_log) - source_root: Final = str(Path(__file__).resolve().parents[3]) - pythonpath: Final = os.pathsep.join(filter(None, (source_root, os.environ.get("PYTHONPATH")))) - env: Final = { - **{key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"}, - "PYTHONPATH": pythonpath, - "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", - "LITELLM_PROXY_URL": PROXY_BASE_URL, - } + env: Final = _lite_env(server, None) try: result: Final = _run_lite(("--version",), server, env) finally: @@ -89,15 +92,7 @@ class TestLiteCliCostMapFetch: assert model_names request_log: Final = tmp_path / "requests.log" server, thread = _start_cost_map_server(request_log) - source_root: Final = str(Path(__file__).resolve().parents[3]) - pythonpath: Final = os.pathsep.join(filter(None, (source_root, os.environ.get("PYTHONPATH")))) - env: Final = { - **{key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"}, - "PYTHONPATH": pythonpath, - "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", - "LITELLM_PROXY_URL": PROXY_BASE_URL, - "LITELLM_PROXY_API_KEY": MASTER_KEY, - } + env: Final = _lite_env(server, MASTER_KEY) try: result: Final = _run_lite(("models", "list"), server, env) finally: From 996ee5635ab79854992661279c2e8db793b3dda4 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:35:50 -0700 Subject: [PATCH 097/105] perf(proxy): pipeline spend counter increments into one Redis call per request (#40371) * perf(proxy): pipeline spend counter increments into one redis call Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): apply surviving spend increments before raising scope error Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(proxy): ruff format spend counter helpers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): settle inner spend counter gathers and fall back per key on pipeline failure Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): suppress BLE001 on pipeline fallback catch Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): invalidate all batched spend counters on pipeline failure Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yassin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 331 +++++++---- .../proxy/proxy_server/test_spend_counters.py | 538 ++++++++++-------- .../test_budget_reservation_redis_failure.py | 9 + .../proxy/test_budget_reservation.py | 9 + tests/test_litellm/proxy/test_proxy_server.py | 94 ++- 5 files changed, 609 insertions(+), 372 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 7aed0553b4b..c241e66049b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -17,6 +17,7 @@ import time import traceback import warnings from collections.abc import AsyncGenerator, AsyncIterator, Callable, Collection, Mapping, MutableMapping, Sequence +from dataclasses import dataclass from datetime import datetime, timedelta, timezone from types import MappingProxyType, UnionType from typing import ( @@ -131,6 +132,7 @@ from litellm.router_utils.auto_router_tuning_baseline import ( snapshot_tuning_baselines, tuning_limit_violation, ) +from litellm.types.caching import RedisPipelineIncrementOperation from litellm.types.utils import ( ModelResponse, ModelResponseStream, @@ -2703,6 +2705,12 @@ async def _read_spend_counter_estimate(counter_key: str, fallback_spend: float) return fallback_spend, False +@dataclass(frozen=True, slots=True) +class _PendingSpendIncrement: + counter_key: str + increment: float + + async def increment_spend_counters( token: str | None, team_id: str | None, @@ -2737,7 +2745,7 @@ async def increment_spend_counters( cost: Final[float] = response_cost - async def _key_scope(key_token: str) -> None: + async def _key_scope(key_token: str) -> tuple[_PendingSpendIncrement | BaseException, ...]: # key_token arrives pre-hashed from metadata["user_api_key"] (auth flow # hashes raw "sk-..." keys before they reach the callback). The # startswith("sk-") check is a safety net matching update_cache — @@ -2748,30 +2756,29 @@ async def increment_spend_counters( hash_token(token=key_token) if isinstance(key_token, str) and key_token.startswith("sk-") else key_token ) key_counter_key: Final = f"spend:key:{hashed_token}" - if key_counter_key not in reserved_counter_keys: - await _init_and_increment_spend_counter( - counter_key=key_counter_key, - source_cache_key=hashed_token, - increment=cost, + key_pending: Final[tuple[_PendingSpendIncrement, ...]] = ( + () + if key_counter_key in reserved_counter_keys + else ( + await _prepare_spend_counter_increment( + counter_key=key_counter_key, + source_cache_key=hashed_token, + increment=cost, + ), ) - - key_obj: Final[object] = await user_api_key_cache.async_get_cache(key=hashed_token) - if key_obj is None: - return - key_budget_limits = getattr(key_obj, "budget_limits", None) or ( - key_obj.get("budget_limits") if isinstance(key_obj, dict) else None ) - if isinstance(key_budget_limits, str): - key_budget_limits = json.loads(key_budget_limits) - if not isinstance(key_budget_limits, list): - return - for window in key_budget_limits: - duration = window["budget_duration"] if isinstance(window, dict) else window.budget_duration - key_window_reset_at = window.get("reset_at") if isinstance(window, dict) else window.reset_at - key_window_counter = f"spend:key:{hashed_token}:window:{duration}" + + async def _key_window_increment(window: object) -> _PendingSpendIncrement | None: + duration = ( + window["budget_duration"] if isinstance(window, dict) else getattr(window, "budget_duration", None) + ) + key_window_reset_at = ( + window.get("reset_at") if isinstance(window, dict) else getattr(window, "reset_at", None) + ) + key_window_counter: Final = f"spend:key:{hashed_token}:window:{duration}" key_window_start = get_budget_window_start(window) - if key_window_counter not in reserved_counter_keys: - await _init_and_increment_window_spend_counter( + pending_window: Final = ( + await _prepare_window_spend_counter_increment( counter_key=key_window_counter, entity_type="Key", entity_id=hashed_token, @@ -2779,6 +2786,9 @@ async def increment_spend_counters( window_start=key_window_start, increment=cost, ) + if key_window_counter not in reserved_counter_keys + else None + ) await _enqueue_window_spend_row_update( entity_type=Litellm_EntityType.KEY, entity_id=hashed_token, @@ -2788,33 +2798,48 @@ async def increment_spend_counters( increment=cost, request_started_at=request_started_at, ) + return pending_window - async def _team_scope(scope_team_id: str) -> None: - team_counter_key: Final = f"spend:team:{scope_team_id}" - if team_counter_key not in reserved_counter_keys: - await _init_and_increment_spend_counter( - counter_key=team_counter_key, - source_cache_key=f"team_id:{scope_team_id}", - increment=cost, - ) - - team_obj: Final[object] = await user_api_key_cache.async_get_cache(key=f"team_id:{scope_team_id}") - if team_obj is None: - return - team_budget_limits = getattr(team_obj, "budget_limits", None) or ( - team_obj.get("budget_limits") if isinstance(team_obj, dict) else None + key_obj: Final[object] = await user_api_key_cache.async_get_cache(key=hashed_token) + if key_obj is None: + return key_pending + key_budget_limits = getattr(key_obj, "budget_limits", None) or ( + key_obj.get("budget_limits") if isinstance(key_obj, dict) else None ) - if isinstance(team_budget_limits, str): - team_budget_limits = json.loads(team_budget_limits) - if not isinstance(team_budget_limits, list): - return - for window in team_budget_limits: - duration = window["budget_duration"] if isinstance(window, dict) else window.budget_duration - team_window_reset_at = window.get("reset_at") if isinstance(window, dict) else window.reset_at - team_window_counter = f"spend:team:{scope_team_id}:window:{duration}" + if isinstance(key_budget_limits, str): + key_budget_limits = json.loads(key_budget_limits) + if not isinstance(key_budget_limits, list): + return key_pending + window_pending: Final = await asyncio.gather( + *(_key_window_increment(window) for window in key_budget_limits), return_exceptions=True + ) + return key_pending + tuple(item for item in window_pending if item is not None) + + async def _team_scope(scope_team_id: str) -> tuple[_PendingSpendIncrement | BaseException, ...]: + team_counter_key: Final = f"spend:team:{scope_team_id}" + team_pending: Final[tuple[_PendingSpendIncrement, ...]] = ( + () + if team_counter_key in reserved_counter_keys + else ( + await _prepare_spend_counter_increment( + counter_key=team_counter_key, + source_cache_key=f"team_id:{scope_team_id}", + increment=cost, + ), + ) + ) + + async def _team_window_increment(window: object) -> _PendingSpendIncrement | None: + duration = ( + window["budget_duration"] if isinstance(window, dict) else getattr(window, "budget_duration", None) + ) + team_window_reset_at = ( + window.get("reset_at") if isinstance(window, dict) else getattr(window, "reset_at", None) + ) + team_window_counter: Final = f"spend:team:{scope_team_id}:window:{duration}" team_window_start = get_budget_window_start(window) - if team_window_counter not in reserved_counter_keys: - await _init_and_increment_window_spend_counter( + pending_window: Final = ( + await _prepare_window_spend_counter_increment( counter_key=team_window_counter, entity_type="Team", entity_id=scope_team_id, @@ -2822,6 +2847,9 @@ async def increment_spend_counters( window_start=team_window_start, increment=cost, ) + if team_window_counter not in reserved_counter_keys + else None + ) await _enqueue_window_spend_row_update( entity_type=Litellm_EntityType.TEAM, entity_id=scope_team_id, @@ -2831,25 +2859,47 @@ async def increment_spend_counters( increment=cost, request_started_at=request_started_at, ) + return pending_window - async def _team_member_scope(scope_user_id: str, scope_team_id: str) -> None: + team_obj: Final[object] = await user_api_key_cache.async_get_cache(key=f"team_id:{scope_team_id}") + if team_obj is None: + return team_pending + team_budget_limits = getattr(team_obj, "budget_limits", None) or ( + team_obj.get("budget_limits") if isinstance(team_obj, dict) else None + ) + if isinstance(team_budget_limits, str): + team_budget_limits = json.loads(team_budget_limits) + if not isinstance(team_budget_limits, list): + return team_pending + window_pending: Final = await asyncio.gather( + *(_team_window_increment(window) for window in team_budget_limits), return_exceptions=True + ) + return team_pending + tuple(item for item in window_pending if item is not None) + + async def _team_member_scope( + scope_user_id: str, scope_team_id: str + ) -> tuple[_PendingSpendIncrement | BaseException, ...]: team_member_counter_key: Final = f"spend:team_member:{scope_user_id}:{scope_team_id}" if team_member_counter_key in reserved_counter_keys: - return - await _init_and_increment_spend_counter( - counter_key=team_member_counter_key, - source_cache_key=f"team_membership:{scope_user_id}:{scope_team_id}", - increment=cost, + return () + return ( + await _prepare_spend_counter_increment( + counter_key=team_member_counter_key, + source_cache_key=f"team_membership:{scope_user_id}:{scope_team_id}", + increment=cost, + ), ) - async def _user_scope(scope_user_id: str) -> None: + async def _user_scope(scope_user_id: str) -> tuple[_PendingSpendIncrement | BaseException, ...]: user_counter_key: Final = f"spend:user:{scope_user_id}" if user_counter_key in reserved_counter_keys: - return - await _init_and_increment_spend_counter( - counter_key=user_counter_key, - source_cache_key=scope_user_id, - increment=cost, + return () + return ( + await _prepare_spend_counter_increment( + counter_key=user_counter_key, + source_cache_key=scope_user_id, + increment=cost, + ), ) scope_coros: Final = tuple( @@ -2859,7 +2909,7 @@ async def increment_spend_counters( _team_scope(team_id) if team_id is not None else None, _team_member_scope(user_id, team_id) if user_id is not None and team_id is not None else None, _user_scope(user_id) if user_id is not None else None, - _increment_end_user_and_tag_spend_counters( + _prepare_end_user_and_tag_spend_increments( end_user_id=end_user_id, tags=tags, response_cost=cost, @@ -2867,14 +2917,14 @@ async def increment_spend_counters( ) if end_user_id is not None or tags is not None else None, - _increment_model_access_group_spend_counters( + _prepare_model_access_group_spend_increments( model_access_groups=model_access_groups, response_cost=cost, reserved_counter_keys=reserved_counter_keys, ) if model_access_groups else None, - _increment_org_spend_counter( + _prepare_org_spend_increment( org_id=org_id, response_cost=cost, reserved_counter_keys=reserved_counter_keys, @@ -2889,7 +2939,20 @@ async def increment_spend_counters( # as orphaned tasks that race the caller's reservation-counter invalidation; # all scopes settle, then the first error propagates as before. scope_results: Final = await asyncio.gather(*scope_coros, return_exceptions=True) - scope_errors: Final = [r for r in scope_results if isinstance(r, BaseException)] + scope_errors: Final = tuple( + item + for scope in scope_results + for item in (scope if isinstance(scope, tuple) else (scope,)) + if isinstance(item, BaseException) + ) + pending: Final = tuple( + item + for scope in scope_results + if not isinstance(scope, BaseException) + for item in scope + if not isinstance(item, BaseException) + ) + await _apply_spend_counter_increments(pending=pending) if scope_errors: raise scope_errors[0] @@ -2932,41 +2995,49 @@ async def _reconcile_budget_reservation_for_counter_update( return reserved_counter_keys -async def _increment_end_user_and_tag_spend_counters( +async def _prepare_end_user_and_tag_spend_increments( end_user_id: str | None, tags: list[str] | None, response_cost: float, reserved_counter_keys: set[str], -) -> None: - if end_user_id is not None: - await _init_and_increment_unreserved_spend_counter( - counter_key=f"spend:end_user:{end_user_id}", - source_cache_key=end_user_cache_key(end_user_id), - increment=response_cost, - reserved_counter_keys=reserved_counter_keys, - ) - - if tags is None: - return - - seen_tags: Final[set[str]] = set() - for tag_name in tags: - if not tag_name or not isinstance(tag_name, str) or tag_name in seen_tags: - continue - seen_tags.add(tag_name) - await _init_and_increment_unreserved_spend_counter( - counter_key=f"spend:tag:{tag_name}", - source_cache_key=tag_cache_key(tag_name), - increment=response_cost, - reserved_counter_keys=reserved_counter_keys, - ) +) -> tuple[_PendingSpendIncrement | BaseException, ...]: + unique_tags: Final = ( + tuple(dict.fromkeys(tag for tag in tags if tag and isinstance(tag, str))) if tags is not None else () + ) + results: Final = await asyncio.gather( + *( + coro + for coro in ( + _prepare_unreserved_spend_counter_increment( + counter_key=f"spend:end_user:{end_user_id}", + source_cache_key=end_user_cache_key(end_user_id), + increment=response_cost, + reserved_counter_keys=reserved_counter_keys, + ) + if end_user_id is not None + else None, + *( + _prepare_unreserved_spend_counter_increment( + counter_key=f"spend:tag:{tag_name}", + source_cache_key=tag_cache_key(tag_name), + increment=response_cost, + reserved_counter_keys=reserved_counter_keys, + ) + for tag_name in unique_tags + ), + ) + if coro is not None + ), + return_exceptions=True, + ) + return tuple(item for item in results if item is not None) -async def _increment_model_access_group_spend_counters( +async def _prepare_model_access_group_spend_increments( model_access_groups: Sequence[object], response_cost: float, reserved_counter_keys: set[str], -) -> None: +) -> tuple[_PendingSpendIncrement | BaseException, ...]: """Charge the model access groups that authorized this request. Without this the counter auth reads is written only by the reservation path, so @@ -2980,55 +3051,63 @@ async def _increment_model_access_group_spend_counters( unique_groups: Final = tuple( dict.fromkeys(group for group in model_access_groups if group and isinstance(group, str)) ) - for group in unique_groups: - await _init_and_increment_unreserved_spend_counter( - counter_key=model_access_group_spend_counter_key(group), - source_cache_key=model_access_group_cache_key(group), - increment=response_cost, - reserved_counter_keys=reserved_counter_keys, - ) + results: Final = await asyncio.gather( + *( + _prepare_unreserved_spend_counter_increment( + counter_key=model_access_group_spend_counter_key(group), + source_cache_key=model_access_group_cache_key(group), + increment=response_cost, + reserved_counter_keys=reserved_counter_keys, + ) + for group in unique_groups + ), + return_exceptions=True, + ) + return tuple(item for item in results if item is not None) -async def _increment_org_spend_counter( +async def _prepare_org_spend_increment( org_id: str | None, response_cost: float, reserved_counter_keys: set[str], -) -> None: +) -> tuple[_PendingSpendIncrement, ...]: if org_id is None: - return + return () - await _init_and_increment_unreserved_spend_counter( + pending: Final = await _prepare_unreserved_spend_counter_increment( counter_key=f"spend:org:{org_id}", source_cache_key=[f"org_id:{org_id}:with_budget", f"org_id:{org_id}"], increment=response_cost, reserved_counter_keys=reserved_counter_keys, ) + return (pending,) if pending is not None else () -async def _init_and_increment_unreserved_spend_counter( +async def _prepare_unreserved_spend_counter_increment( counter_key: str, source_cache_key: str | list[str], increment: float, reserved_counter_keys: set[str], -) -> None: +) -> _PendingSpendIncrement | None: if counter_key in reserved_counter_keys: - return + return None - await _init_and_increment_spend_counter( + return await _prepare_spend_counter_increment( counter_key=counter_key, source_cache_key=source_cache_key, increment=increment, ) -async def _init_and_increment_spend_counter( +async def _prepare_spend_counter_increment( counter_key: str, source_cache_key: str | list[str], increment: float, -): +) -> _PendingSpendIncrement: """ Initialize counter from the authoritative DB spend value if not yet - set, then atomically increment in both in-memory and Redis. + set, then return the pending increment for the caller to apply in one + pipelined Redis call. On first access per pod: 1. Check spend_counter_cache (in-memory -> Redis via DualCache) @@ -3040,13 +3119,13 @@ async def _init_and_increment_spend_counter( the counter as absent and seed it. Using increment means the worst case is over-counting (conservative, blocks slightly early) rather than under-counting (would allow overspend). - 4. Increment atomically (both in-memory + Redis) + 4. Increment is returned for the caller to apply via pipeline """ await _ensure_spend_counter_initialized( counter_key=counter_key, source_cache_key=source_cache_key, ) - await _increment_spend_counter_cache(counter_key=counter_key, increment=increment) + return _PendingSpendIncrement(counter_key=counter_key, increment=increment) async def _enqueue_window_spend_row_update( @@ -3098,20 +3177,20 @@ async def _enqueue_window_spend_row_update( ) -async def _init_and_increment_window_spend_counter( +async def _prepare_window_spend_counter_increment( counter_key: str, entity_type: str, entity_id: str, window_duration: str | None, window_start: datetime | None, increment: float, -): +) -> _PendingSpendIncrement | None: if window_start is None: verbose_proxy_logger.warning( "Skipping spend counter increment for invalid budget window %s", counter_key, ) - return + return None initialized: Final = await _ensure_window_spend_counter_initialized( counter_key=counter_key, @@ -3121,8 +3200,8 @@ async def _init_and_increment_window_spend_counter( window_start=window_start, ) if initialized is False: - return - await _increment_spend_counter_cache(counter_key=counter_key, increment=increment) + return None + return _PendingSpendIncrement(counter_key=counter_key, increment=increment) async def _ensure_spend_counter_initialized( @@ -3255,6 +3334,32 @@ async def _invalidate_spend_counter(counter_key: str): ) +async def _apply_spend_counter_increments(pending: Sequence[_PendingSpendIncrement]) -> None: + if not pending: + return + redis_cache: Final = spend_counter_cache.redis_cache + if redis_cache is None: + for item in pending: + await spend_counter_cache.async_increment_cache( + key=item.counter_key, + value=item.increment, + refresh_ttl=True, + ) + return + ttl: Final = redis_cache.get_ttl() + increment_list: Final = [ # mutable-ok: async_increment_pipeline signature requires list[RedisPipelineIncrementOperation] + RedisPipelineIncrementOperation(key=item.counter_key, increment_value=item.increment, ttl=ttl) + for item in pending + ] + try: + results: Final = await redis_cache.async_increment_pipeline(increment_list=increment_list) + except Exception: + await asyncio.gather(*(_invalidate_spend_counter(counter_key=item.counter_key) for item in pending)) + raise + for item, current_value in zip(pending, results or ()): + spend_counter_cache.in_memory_cache.set_cache(key=item.counter_key, value=current_value) + + async def update_cache( token: str | None, user_id: str | None, diff --git a/tests/test_litellm/proxy/proxy_server/test_spend_counters.py b/tests/test_litellm/proxy/proxy_server/test_spend_counters.py index 86e97a334df..c343652efd9 100644 --- a/tests/test_litellm/proxy/proxy_server/test_spend_counters.py +++ b/tests/test_litellm/proxy/proxy_server/test_spend_counters.py @@ -4,11 +4,12 @@ Pins covered: - ``get_current_spend`` - ``increment_spend_counters`` - ``_reconcile_budget_reservation_for_counter_update`` -- ``_increment_end_user_and_tag_spend_counters`` -- ``_increment_org_spend_counter`` -- ``_init_and_increment_unreserved_spend_counter`` -- ``_init_and_increment_spend_counter`` -- ``_init_and_increment_window_spend_counter`` +- ``_prepare_end_user_and_tag_spend_increments`` +- ``_prepare_org_spend_increment`` +- ``_prepare_unreserved_spend_counter_increment`` +- ``_prepare_spend_counter_increment`` +- ``_prepare_window_spend_counter_increment`` +- ``_apply_spend_counter_increments`` - ``_ensure_spend_counter_initialized`` - ``_get_source_cache_base_spend`` - ``_ensure_window_spend_counter_initialized`` @@ -48,9 +49,7 @@ def _make_spend_counter_cache( cache.in_memory_cache.delete_cache = MagicMock() if with_redis: cache.redis_cache = MagicMock() - cache.redis_cache.async_get_cache = AsyncMock( - return_value=redis_get_value, side_effect=redis_get_side_effect - ) + cache.redis_cache.async_get_cache = AsyncMock(return_value=redis_get_value, side_effect=redis_get_side_effect) cache.redis_cache.async_increment = AsyncMock( return_value=redis_increment_value, side_effect=redis_increment_side_effect, @@ -58,6 +57,8 @@ def _make_spend_counter_cache( cache.redis_cache.async_delete_cache = AsyncMock() cache.redis_cache.async_set_cache = AsyncMock() cache.redis_cache.async_set_max = AsyncMock() + cache.redis_cache.async_increment_pipeline = AsyncMock(return_value=None) + cache.redis_cache.get_ttl = MagicMock(return_value=None) else: cache.redis_cache = None cache.async_increment_cache = AsyncMock(return_value=redis_increment_value) @@ -70,9 +71,7 @@ def _make_spend_counter_cache( def _make_user_api_key_cache(get_value=None, get_side_effect=None): cache = MagicMock() - cache.async_get_cache = AsyncMock( - return_value=get_value, side_effect=get_side_effect - ) + cache.async_get_cache = AsyncMock(return_value=get_value, side_effect=get_side_effect) cache.async_set_cache_pipeline = AsyncMock() return cache @@ -109,9 +108,7 @@ async def test_get_current_spend_redis_error_falls_back_to_in_memory(monkeypatch ) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - result = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=99.0 - ) + result = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=99.0) assert result == 17.0 @@ -136,9 +133,7 @@ async def test_get_current_spend_floors_stale_low_counter_against_db(monkeypatch # the stale counter is repaired up to the authoritative DB value via a # monotonic set-max so other workers read the corrected total, and a # concurrent increment cannot be clobbered - fake_cache.redis_cache.async_set_max.assert_awaited_once_with( - key="spend:key:abc", value=12.0 - ) + fake_cache.redis_cache.async_set_max.assert_awaited_once_with(key="spend:key:abc", value=12.0) @pytest.mark.asyncio @@ -169,9 +164,7 @@ async def test_get_current_spend_no_floor_without_max_budget(monkeypatch): from_db = AsyncMock(return_value=12.0) monkeypatch.setattr(ps.SpendCounterReseed, "from_db", from_db) - result = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=12.0 - ) + result = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=12.0) assert result == 2.0 assert from_db.await_count == 0 @@ -210,12 +203,8 @@ async def test_get_current_spend_floor_caches_db_read(monkeypatch): from_db = AsyncMock(return_value=12.0) monkeypatch.setattr(ps.SpendCounterReseed, "from_db", from_db) - first = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=12.0, max_budget=10.0 - ) - second = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=12.0, max_budget=10.0 - ) + first = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=12.0, max_budget=10.0) + second = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=12.0, max_budget=10.0) assert first == 12.0 assert second == 12.0 @@ -336,9 +325,7 @@ async def test_get_current_spend_floors_window_against_spend_logs(monkeypatch): assert result == 15.0 assert wfsl.await_count == 1 - fake_cache.redis_cache.async_set_max.assert_awaited_once_with( - key=counter_key, value=15.0 - ) + fake_cache.redis_cache.async_set_max.assert_awaited_once_with(key=counter_key, value=15.0) def _make_window_spend_prisma(row=None, spend_logs_total=0.0): @@ -379,9 +366,7 @@ async def test_get_current_spend_floors_window_against_maintained_row(monkeypatc assert result == 15.0 fake_prisma.db.litellm_spendlogs.group_by.assert_not_awaited() - fake_cache.redis_cache.async_set_max.assert_awaited_once_with( - key=counter_key, value=15.0 - ) + fake_cache.redis_cache.async_set_max.assert_awaited_once_with(key=counter_key, value=15.0) @pytest.mark.asyncio @@ -393,9 +378,7 @@ async def test_get_current_spend_floors_window_against_logs_when_row_stale(monke window_start = datetime(2026, 1, 8, tzinfo=timezone.utc) fake_prisma = _make_window_spend_prisma( - row=SimpleNamespace( - window_start=window_start - timedelta(days=7), spend=999.0 - ), + row=SimpleNamespace(window_start=window_start - timedelta(days=7), spend=999.0), spend_logs_total=15.0, ) fake_cache = _make_spend_counter_cache(redis_get_value=2.0) @@ -423,21 +406,13 @@ async def test_get_current_spend_fail_closed_rejects_when_unverifiable(monkeypat rather than admitted on an unverifiable budget.""" from fastapi import HTTPException - fake_cache = _make_spend_counter_cache( - redis_get_side_effect=RuntimeError("redis down") - ) + fake_cache = _make_spend_counter_cache(redis_get_side_effect=RuntimeError("redis down")) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - monkeypatch.setattr( - ps, "general_settings", {"fail_closed_budget_enforcement": True} - ) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps, "general_settings", {"fail_closed_budget_enforcement": True}) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) with pytest.raises(HTTPException) as exc: - await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0 - ) + await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0) assert exc.value.status_code == 503 @@ -445,18 +420,12 @@ async def test_get_current_spend_fail_closed_rejects_when_unverifiable(monkeypat async def test_get_current_spend_fail_closed_off_admits_when_unverifiable(monkeypatch): """Default (flag off): an unverifiable read keeps the existing behavior and admits using the cached fallback — no new rejection.""" - fake_cache = _make_spend_counter_cache( - redis_get_side_effect=RuntimeError("redis down") - ) + fake_cache = _make_spend_counter_cache(redis_get_side_effect=RuntimeError("redis down")) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "general_settings", {}) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) - result = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0 - ) + result = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0) assert result == 1.0 @@ -466,13 +435,9 @@ async def test_get_current_spend_fail_closed_admits_when_redis_verified(monkeypa authoritative, so an under-budget request is admitted normally.""" fake_cache = _make_spend_counter_cache(redis_get_value=1.0) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - monkeypatch.setattr( - ps, "general_settings", {"fail_closed_budget_enforcement": True} - ) + monkeypatch.setattr(ps, "general_settings", {"fail_closed_budget_enforcement": True}) - result = await ps.get_current_spend( - counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0 - ) + result = await ps.get_current_spend(counter_key="spend:key:abc", fallback_spend=1.0, max_budget=10.0) assert result == 1.0 @@ -481,16 +446,10 @@ async def test_get_current_spend_fail_closed_allows_authoritative_fallback(monke """End-user/tag callers pass fallback_authoritative=True (their spend is loaded fresh from the DB in auth), so fail-closed does not reject them even when the counter path is unreadable.""" - fake_cache = _make_spend_counter_cache( - redis_get_side_effect=RuntimeError("redis down") - ) + fake_cache = _make_spend_counter_cache(redis_get_side_effect=RuntimeError("redis down")) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - monkeypatch.setattr( - ps, "general_settings", {"fail_closed_budget_enforcement": True} - ) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps, "general_settings", {"fail_closed_budget_enforcement": True}) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) result = await ps.get_current_spend( counter_key="spend:end_user:e1", @@ -508,9 +467,7 @@ async def test_get_current_spend_strict_floors_when_fallback_also_stale(monkeypa re-checks the authoritative DB and enforces against it.""" fake_cache = _make_spend_counter_cache(redis_get_value=0.00001) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - monkeypatch.setattr( - ps, "general_settings", {"fail_closed_budget_enforcement": True} - ) + monkeypatch.setattr(ps, "general_settings", {"fail_closed_budget_enforcement": True}) from_db = AsyncMock(return_value=0.5) monkeypatch.setattr(ps.SpendCounterReseed, "from_db", from_db) @@ -532,9 +489,7 @@ async def test_get_current_spend_strict_floors_when_fallback_also_stale(monkeypa @pytest.mark.asyncio async def test_increment_spend_counters_increments_all_buckets(monkeypatch): - fake_cache = _make_spend_counter_cache( - redis_get_value=None, redis_increment_value=5.0 - ) + fake_cache = _make_spend_counter_cache(redis_get_value=None, redis_increment_value=5.0) fake_user_cache = _make_user_api_key_cache(get_value=None) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) @@ -543,9 +498,7 @@ async def test_increment_spend_counters_increments_all_buckets(monkeypatch): async def _fake_coalesced(**kwargs): return None - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(side_effect=_fake_coalesced) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(side_effect=_fake_coalesced)) await ps.increment_spend_counters( token="hashed-tok", @@ -554,25 +507,36 @@ async def test_increment_spend_counters_increments_all_buckets(monkeypatch): response_cost=5.0, ) + pipeline = fake_cache.redis_cache.async_increment_pipeline + pipeline.assert_awaited_once() + increment_list = pipeline.await_args.kwargs["increment_list"] + assert {op["key"] for op in increment_list} == { + "spend:key:hashed-tok", + "spend:team:t1", + "spend:team_member:u1:t1", + "spend:user:u1", + } + assert all(op["increment_value"] == 5.0 for op in increment_list) observed = { "redis_increment_called": fake_cache.redis_cache.async_increment.called, - "increment_calls": fake_cache.redis_cache.async_increment.call_count, + "pipeline_calls": pipeline.await_count, "user_cache_used": fake_user_cache.async_get_cache.called, } assert normalize(observed) == { - "redis_increment_called": True, - "increment_calls": 4, + "redis_increment_called": False, + "pipeline_calls": 1, "user_cache_used": True, } class _ConcurrencyProbe: - """Stand-in for redis_cache.async_increment that pins concurrency. + """Stand-in for redis_cache.async_get_cache that pins concurrency. - Each call registers itself as in-flight and blocks on ``release`` until the - test lets it proceed. ``all_arrived`` fires once ``expected`` distinct scope - increments are simultaneously suspended here, which can only happen if the - per-scope increments are gathered rather than awaited one after another. + Each warm-check read registers itself as in-flight and blocks on ``release`` + until the test lets it proceed. ``all_arrived`` fires once ``expected`` + distinct scope warm-checks are simultaneously suspended here, which can only + happen if the per-scope prepares are gathered rather than awaited one after + another. """ def __init__(self, expected_concurrency: int): @@ -581,36 +545,45 @@ class _ConcurrencyProbe: self.max_in_flight = 0 self.all_arrived = asyncio.Event() self.release = asyncio.Event() - self.values: dict[str, float] = {} + self.keys: list[str] = [] - async def async_increment(self, *, key, value, refresh_ttl=True): + async def async_get_cache(self, *, key, **kwargs): self.in_flight += 1 self.max_in_flight = max(self.max_in_flight, self.in_flight) + self.keys.append(key) if self.in_flight >= self.expected: self.all_arrived.set() if not self.release.is_set(): await self.release.wait() self.in_flight -= 1 - self.values[key] = self.values.get(key, 0.0) + value - return self.values[key] + return 1.0 @pytest.mark.asyncio async def test_increment_spend_counters_runs_scopes_concurrently(monkeypatch): """The six independent scopes (key, team, team_member, user, end_user+tags, - org) must be incremented concurrently. The probe only fires once all six are - suspended in async_increment at the same time, which is impossible if the + org) must prepare their increments concurrently. The probe only fires once + all eight warm-check reads (one per counter: 6 scopes + 2 tags) are + suspended in async_get_cache at the same time, which is impossible if the awaits are chained sequentially.""" - probe = _ConcurrencyProbe(expected_concurrency=6) - fake_cache = _make_spend_counter_cache(redis_get_value=None) - fake_cache.redis_cache.async_increment = probe.async_increment + probe = _ConcurrencyProbe(expected_concurrency=8) + fake_cache = _make_spend_counter_cache() + fake_cache.redis_cache.async_get_cache = probe.async_get_cache + recorded: dict[str, float] = {} + + async def _record_pipeline(increment_list, **_): + results = [] + for op in increment_list: + recorded[op["key"]] = recorded.get(op["key"], 0.0) + op["increment_value"] + results.append(recorded[op["key"]]) + return results + + fake_cache.redis_cache.async_increment_pipeline = AsyncMock(side_effect=_record_pipeline) fake_user_cache = _make_user_api_key_cache(get_value=None) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) task = asyncio.create_task( ps.increment_spend_counters( @@ -630,16 +603,16 @@ async def test_increment_spend_counters_runs_scopes_concurrently(monkeypatch): probe.release.set() await task pytest.fail( - "scope increments did not run concurrently; sequential awaits " - f"detected (peak in-flight was {probe.max_in_flight}, expected 6)" + "scope prepares did not run concurrently; sequential awaits " + f"detected (peak in-flight was {probe.max_in_flight}, expected 8)" ) - assert probe.in_flight == 6 - assert probe.max_in_flight == 6 + assert probe.in_flight == 8 + assert probe.max_in_flight == 8 probe.release.set() await task - assert probe.values == { + assert recorded == { "spend:key:hashed-tok": 5.0, "spend:team:t1": 5.0, "spend:team_member:u1:t1": 5.0, @@ -659,26 +632,25 @@ async def test_increment_spend_counters_skips_reserved_counter_keys(monkeypatch) import litellm.proxy.spend_tracking.budget_reservation as br reserved = {"spend:key:hashed-tok", "spend:org:org1"} - monkeypatch.setattr( - br, "get_reserved_counter_keys", MagicMock(return_value=set(reserved)) - ) + monkeypatch.setattr(br, "get_reserved_counter_keys", MagicMock(return_value=set(reserved))) monkeypatch.setattr(br, "reconcile_budget_reservation", AsyncMock()) recorded: dict[str, float] = {} - async def _record_increment(*, key, value, refresh_ttl=True): - recorded[key] = recorded.get(key, 0.0) + value - return recorded[key] + async def _record_pipeline(increment_list, **_): + results = [] + for op in increment_list: + recorded[op["key"]] = recorded.get(op["key"], 0.0) + op["increment_value"] + results.append(recorded[op["key"]]) + return results fake_cache = _make_spend_counter_cache(redis_get_value=None) - fake_cache.redis_cache.async_increment = _record_increment + fake_cache.redis_cache.async_increment_pipeline = AsyncMock(side_effect=_record_pipeline) fake_user_cache = _make_user_api_key_cache(get_value=None) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) reservation = {"finalized": False} await ps.increment_spend_counters( @@ -708,27 +680,46 @@ async def test_increment_spend_counters_failing_scope_propagates_after_siblings_ ): """A failure in one scope must propagate to the caller (so it can invalidate reserved counters) while every other scope still settles rather than being - left as an orphaned background task, and the reservation is not finalized.""" - recorded: dict[str, float] = {} + left as an orphaned background task, and the reservation is not finalized. + The surviving scopes' increments are still applied in the single pipeline: + dropping them would under-count spend, the unsafe direction for budget + enforcement.""" + warmed_keys: list[str] = [] - async def _increment(*, key, value, refresh_ttl=True): + async def _warm_check(*, key, **kwargs): + warmed_keys.append(key) if key == "spend:team:t1": - raise RuntimeError("redis increment failed") - recorded[key] = recorded.get(key, 0.0) + value - return recorded[key] + raise RuntimeError("redis get failed") + return 1.0 - fake_cache = _make_spend_counter_cache(redis_get_value=None) - fake_cache.redis_cache.async_increment = _increment + async def _reseed_fails(*, counter_key, **kwargs): + if counter_key == "spend:team:t1": + raise RuntimeError("reseed failed") + + applied: dict[str, float] = {} + + async def _record_pipeline(increment_list, **_): + results = [] + for op in increment_list: + applied[op["key"]] = op["increment_value"] + results.append(op["increment_value"]) + return results + + fake_cache = _make_spend_counter_cache() + fake_cache.redis_cache.async_get_cache = AsyncMock(side_effect=_warm_check) + fake_cache.redis_cache.async_increment_pipeline = AsyncMock(side_effect=_record_pipeline) fake_user_cache = _make_user_api_key_cache(get_value=None) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) + ps.SpendCounterReseed, + "coalesced", + AsyncMock(side_effect=_reseed_fails), ) reservation = {"finalized": False} - with pytest.raises(RuntimeError, match="redis increment failed"): + with pytest.raises(RuntimeError, match="reseed failed"): await ps.increment_spend_counters( token="hashed-tok", team_id="t1", @@ -741,7 +732,19 @@ async def test_increment_spend_counters_failing_scope_propagates_after_siblings_ ) assert reservation["finalized"] is False - assert recorded == { + # every sibling scope settled (its warm-check ran) before the error propagated + assert set(warmed_keys) == { + "spend:key:hashed-tok", + "spend:team:t1", + "spend:team_member:u1:t1", + "spend:user:u1", + "spend:end_user:eu1", + "spend:tag:a", + "spend:org:org1", + } + # the surviving scopes' increments were still applied, in one pipeline call + fake_cache.redis_cache.async_increment_pipeline.assert_awaited_once() + assert applied == { "spend:key:hashed-tok": 5.0, "spend:team_member:u1:t1": 5.0, "spend:user:u1": 5.0, @@ -749,6 +752,7 @@ async def test_increment_spend_counters_failing_scope_propagates_after_siblings_ "spend:tag:a": 5.0, "spend:org:org1": 5.0, } + fake_cache.redis_cache.async_increment.assert_not_awaited() @pytest.mark.asyncio @@ -772,6 +776,108 @@ async def test_increment_spend_counters_zero_cost_is_noop_finalizes_reservation( assert reservation == {"finalized": True} assert fake_cache.redis_cache.async_increment.called is False + fake_cache.redis_cache.async_increment_pipeline.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_increment_spend_counters_pipelines_all_scopes_in_one_redis_call( + monkeypatch, +): + """Every scope's increment must go out in a single async_increment_pipeline + call, not one INCRBYFLOAT round-trip per scope.""" + counter_cache = ps.DualCache() + fake_redis = AsyncMock() + fake_redis.async_get_cache = AsyncMock(return_value=1.0) # counters warm + + async def _pipeline(increment_list, **_): + return [1.5] * len(increment_list) + + fake_redis.async_increment_pipeline = AsyncMock(side_effect=_pipeline) + fake_redis.async_increment = AsyncMock() + fake_redis.get_ttl = MagicMock(return_value=None) + counter_cache.redis_cache = fake_redis + monkeypatch.setattr(ps, "spend_counter_cache", counter_cache) + monkeypatch.setattr(ps, "user_api_key_cache", ps.DualCache()) + monkeypatch.setattr(ps, "prisma_client", None) + + await ps.increment_spend_counters( + token="hashed", + team_id="team-1", + user_id="user-1", + response_cost=0.5, + org_id="org-1", + end_user_id="eu-1", + tags=["tag-a", "tag-b"], + ) + + fake_redis.async_increment_pipeline.assert_awaited_once() + assert fake_redis.async_increment.await_count == 0 + increment_list = fake_redis.async_increment_pipeline.await_args.kwargs["increment_list"] + expected_keys = { + "spend:key:hashed", + "spend:team:team-1", + "spend:team_member:user-1:team-1", + "spend:user:user-1", + "spend:end_user:eu-1", + "spend:tag:tag-a", + "spend:tag:tag-b", + "spend:org:org-1", + } + assert {op["key"] for op in increment_list} == expected_keys + assert all(op["increment_value"] == 0.5 for op in increment_list) + for key in expected_keys: + assert counter_cache.in_memory_cache.get_cache(key=key) == 1.5 + + +@pytest.mark.asyncio +async def test_increment_spend_counters_pipeline_failure_invalidates_all_counters( + monkeypatch, +): + """A failing pipeline must invalidate every pending counter so the next + request reseeds from the DB (which already holds this request's cost) + instead of trusting a value the write may have partially applied.""" + from redis.exceptions import MaxConnectionsError + + counter_cache = ps.DualCache() + pending_keys = ( + "spend:key:hashed", + "spend:team:team-1", + "spend:team_member:user-1:team-1", + "spend:user:user-1", + "spend:end_user:eu-1", + "spend:tag:tag-a", + "spend:tag:tag-b", + "spend:org:org-1", + ) + for key in pending_keys: + counter_cache.in_memory_cache.set_cache(key=key, value=1.0) + fake_redis = AsyncMock() + fake_redis.async_get_cache = AsyncMock(return_value=1.0) # counters warm + fake_redis.async_increment_pipeline = AsyncMock(side_effect=MaxConnectionsError()) + fake_redis.async_increment = AsyncMock() + fake_redis.async_delete_cache = AsyncMock() + fake_redis.get_ttl = MagicMock(return_value=None) + counter_cache.redis_cache = fake_redis + monkeypatch.setattr(ps, "spend_counter_cache", counter_cache) + monkeypatch.setattr(ps, "user_api_key_cache", ps.DualCache()) + monkeypatch.setattr(ps, "prisma_client", None) + + with pytest.raises(MaxConnectionsError): + await ps.increment_spend_counters( + token="hashed", + team_id="team-1", + user_id="user-1", + response_cost=0.5, + org_id="org-1", + end_user_id="eu-1", + tags=["tag-a", "tag-b"], + ) + + assert fake_redis.async_increment.await_count == 0 + deleted_keys = {call.kwargs["key"] for call in fake_redis.async_delete_cache.await_args_list} + assert deleted_keys == set(pending_keys) + for key in pending_keys: + assert counter_cache.in_memory_cache.get_cache(key=key) is None # --------------------------------------------------------------------------- @@ -781,9 +887,7 @@ async def test_increment_spend_counters_zero_cost_is_noop_finalizes_reservation( @pytest.mark.asyncio async def test_reconcile_budget_reservation_for_counter_update_returns_empty_set_when_none(): - result = await ps._reconcile_budget_reservation_for_counter_update( - budget_reservation=None, response_cost=1.0 - ) + result = await ps._reconcile_budget_reservation_for_counter_update(budget_reservation=None, response_cost=1.0) assert result == set() @@ -818,179 +922,151 @@ async def test_reconcile_budget_reservation_for_counter_update_failure_invalidat # --------------------------------------------------------------------------- -# _increment_end_user_and_tag_spend_counters +# _prepare_end_user_and_tag_spend_increments # --------------------------------------------------------------------------- @pytest.mark.asyncio -async def test_increment_end_user_and_tag_spend_counters_increments_each_unique_tag( +async def test_prepare_end_user_and_tag_spend_increments_returns_each_unique_tag( monkeypatch, ): - fake_cache = _make_spend_counter_cache( - redis_get_value=None, redis_increment_value=3.0 - ) + fake_cache = _make_spend_counter_cache(redis_get_value=1.0) fake_user_cache = _make_user_api_key_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) - await ps._increment_end_user_and_tag_spend_counters( + pending = await ps._prepare_end_user_and_tag_spend_increments( end_user_id="eu1", tags=["a", "b", "a", "", None], response_cost=3.0, reserved_counter_keys=set(), ) - observed = { - "increment_calls": fake_cache.redis_cache.async_increment.call_count, - "in_memory_set_calls": fake_cache.in_memory_cache.set_cache.call_count, - "called": fake_cache.redis_cache.async_increment.called, - } - assert normalize(observed) == { - "increment_calls": 3, - "in_memory_set_calls": 3, - "called": True, + assert {item.counter_key for item in pending} == { + "spend:end_user:eu1", + "spend:tag:a", + "spend:tag:b", } + assert all(item.increment == 3.0 for item in pending) @pytest.mark.asyncio -async def test_increment_end_user_and_tag_spend_counters_no_end_user_no_tags_invalid_input_noop( +async def test_prepare_end_user_and_tag_spend_increments_no_end_user_no_tags_invalid_input_noop( monkeypatch, ): fake_cache = _make_spend_counter_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - await ps._increment_end_user_and_tag_spend_counters( + pending = await ps._prepare_end_user_and_tag_spend_increments( end_user_id=None, tags=None, response_cost=1.0, reserved_counter_keys=set(), ) + assert pending == () assert fake_cache.redis_cache.async_increment.called is False # --------------------------------------------------------------------------- -# _increment_org_spend_counter +# _prepare_org_spend_increment # --------------------------------------------------------------------------- @pytest.mark.asyncio -async def test_increment_org_spend_counter_increments_when_org_present(monkeypatch): - fake_cache = _make_spend_counter_cache( - redis_get_value=None, redis_increment_value=10.0 - ) +async def test_prepare_org_spend_increment_returns_pending_when_org_present(monkeypatch): + fake_cache = _make_spend_counter_cache(redis_get_value=1.0) fake_user_cache = _make_user_api_key_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) - await ps._increment_org_spend_counter( + pending = await ps._prepare_org_spend_increment( org_id="org-1", response_cost=10.0, reserved_counter_keys=set(), ) - observed = { - "increment_called": fake_cache.redis_cache.async_increment.called, - "increment_calls": fake_cache.redis_cache.async_increment.call_count, - "counter_key_arg": fake_cache.redis_cache.async_increment.call_args.kwargs[ - "key" - ], - } - assert normalize(observed) == { - "increment_called": True, - "increment_calls": 1, - "counter_key_arg": "spend:org:org-1", - } + assert len(pending) == 1 + assert pending[0].counter_key == "spend:org:org-1" + assert pending[0].increment == 10.0 @pytest.mark.asyncio -async def test_increment_org_spend_counter_no_org_is_noop_invalid_id(monkeypatch): +async def test_prepare_org_spend_increment_no_org_is_noop_invalid_id(monkeypatch): fake_cache = _make_spend_counter_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - await ps._increment_org_spend_counter( + pending = await ps._prepare_org_spend_increment( org_id=None, response_cost=1.0, reserved_counter_keys=set(), ) + assert pending == () assert fake_cache.redis_cache.async_increment.called is False # --------------------------------------------------------------------------- -# _init_and_increment_unreserved_spend_counter +# _prepare_unreserved_spend_counter_increment # --------------------------------------------------------------------------- @pytest.mark.asyncio -async def test_init_and_increment_unreserved_spend_counter_skips_reserved_keys( +async def test_prepare_unreserved_spend_counter_increment_skips_reserved_keys( monkeypatch, ): fake_cache = _make_spend_counter_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - await ps._init_and_increment_unreserved_spend_counter( + pending = await ps._prepare_unreserved_spend_counter_increment( counter_key="spend:tag:x", source_cache_key="tag:x", increment=1.0, reserved_counter_keys={"spend:tag:x"}, ) + assert pending is None assert fake_cache.redis_cache.async_increment.called is False @pytest.mark.asyncio -async def test_init_and_increment_unreserved_spend_counter_proceeds_when_not_reserved( +async def test_prepare_unreserved_spend_counter_increment_proceeds_when_not_reserved( monkeypatch, ): - fake_cache = _make_spend_counter_cache( - redis_get_value=None, redis_increment_value=2.0 - ) + fake_cache = _make_spend_counter_cache(redis_get_value=None) fake_user_cache = _make_user_api_key_cache() + reseed = AsyncMock(return_value=None) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", reseed) - await ps._init_and_increment_unreserved_spend_counter( + pending = await ps._prepare_unreserved_spend_counter_increment( counter_key="spend:tag:y", source_cache_key="tag:y", increment=2.0, reserved_counter_keys=set(), ) - observed = { - "increment_called": fake_cache.redis_cache.async_increment.called, - "redis_get_called": fake_cache.redis_cache.async_get_cache.called, - "reseed_consulted": True, - } - assert observed == { - "increment_called": True, - "redis_get_called": True, - "reseed_consulted": True, - } + assert pending is not None + assert pending.counter_key == "spend:tag:y" + assert pending.increment == 2.0 + assert fake_cache.redis_cache.async_get_cache.called is True + assert reseed.called is True # --------------------------------------------------------------------------- -# _init_and_increment_spend_counter +# _prepare_spend_counter_increment # --------------------------------------------------------------------------- @pytest.mark.asyncio -async def test_init_and_increment_spend_counter_warm_cache_skips_reseed(monkeypatch): - fake_cache = _make_spend_counter_cache( - redis_get_value=11.0, redis_increment_value=14.0 - ) +async def test_prepare_spend_counter_increment_warm_cache_skips_reseed(monkeypatch): + fake_cache = _make_spend_counter_cache(redis_get_value=11.0) fake_user_cache = _make_user_api_key_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) @@ -998,12 +1074,14 @@ async def test_init_and_increment_spend_counter_warm_cache_skips_reseed(monkeypa reseed = AsyncMock(return_value=None) monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", reseed) - await ps._init_and_increment_spend_counter( + pending = await ps._prepare_spend_counter_increment( counter_key="spend:key:k", source_cache_key="k", increment=3.0, ) + assert pending.counter_key == "spend:key:k" + assert pending.increment == 3.0 observed = { "reseed_called": reseed.called, "increment_called": fake_cache.redis_cache.async_increment.called, @@ -1011,23 +1089,21 @@ async def test_init_and_increment_spend_counter_warm_cache_skips_reseed(monkeypa } assert normalize(observed) == { "reseed_called": False, - "increment_called": True, + "increment_called": False, "in_memory_seeded_from_redis": True, } # --------------------------------------------------------------------------- -# _init_and_increment_window_spend_counter +# _prepare_window_spend_counter_increment # --------------------------------------------------------------------------- @pytest.mark.asyncio -async def test_init_and_increment_window_spend_counter_increments_when_initialized( +async def test_prepare_window_spend_counter_increment_returns_pending_when_initialized( monkeypatch, ): - fake_cache = _make_spend_counter_cache( - redis_get_value=0.0, redis_increment_value=5.0 - ) + fake_cache = _make_spend_counter_cache(redis_get_value=0.0) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "prisma_client", None) monkeypatch.setattr( @@ -1036,7 +1112,7 @@ async def test_init_and_increment_window_spend_counter_increments_when_initializ AsyncMock(return_value=0.0), ) - await ps._init_and_increment_window_spend_counter( + pending = await ps._prepare_window_spend_counter_increment( counter_key="spend:key:k:window:1d", entity_type="Key", entity_id="k", @@ -1045,26 +1121,19 @@ async def test_init_and_increment_window_spend_counter_increments_when_initializ increment=5.0, ) - observed = { - "redis_increment_called": fake_cache.redis_cache.async_increment.called, - "increment_calls": fake_cache.redis_cache.async_increment.call_count, - "in_memory_set_calls": fake_cache.in_memory_cache.set_cache.call_count, - } - assert normalize(observed) == { - "redis_increment_called": True, - "increment_calls": 1, - "in_memory_set_calls": 2, - } + assert pending is not None + assert pending.counter_key == "spend:key:k:window:1d" + assert pending.increment == 5.0 @pytest.mark.asyncio -async def test_init_and_increment_window_spend_counter_missing_window_start_invalid_skips( +async def test_prepare_window_spend_counter_increment_missing_window_start_invalid_skips( monkeypatch, ): fake_cache = _make_spend_counter_cache() monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - await ps._init_and_increment_window_spend_counter( + pending = await ps._prepare_window_spend_counter_increment( counter_key="spend:key:k:window:1d", entity_type="Key", entity_id="k", @@ -1073,6 +1142,7 @@ async def test_init_and_increment_window_spend_counter_missing_window_start_inva increment=5.0, ) + assert pending is None assert fake_cache.redis_cache.async_increment.called is False @@ -1114,16 +1184,12 @@ async def test_ensure_spend_counter_initialized_warm_skips_reseed_and_source( async def test_ensure_spend_counter_initialized_cold_seeds_from_source_cache( monkeypatch, ): - fake_cache = _make_spend_counter_cache( - redis_get_value=None, redis_increment_value=7.0 - ) + fake_cache = _make_spend_counter_cache(redis_get_value=None, redis_increment_value=7.0) fake_user_cache = _make_user_api_key_cache(get_value={"spend": 7.0}) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) monkeypatch.setattr(ps, "prisma_client", None) - monkeypatch.setattr( - ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None) - ) + monkeypatch.setattr(ps.SpendCounterReseed, "coalesced", AsyncMock(return_value=None)) await ps._ensure_spend_counter_initialized( counter_key="spend:user:u", @@ -1163,9 +1229,7 @@ async def test_get_source_cache_base_spend_reads_first_hit_from_list(monkeypatch fake_user_cache.async_get_cache = AsyncMock(side_effect=_get) monkeypatch.setattr(ps, "user_api_key_cache", fake_user_cache) - result = await ps._get_source_cache_base_spend( - source_cache_key=["miss", "hit-obj", "miss2"] - ) + result = await ps._get_source_cache_base_spend(source_cache_key=["miss", "hit-obj", "miss2"]) observed = { "result": result, @@ -1294,9 +1358,7 @@ async def test_increment_spend_counter_cache_redis_path_returns_new_value(monkey fake_cache = _make_spend_counter_cache(redis_increment_value=44.0) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) - result = await ps._increment_spend_counter_cache( - counter_key="spend:key:k", increment=4.0 - ) + result = await ps._increment_spend_counter_cache(counter_key="spend:key:k", increment=4.0) observed = { "result": result, @@ -1314,15 +1376,11 @@ async def test_increment_spend_counter_cache_redis_path_returns_new_value(monkey async def test_increment_spend_counter_cache_redis_error_raises_and_invalidates( monkeypatch, ): - fake_cache = _make_spend_counter_cache( - redis_increment_side_effect=RuntimeError("incr fail") - ) + fake_cache = _make_spend_counter_cache(redis_increment_side_effect=RuntimeError("incr fail")) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) with pytest.raises(RuntimeError): - await ps._increment_spend_counter_cache( - counter_key="spend:key:k", increment=1.0 - ) + await ps._increment_spend_counter_cache(counter_key="spend:key:k", increment=1.0) assert fake_cache.in_memory_cache.delete_cache.called is True assert fake_cache.redis_cache.async_delete_cache.called is True @@ -1343,9 +1401,7 @@ async def test_invalidate_spend_counter_deletes_in_memory_and_redis(monkeypatch) observed = { "in_memory_delete_called": fake_cache.in_memory_cache.delete_cache.called, "redis_delete_called": fake_cache.redis_cache.async_delete_cache.called, - "delete_args_key": fake_cache.redis_cache.async_delete_cache.call_args.kwargs[ - "key" - ], + "delete_args_key": fake_cache.redis_cache.async_delete_cache.call_args.kwargs["key"], } assert normalize(observed) == { "in_memory_delete_called": True, @@ -1357,9 +1413,7 @@ async def test_invalidate_spend_counter_deletes_in_memory_and_redis(monkeypatch) @pytest.mark.asyncio async def test_invalidate_spend_counter_swallows_redis_failure_no_raise(monkeypatch): fake_cache = _make_spend_counter_cache() - fake_cache.redis_cache.async_delete_cache = AsyncMock( - side_effect=RuntimeError("redis down") - ) + fake_cache.redis_cache.async_delete_cache = AsyncMock(side_effect=RuntimeError("redis down")) monkeypatch.setattr(ps, "spend_counter_cache", fake_cache) await ps._invalidate_spend_counter(counter_key="spend:key:k") diff --git a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation_redis_failure.py b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation_redis_failure.py index c123eeeed36..6165af4920d 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation_redis_failure.py +++ b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation_redis_failure.py @@ -41,6 +41,15 @@ class _FlakyRedisCache: self._store[key] = float(value) return True + async def async_increment_pipeline(self, increment_list, **kwargs): + results = [] + for op in increment_list: + results.append(await self.async_increment(op["key"], op["increment_value"])) + return results + + def get_ttl(self, **kwargs): + return None + @pytest.mark.asyncio async def test_direct_increment_runs_when_reservation_reconcile_hits_redis_failure( diff --git a/tests/test_litellm/proxy/test_budget_reservation.py b/tests/test_litellm/proxy/test_budget_reservation.py index 6dab054d8ea..40ebc03781c 100644 --- a/tests/test_litellm/proxy/test_budget_reservation.py +++ b/tests/test_litellm/proxy/test_budget_reservation.py @@ -2220,6 +2220,15 @@ class _ExpiringRedisCache: async def async_delete_cache(self, key: str, *args: object, **kwargs: object) -> None: self.store.pop(key, None) + async def async_increment_pipeline(self, increment_list, **kwargs): + results = [] + for op in increment_list: + results.append(await self.async_increment(op["key"], op["increment_value"])) + return results + + def get_ttl(self, **kwargs) -> None: + return None + @pytest.mark.asyncio async def test_reconcile_after_redis_counter_expiry_keeps_request_cost_enforced( diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 54579e6cb7c..b0f38978727 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -7749,7 +7749,7 @@ async def test_increment_spend_counters_team_and_member(): @pytest.mark.asyncio -async def test_init_and_increment_spend_counter_reseeds_from_db_on_counter_miss(): +async def test_prepare_spend_counter_increment_reseeds_from_db_on_counter_miss(): """When the Redis counter is missing, the reseed path reads the authoritative spend from the DB (not a stale cache), so the next increment continues from the correct base value.""" @@ -7762,8 +7762,17 @@ async def test_init_and_increment_spend_counter_reseeds_from_db_on_counter_miss( recorded_increments.append({"key": key, "value": value, "ttl": ttl}) return value + async def record_pipeline(increment_list, **kwargs): + results = [] + for op in increment_list: + await record_increment(key=op["key"], value=op["increment_value"], ttl=op["ttl"]) + results.append(op["increment_value"]) + return results + fake_redis = AsyncMock() fake_redis.async_increment = AsyncMock(side_effect=record_increment) + fake_redis.async_increment_pipeline = AsyncMock(side_effect=record_pipeline) + fake_redis.get_ttl = MagicMock(return_value=None) fake_redis.async_get_cache = AsyncMock(return_value=None) # counter missing fake_redis.async_set_cache = AsyncMock(return_value=True) # SET NX wins counter_cache.redis_cache = fake_redis @@ -7782,7 +7791,10 @@ async def test_init_and_increment_spend_counter_reseeds_from_db_on_counter_miss( stale_cache.in_memory_cache.set_cache(key="team_id:team-9", value=stale_team) import litellm.proxy.proxy_server as ps - from litellm.proxy.proxy_server import _init_and_increment_spend_counter + from litellm.proxy.proxy_server import ( + _apply_spend_counter_increments, + _prepare_spend_counter_increment, + ) orig_user, orig_counter, orig_prisma = ( ps.user_api_key_cache, @@ -7793,11 +7805,12 @@ async def test_init_and_increment_spend_counter_reseeds_from_db_on_counter_miss( ps.spend_counter_cache = counter_cache ps.prisma_client = fake_prisma try: - await _init_and_increment_spend_counter( + pending = await _prepare_spend_counter_increment( counter_key="spend:team:team-9", source_cache_key="team_id:team-9", increment=1.5, ) + await _apply_spend_counter_increments(pending=(pending,)) fake_prisma.db.litellm_teamtable.find_unique.assert_awaited_once_with(where={"team_id": "team-9"}) # Seed uses SET NX with db_spend (42) — cross-pod safe, no INCR of 42. @@ -7976,7 +7989,10 @@ async def test_reseed_spend_from_db_skips_window_variant_keys(): @pytest.mark.asyncio async def test_window_spend_counter_reseeds_from_spend_logs_on_counter_miss(): from litellm.caching.dual_cache import DualCache - from litellm.proxy.proxy_server import _init_and_increment_window_spend_counter + from litellm.proxy.proxy_server import ( + _apply_spend_counter_increments, + _prepare_window_spend_counter_increment, + ) counter_cache = DualCache() window_start = datetime.now(timezone.utc) - timedelta(hours=1) @@ -7992,7 +8008,7 @@ async def test_window_spend_counter_reseeds_from_spend_logs_on_counter_miss(): ps.spend_counter_cache = counter_cache ps.prisma_client = fake_prisma try: - await _init_and_increment_window_spend_counter( + pending = await _prepare_window_spend_counter_increment( counter_key="spend:key:key-window:window:1h", entity_type="Key", entity_id="key-window", @@ -8000,6 +8016,7 @@ async def test_window_spend_counter_reseeds_from_spend_logs_on_counter_miss(): window_start=window_start, increment=0.5, ) + await _apply_spend_counter_increments(pending=(pending,) if pending is not None else ()) fake_prisma.db.litellm_spendlogs.group_by.assert_awaited_once_with( by=["api_key"], @@ -8015,7 +8032,10 @@ async def test_window_spend_counter_reseeds_from_spend_logs_on_counter_miss(): @pytest.mark.asyncio async def test_init_spend_counter_redis_clean_miss_skips_stale_in_memory(): from litellm.caching.dual_cache import DualCache - from litellm.proxy.proxy_server import _init_and_increment_spend_counter + from litellm.proxy.proxy_server import ( + _apply_spend_counter_increments, + _prepare_spend_counter_increment, + ) counter_cache = DualCache() counter_key = "spend:team:team-stale-local" @@ -8037,6 +8057,15 @@ async def test_init_spend_counter_redis_clean_miss_skips_stale_in_memory(): fake_redis.async_get_cache = AsyncMock(return_value=None) fake_redis.async_increment = AsyncMock(side_effect=redis_increment) fake_redis.async_set_cache = AsyncMock(side_effect=redis_set_cache) + + async def redis_increment_pipeline(increment_list, **_): + results = [] + for op in increment_list: + results.append(await redis_increment(key=op["key"], value=op["increment_value"])) + return results + + fake_redis.async_increment_pipeline = AsyncMock(side_effect=redis_increment_pipeline) + fake_redis.get_ttl = MagicMock(return_value=None) counter_cache.redis_cache = fake_redis db_row = MagicMock() @@ -8055,11 +8084,12 @@ async def test_init_spend_counter_redis_clean_miss_skips_stale_in_memory(): ps.prisma_client = fake_prisma ps.user_api_key_cache = DualCache() try: - await _init_and_increment_spend_counter( + pending = await _prepare_spend_counter_increment( counter_key=counter_key, source_cache_key="team_id:team-stale-local", increment=1.5, ) + await _apply_spend_counter_increments(pending=(pending,)) fake_prisma.db.litellm_teamtable.find_unique.assert_awaited_once_with(where={"team_id": "team-stale-local"}) # Seed via SET NX (42) + delta via INCRBYFLOAT (1.5) = 43.5. @@ -8074,7 +8104,10 @@ async def test_init_spend_counter_redis_clean_miss_skips_stale_in_memory(): @pytest.mark.asyncio async def test_window_spend_counter_redis_clean_miss_skips_stale_in_memory(): from litellm.caching.dual_cache import DualCache - from litellm.proxy.proxy_server import _init_and_increment_window_spend_counter + from litellm.proxy.proxy_server import ( + _apply_spend_counter_increments, + _prepare_window_spend_counter_increment, + ) counter_cache = DualCache() counter_key = "spend:key:key-window-stale-local:window:1h" @@ -8097,6 +8130,15 @@ async def test_window_spend_counter_redis_clean_miss_skips_stale_in_memory(): fake_redis.async_get_cache = AsyncMock(return_value=None) fake_redis.async_set_cache = AsyncMock(side_effect=redis_set_cache) fake_redis.async_increment = AsyncMock(side_effect=redis_increment) + + async def redis_increment_pipeline(increment_list, **_): + results = [] + for op in increment_list: + results.append(await redis_increment(key=op["key"], value=op["increment_value"])) + return results + + fake_redis.async_increment_pipeline = AsyncMock(side_effect=redis_increment_pipeline) + fake_redis.get_ttl = MagicMock(return_value=None) counter_cache.redis_cache = fake_redis fake_prisma = MagicMock() @@ -8111,7 +8153,7 @@ async def test_window_spend_counter_redis_clean_miss_skips_stale_in_memory(): ps.spend_counter_cache = counter_cache ps.prisma_client = fake_prisma try: - await _init_and_increment_window_spend_counter( + pending = await _prepare_window_spend_counter_increment( counter_key=counter_key, entity_type="Key", entity_id="key-window-stale-local", @@ -8119,6 +8161,7 @@ async def test_window_spend_counter_redis_clean_miss_skips_stale_in_memory(): window_start=window_start, increment=0.5, ) + await _apply_spend_counter_increments(pending=(pending,) if pending is not None else ()) fake_prisma.db.litellm_spendlogs.group_by.assert_awaited_once_with( by=["api_key"], @@ -8138,7 +8181,10 @@ async def test_window_spend_counter_redis_clean_miss_skips_stale_in_memory(): @pytest.mark.asyncio async def test_window_spend_counter_redis_concurrent_seed_does_not_double_seed(): from litellm.caching.dual_cache import DualCache - from litellm.proxy.proxy_server import _init_and_increment_window_spend_counter + from litellm.proxy.proxy_server import ( + _apply_spend_counter_increments, + _prepare_window_spend_counter_increment, + ) counter_cache = DualCache() counter_key = "spend:key:key-window-concurrent-seed:window:1h" @@ -8161,6 +8207,15 @@ async def test_window_spend_counter_redis_concurrent_seed_does_not_double_seed() fake_redis.async_get_cache = AsyncMock(side_effect=redis_get_cache) fake_redis.async_set_cache = AsyncMock(return_value=False) fake_redis.async_increment = AsyncMock(side_effect=redis_increment) + + async def redis_increment_pipeline(increment_list, **_): + results = [] + for op in increment_list: + results.append(await redis_increment(key=op["key"], value=op["increment_value"])) + return results + + fake_redis.async_increment_pipeline = AsyncMock(side_effect=redis_increment_pipeline) + fake_redis.get_ttl = MagicMock(return_value=None) counter_cache.redis_cache = fake_redis fake_prisma = MagicMock() @@ -8175,7 +8230,7 @@ async def test_window_spend_counter_redis_concurrent_seed_does_not_double_seed() ps.spend_counter_cache = counter_cache ps.prisma_client = fake_prisma try: - await _init_and_increment_window_spend_counter( + pending = await _prepare_window_spend_counter_increment( counter_key=counter_key, entity_type="Key", entity_id="key-window-concurrent-seed", @@ -8183,6 +8238,7 @@ async def test_window_spend_counter_redis_concurrent_seed_does_not_double_seed() window_start=window_start, increment=0.5, ) + await _apply_spend_counter_increments(pending=(pending,) if pending is not None else ()) fake_redis.async_set_cache.assert_awaited_once_with( key=counter_key, @@ -8199,7 +8255,7 @@ async def test_window_spend_counter_redis_concurrent_seed_does_not_double_seed() @pytest.mark.asyncio async def test_window_spend_counter_skips_invalid_window_start(): from litellm.caching.dual_cache import DualCache - from litellm.proxy.proxy_server import _init_and_increment_window_spend_counter + from litellm.proxy.proxy_server import _prepare_window_spend_counter_increment counter_cache = DualCache() @@ -8208,7 +8264,7 @@ async def test_window_spend_counter_skips_invalid_window_start(): orig_counter = ps.spend_counter_cache ps.spend_counter_cache = counter_cache try: - await _init_and_increment_window_spend_counter( + pending = await _prepare_window_spend_counter_increment( counter_key="spend:key:key-invalid-window:window:not-a-duration", entity_type="Key", entity_id="key-invalid-window", @@ -8216,6 +8272,7 @@ async def test_window_spend_counter_skips_invalid_window_start(): window_start=None, increment=0.5, ) + assert pending is None assert counter_cache.in_memory_cache.get_cache(key="spend:key:key-invalid-window:window:not-a-duration") is None finally: @@ -8279,6 +8336,9 @@ async def test_increment_spend_counters_finalizes_after_unreserved_increments(): async def assert_reservation_not_finalized_yet(**kwargs): assert budget_reservation["finalized"] is False incremented_counters.append(kwargs["counter_key"]) + return ps._PendingSpendIncrement( + counter_key=kwargs["counter_key"], increment=kwargs["increment"] + ) import litellm.proxy.proxy_server as ps @@ -8287,7 +8347,7 @@ async def test_increment_spend_counters_finalizes_after_unreserved_increments(): ps.user_api_key_cache = DualCache() try: with patch( - "litellm.proxy.proxy_server._init_and_increment_spend_counter", + "litellm.proxy.proxy_server._prepare_spend_counter_increment", new=AsyncMock(side_effect=assert_reservation_not_finalized_yet), ): await increment_spend_counters( @@ -8620,7 +8680,7 @@ async def test_get_current_spend_uses_db_zero_over_stale_fallback(): async def test_concurrent_read_and_write_paths_share_one_db_query(): """ The read path (`get_current_spend`) and the write path - (`_init_and_increment_spend_counter`) both reseed cold counters from + (`_prepare_spend_counter_increment`) both reseed cold counters from the DB. They must share the per-counter lock so a concurrent pre-call enforcement read and post-call increment for the same counter collapse to one DB query, not two. @@ -8629,7 +8689,7 @@ async def test_concurrent_read_and_write_paths_share_one_db_query(): from litellm.caching.dual_cache import DualCache from litellm.proxy.proxy_server import ( - _init_and_increment_spend_counter, + _prepare_spend_counter_increment, get_current_spend, ) @@ -8683,7 +8743,7 @@ async def test_concurrent_read_and_write_paths_share_one_db_query(): try: results = await _asyncio.gather( get_current_spend(counter_key=counter_key, fallback_spend=0.0), - _init_and_increment_spend_counter( + _prepare_spend_counter_increment( counter_key=counter_key, source_cache_key="ignored", increment=1.5, From 7c6e33ef70889445fab5650b1490ddf3c0448a11 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:38:26 -0700 Subject: [PATCH 098/105] fix(proxy): ignore team_id="" on /key/update so team-less keys can be updated and imported (#40421) Co-authored-by: yassin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/_types.py | 8 ++++++ .../test_key_management_endpoints.py | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index df7ee8f508c..0e2fd50ac9f 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3,6 +3,7 @@ import json import os from collections.abc import Callable, Mapping from datetime import datetime +from types import MappingProxyType from typing import TYPE_CHECKING, Any, Final, Literal, NamedTuple import httpx @@ -1294,6 +1295,13 @@ class UpdateKeyRequest(KeyRequestBase): rotation_interval: str | None = None organization_id: str | None = None + @model_validator(mode="before") + @classmethod + def drop_blank_team_id(cls, values: object) -> object: + if isinstance(values, Mapping) and values.get("team_id") == "": + return MappingProxyType({k: v for k, v in values.items() if k != "team_id"}) + return values + @field_validator("organization_id", mode="before") @classmethod def treat_cleared_organization_id_as_unset(cls, v: object) -> object: 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 a873a367eab..d2aeba18f7d 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 @@ -17975,6 +17975,32 @@ def test_key_request_blank_organization_id_is_unset(): assert UpdateKeyRequest(key="sk-1", organization_id="org-1").organization_id == "org-1" +def test_update_key_request_blank_team_id_is_not_a_team_change(): + from litellm.proxy._types import UpdateKeyRequest + from litellm.proxy.management_endpoints.key_management_endpoints import ( + is_different_team, + ) + + blank = UpdateKeyRequest(key="sk-1", team_id="", key_alias="renamed") + assert blank.team_id is None + assert "team_id" not in blank.model_dump(exclude_unset=True) + assert blank.model_dump(exclude_unset=True) == {"key": "sk-1", "key_alias": "renamed"} + assert is_different_team(data=blank, existing_key_row=LiteLLM_VerificationToken(token="hashed")) is False + assert ( + is_different_team(data=blank, existing_key_row=LiteLLM_VerificationToken(token="hashed", team_id="team-1")) + is False + ) + assert "team_id" in UpdateKeyRequest(key="sk-1", team_id=None).model_dump(exclude_unset=True) + assert UpdateKeyRequest(key="sk-1", team_id="team-1").team_id == "team-1" + assert ( + is_different_team( + data=UpdateKeyRequest(key="sk-1", team_id="team-1"), + existing_key_row=LiteLLM_VerificationToken(token="hashed"), + ) + is True + ) + + def test_key_generation_check_blank_team_id_uses_personal_permissions(monkeypatch): """key_generation_check with team_id="" must take the personal-key path instead of failing the team lookup with "Unable to find team object" (LIT-3925).""" From 699ae63b2a3270ab8b1068f8beba4a3bf71543b9 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 10 Sep 2026 01:47:34 +0800 Subject: [PATCH 099/105] feat(router): support percentile-based TTFT routing (#40352) * feat(router): support percentile-based TTFT routing * fix(router): apply routing_strategy_args updates to the live selector Runtime routing_strategy_args updates (config reload, update_settings) only rebuilt the strategy selector when routing_strategy itself changed, so a newly added ttft_percentile sat unused until the proxy restarted. Also drops a comment that only restated the code it sat above. Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB * refactor(router): drop unreachable empty-samples guard in percentile latency _percentile_latency is only called behind use_ttft, which already requires a non-empty ttft sample list, so the early return was dead code and the one line Codecov flagged as uncovered on this patch. Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB * test(router): cover the no-selector path of a routing_strategy_args update simple-shuffle has no selector attribute to re-link, so the early return guards a setattr with a None attribute name. Dropping the guard makes the new test fail with "attribute name must be string, not 'NoneType'". Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB * fix(test): assert ValidationError on out-of-range ttft_percentile pytest.raises(ValueError) tripped PT011 for being too broad. Pydantic raises ValidationError for the gt/le constraint, so naming it satisfies the rule and pins the assertion to the constraint under test. Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB * fix(router): drop Final from a per-deployment loop variable basedpyright rejects "A Final variable cannot be assigned within a loop", which pushed reportGeneralTypeIssues one over its budget. selected_latency is rebound each iteration, so it matches its unannotated neighbours in the same loop. Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB * test(router): exempt _apply_updated_routing_strategy_args from the name scan The scan only reads test files with "router" in the filename, so it cannot see the update_settings tests in router_strategy/test_lowest_latency.py. Calling the private helper directly would test structure rather than behaviour, so it joins the existing entries ignored for the same reason. Claude-Session: https://claude.ai/code/session_01PmqjhFYcUh6vA72d8W9gdB --- litellm/router.py | 47 ++++- litellm/router_strategy/lowest_latency.py | 21 ++- .../router_code_coverage.py | 1 + .../router_strategy/test_lowest_latency.py | 174 +++++++++++++++++- 4 files changed, 225 insertions(+), 18 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 934a4ac86a9..2fbdf541487 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -1317,6 +1317,43 @@ class Router: if isinstance(litellm.input_callback, list): litellm.input_callback = [c for c in litellm.input_callback if id(c) not in selector_ids] + def _apply_updated_routing_strategy_args(self) -> None: + """ + Re-link the default group's selector to the current `routing_strategy_args`. + + Selectors freeze their `RoutingArgs` at construction, so a runtime args + update would otherwise keep serving the boot-time values until restart. + Latency/usage state survives the rebuild: it lives in the shared router + cache, not on the selector. + """ + strategy: Final = self._normalize_strategy(self.routing_strategy) + if strategy == "lar1": + from litellm.router_strategy.lar1_routing import apply_lar1_routing_strategy + + apply_lar1_routing_strategy(self, self.routing_strategy_args) + return + + attr: Final = self._DEFAULT_SELECTOR_ATTR_BY_STRATEGY.get(strategy or "") + current: Final = getattr(self, attr, None) if attr is not None else None + if attr is None or current is None: + return + + try: + rebuilt: Final = self._build_strategy_selector( + strategy=strategy or "", + routing_strategy_args=self.routing_strategy_args, + ) + except (TypeError, ValidationError): + verbose_router_logger.exception( + "Invalid routing_strategy_args %s for '%s'; keeping the previous ones", + self.routing_strategy_args, + strategy, + ) + return + + self._unregister_router_selectors((current,)) + setattr(self, attr, rebuilt) + def routing_strategy_init(self, routing_strategy: RoutingStrategy | str, routing_strategy_args: dict): verbose_router_logger.info("Routing strategy: %s", routing_strategy) self._validate_routing_strategy(routing_strategy) @@ -11847,7 +11884,7 @@ class Router: _existing_router_settings: Final = self.get_settings() rebuild_routing_groups = False - relink_lar1_from_args = False + routing_args_updated = False for var in kwargs: if var in RUNTIME_UPDATABLE_ROUTER_SETTINGS: if var in _int_settings: @@ -11886,15 +11923,13 @@ class Router: ) rebuild_routing_groups = True elif var == "routing_strategy_args": - relink_lar1_from_args = True + routing_args_updated = True setattr(self, var, value) else: verbose_router_logger.debug("Setting %s is not allowed", var) - if relink_lar1_from_args and self._normalize_strategy(self.routing_strategy) == "lar1": - from litellm.router_strategy.lar1_routing import apply_lar1_routing_strategy - - apply_lar1_routing_strategy(self, self.routing_strategy_args) + if routing_args_updated: + self._apply_updated_routing_strategy_args() if rebuild_routing_groups: self._init_routing_groups(self._routing_groups_input) diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index 805d4ff9080..e902192811c 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -3,8 +3,11 @@ import random from collections.abc import Sequence from datetime import datetime, timedelta +from math import ceil from typing import TYPE_CHECKING, Any, Final +from pydantic import Field + import litellm from litellm import ModelResponse, token_counter, verbose_logger from litellm.caching.caching import DualCache @@ -24,6 +27,7 @@ class RoutingArgs(LiteLLMPydanticObjectBase): ttl: float = 1 * 60 * 60 # 1 hour lowest_latency_buffer: float = 0 max_latency_list_size: int = 10 + ttft_percentile: float | None = Field(default=None, gt=0, le=1) def _average_latency(samples: Sequence[float]) -> float: @@ -32,6 +36,12 @@ def _average_latency(samples: Sequence[float]) -> float: return sum(samples) / len(samples) +def _percentile_latency(samples: Sequence[float], percentile: float) -> float: + values: Final = sorted(samples) + index: Final = ceil(len(values) * percentile) - 1 + return values[index] + + def _ttft_seconds(elapsed: timedelta | float) -> float: if isinstance(elapsed, timedelta): return elapsed.total_seconds() @@ -427,14 +437,17 @@ class LowestLatencyLoggingHandler(CustomLogger): item_rpm = item_map.get(precise_minute, {}).get("rpm", 0) item_tpm = item_map.get(precise_minute, {}).get("tpm", 0) - # get average latency or average ttft (depending on streaming/non-streaming) use_ttft = ( request_kwargs is not None and request_kwargs.get("stream", None) is not None and request_kwargs["stream"] is True and len(item_ttft_latency) > 0 ) - average_latency = _average_latency(item_ttft_latency if use_ttft else item_latency) + selected_latency = ( + _percentile_latency(item_ttft_latency, self.routing_args.ttft_percentile) + if use_ttft and self.routing_args.ttft_percentile is not None + else _average_latency(item_ttft_latency if use_ttft else item_latency) + ) # -------------- # # Debugging Logic @@ -443,7 +456,7 @@ class LowestLatencyLoggingHandler(CustomLogger): # this helps a user to debug why the router picked a specfic deployment # _deployment_api_base = _deployment.get("litellm_params", {}).get("api_base", "") if _deployment_api_base is not None: - _latency_per_deployment[_deployment_api_base] = average_latency + _latency_per_deployment[_deployment_api_base] = selected_latency # -------------- # # End of Debugging Logic # -------------- # @@ -453,7 +466,7 @@ class LowestLatencyLoggingHandler(CustomLogger): ): # if user passed in tpm / rpm in the model_list continue else: - potential_deployments.append((_deployment, average_latency)) + potential_deployments.append((_deployment, selected_latency)) if len(potential_deployments) == 0: return None diff --git a/tests/code_coverage_tests/router_code_coverage.py b/tests/code_coverage_tests/router_code_coverage.py index 0af29f069c6..a11f015743b 100644 --- a/tests/code_coverage_tests/router_code_coverage.py +++ b/tests/code_coverage_tests/router_code_coverage.py @@ -87,6 +87,7 @@ ignored_function_names = [ "_delete_claude_code_session_router_binding", # Tested through Redis cleanup failure in test_router.py "_resolve_claude_code_session_router", # Tested through Claude Code session routing in test_router.py "_get_claude_code_session_router_binding", # Tested through the two-worker session routing test in test_router.py + "_apply_updated_routing_strategy_args", # Tested via update_settings in test_lowest_latency.py (file lacks "router" in name) ] diff --git a/tests/test_litellm/router_strategy/test_lowest_latency.py b/tests/test_litellm/router_strategy/test_lowest_latency.py index 812d7bbff32..1a8614e3fca 100644 --- a/tests/test_litellm/router_strategy/test_lowest_latency.py +++ b/tests/test_litellm/router_strategy/test_lowest_latency.py @@ -8,11 +8,12 @@ import json from datetime import datetime, timedelta import pytest - +from pydantic import ValidationError import litellm from litellm.caching.caching import DualCache -from litellm.router_strategy.lowest_latency import LowestLatencyLoggingHandler +from litellm.router import Router +from litellm.router_strategy.lowest_latency import LowestLatencyLoggingHandler, RoutingArgs DEPLOYMENT_ID = "9876" KWARGS = { @@ -58,9 +59,9 @@ def test_sync_embedding_latency_is_json_serializable(): latencies = _recorded_latencies(cache) assert latencies, "expected a latency entry to be recorded" - assert all( - not isinstance(value, timedelta) for value in latencies - ), f"raw timedelta leaked into latency list: {latencies}" + assert all(not isinstance(value, timedelta) for value in latencies), ( + f"raw timedelta leaked into latency list: {latencies}" + ) assert latencies[-1] == pytest.approx(2.0) # the exact failure mode from production: redis cache sync json.dumps json.dumps({"latency": latencies}) @@ -84,9 +85,9 @@ async def test_async_embedding_latency_is_json_serializable(): latencies = _recorded_latencies(cache) assert latencies, "expected a latency entry to be recorded" - assert all( - not isinstance(value, timedelta) for value in latencies - ), f"raw timedelta leaked into latency list: {latencies}" + assert all(not isinstance(value, timedelta) for value in latencies), ( + f"raw timedelta leaked into latency list: {latencies}" + ) assert latencies[-1] == pytest.approx(3.0) json.dumps({"latency": latencies}) @@ -292,6 +293,85 @@ async def test_streaming_routing_ignores_per_token_ttft_samples_from_older_worke assert picked["model_info"]["id"] == FAST_TTFT_ID +@pytest.mark.asyncio +@pytest.mark.parametrize("sync_mode", [True, False], ids=["sync", "async"]) +@pytest.mark.parametrize( + ("ttft_percentile", "first_samples", "second_samples", "expected_id"), + [ + (None, [0.1, 0.1, 1.0], [0.3, 0.3, 0.3], SLOW_TTFT_ID), + (0.5, [0.1, 0.1, 1.0], [0.3, 0.3, 0.3], FAST_TTFT_ID), + (0.9, [0.1, 0.1, 0.1, 0.1, 1.5], [0.3, 0.3, 0.3, 0.3, 0.3], SLOW_TTFT_ID), + ], + ids=["default_average", "p50", "p90"], +) +async def test_streaming_ttft_ranking_percentile( + sync_mode: bool, + ttft_percentile: float | None, + first_samples: list[float], + second_samples: list[float], + expected_id: str, +): + cache = DualCache() + routing_args = {} if ttft_percentile is None else {"ttft_percentile": ttft_percentile} + handler = LowestLatencyLoggingHandler(router_cache=cache, routing_args=routing_args) + cache.set_cache( + key=f"{MODEL_GROUP}_map", + value={ + FAST_TTFT_ID: {"time_to_first_token_seconds": first_samples}, + SLOW_TTFT_ID: {"time_to_first_token_seconds": second_samples}, + }, + ) + + if sync_mode: + picked = handler.get_available_deployments( + model_group=MODEL_GROUP, + healthy_deployments=STREAMING_DEPLOYMENTS, + request_kwargs={"stream": True, "metadata": {}}, + ) + else: + picked = await handler.async_get_available_deployments( + model_group=MODEL_GROUP, + healthy_deployments=STREAMING_DEPLOYMENTS, + request_kwargs={"stream": True, "metadata": {}}, + ) + + assert picked is not None + assert picked["model_info"]["id"] == expected_id + + +@pytest.mark.parametrize("ttft_percentile", [0, -0.1, 1.1]) +def test_ttft_percentile_validation(ttft_percentile: float): + with pytest.raises(ValidationError): + RoutingArgs(ttft_percentile=ttft_percentile) + + +@pytest.mark.parametrize("ttft_percentile", [0.5, 0.9, 0.95, 1.0]) +def test_ttft_percentile_accepts_valid_values(ttft_percentile: float): + assert RoutingArgs(ttft_percentile=ttft_percentile).ttft_percentile == ttft_percentile + + +@pytest.mark.asyncio +async def test_ttft_percentile_does_not_change_non_streaming_routing(): + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache, routing_args={"ttft_percentile": 0.9}) + cache.set_cache( + key=f"{MODEL_GROUP}_map", + value={ + FAST_TTFT_ID: {"latency": [1.0], "time_to_first_token_seconds": [0.1]}, + SLOW_TTFT_ID: {"latency": [0.2], "time_to_first_token_seconds": [1.5]}, + }, + ) + + picked = await handler.async_get_available_deployments( + model_group=MODEL_GROUP, + healthy_deployments=STREAMING_DEPLOYMENTS, + request_kwargs={"stream": False, "metadata": {}}, + ) + + assert picked is not None + assert picked["model_info"]["id"] == SLOW_TTFT_ID + + @pytest.mark.asyncio @pytest.mark.parametrize( "cached_entry", @@ -318,3 +398,81 @@ async def test_async_get_available_deployments_treats_missing_samples_as_zero_la assert picked is not None assert picked["model_info"]["id"] == DEPLOYMENT_ID + + +def _latency_router(routing_strategy_args: dict) -> Router: + return Router( + model_list=[ + { + "model_name": MODEL_GROUP, + "litellm_params": {"model": f"openai/{MODEL_GROUP}", "api_key": "sk-fake"}, + "model_info": {"id": deployment_id}, + } + for deployment_id in (FAST_TTFT_ID, SLOW_TTFT_ID) + ], + routing_strategy="latency-based-routing", + routing_strategy_args=routing_strategy_args, + ) + + +def _seed_streaming_ttft(router: Router) -> None: + router.cache.set_cache( + key=f"{MODEL_GROUP}_map", + value={ + FAST_TTFT_ID: {"time_to_first_token_seconds": [0.1, 0.1, 1.0]}, + SLOW_TTFT_ID: {"time_to_first_token_seconds": [0.3, 0.3, 0.3]}, + }, + ) + + +async def _pick_streaming(router: Router) -> str: + picked = await router.async_get_available_deployment( + model=MODEL_GROUP, + request_kwargs={"stream": True, "metadata": {}}, + ) + return picked["model_info"]["id"] + + +@pytest.mark.asyncio +async def test_runtime_routing_strategy_args_update_applies_ttft_percentile(): + """A config reload that adds ttft_percentile must reach the live selector, + not sit unused until the proxy restarts.""" + router = _latency_router({"max_latency_list_size": 50}) + _seed_streaming_ttft(router) + + assert await _pick_streaming(router) == SLOW_TTFT_ID + + router.update_settings(routing_strategy_args={"max_latency_list_size": 50, "ttft_percentile": 0.5}) + + assert await _pick_streaming(router) == FAST_TTFT_ID + + +@pytest.mark.asyncio +async def test_runtime_routing_strategy_args_update_keeps_previous_args_when_invalid(): + router = _latency_router({"ttft_percentile": 0.5}) + _seed_streaming_ttft(router) + + router.update_settings(routing_strategy_args={"ttft_percentile": 5}) + + assert await _pick_streaming(router) == FAST_TTFT_ID + + +@pytest.mark.asyncio +async def test_runtime_routing_strategy_args_update_is_a_noop_without_a_selector(): + """simple-shuffle has no selector to re-link, so an args update must leave + the router alone instead of blowing up on a missing selector attribute.""" + router = Router( + model_list=[ + { + "model_name": MODEL_GROUP, + "litellm_params": {"model": f"openai/{MODEL_GROUP}", "api_key": "sk-fake"}, + "model_info": {"id": FAST_TTFT_ID}, + } + ], + routing_strategy="simple-shuffle", + ) + + router.update_settings(routing_strategy_args={"ttl": 5}) + + assert router.routing_strategy_args == {"ttl": 5} + assert await _pick_streaming(router) == FAST_TTFT_ID From c7163a80ddfd34da743b06d770d60aba05b292eb Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:48:06 -0700 Subject: [PATCH 100/105] perf(proxy): collapse per-worker SGR upserts into one statement per flush (#40362) Each proxy worker flushed one Prisma upsert per active (date, category, route) bucket every interval, so the Postgres primary saw workers x routes statements per interval across the deployment. A flush now builds a single multi-row INSERT ... ON CONFLICT DO UPDATE, and with use_redis_transaction_buffer on the workers push snapshots to a Redis list that one lease-holding pod folds and commits, so the whole deployment costs one statement per interval. The leader keeps popping until the list is empty so a deployment wider than the dequeue cap cannot build a backlog, and rows that fail both the commit and the Redis re-queue fall back to the leader's own accumulator instead of being lost. Resolves LIT-7371 Co-authored-by: yassin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/constants.py | 1 + litellm/proxy/db/gateway_request_tracking.py | 220 ++++++++-- litellm/proxy/proxy_server.py | 14 +- .../proxy/db/test_gateway_request_tracking.py | 391 +++++++++++++++--- 4 files changed, 537 insertions(+), 89 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 108a914e9c1..f9389d22dea 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -335,6 +335,7 @@ DEFAULT_SSL_CIPHERS: Final = os.getenv( ########### v2 Architecture constants for managing writing updates to the database ########### REDIS_UPDATE_BUFFER_KEY: Final = "litellm_spend_update_buffer" +REDIS_GATEWAY_REQUESTS_BUFFER_KEY: Final = "litellm_gateway_requests_buffer" REDIS_DAILY_SPEND_UPDATE_BUFFER_KEY: Final = "litellm_daily_spend_update_buffer" REDIS_DAILY_TEAM_SPEND_UPDATE_BUFFER_KEY: Final = "litellm_daily_team_spend_update_buffer" REDIS_DAILY_ORG_SPEND_UPDATE_BUFFER_KEY: Final = "litellm_daily_org_spend_update_buffer" diff --git a/litellm/proxy/db/gateway_request_tracking.py b/litellm/proxy/db/gateway_request_tracking.py index bebd74e877c..c9ace68db33 100644 --- a/litellm/proxy/db/gateway_request_tracking.py +++ b/litellm/proxy/db/gateway_request_tracking.py @@ -10,13 +10,28 @@ strings rather than passing the raw path through. Nothing a caller sends can add a key, so the fold and the table it commits to are bounded by (days x routes) however much traffic arrives, and the response path carries no unbounded queue that would block once full. + +A flush commits its whole snapshot as one multi-row ``INSERT ... ON CONFLICT DO +UPDATE`` rather than one upsert per key, so a worker costs the primary one +statement per interval however many routes it served. With +``use_redis_transaction_buffer`` on, workers instead push their snapshot to a +Redis list and one lock-holding pod folds every entry and writes the table, so +the deployment as a whole costs the primary one statement per interval. """ -from dataclasses import asdict +import json +from collections.abc import AsyncIterator, Iterable from datetime import datetime, timezone -from typing import TYPE_CHECKING, Final +from itertools import chain +from types import MappingProxyType +from typing import TYPE_CHECKING, Final, TypeAlias + +from pydantic import TypeAdapter from litellm._logging import verbose_proxy_logger +from litellm.caching import RedisCache +from litellm.constants import MAX_REDIS_BUFFER_DEQUEUE_COUNT, REDIS_GATEWAY_REQUESTS_BUFFER_KEY +from litellm.proxy.db.db_transaction_queue.pod_lock_manager import PodLockManager from litellm.proxy.middleware.billable_request_metrics_middleware import BillableCategory from litellm.types.proxy.gateway_requests import ( GatewayRequestCounts, @@ -28,6 +43,15 @@ if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient _EMPTY: Final = GatewayRequestCounts(successful_requests=0, failed_requests=0) +_TABLE: Final = '"LiteLLM_DailyGatewayRequests"' +_COLUMNS_PER_ROW: Final = 5 +_UTC_NOW: Final = "(NOW() AT TIME ZONE 'UTC')" +GATEWAY_REQUESTS_JOB_NAME: Final = "update_gateway_requests_job" + +_BufferedRows: TypeAlias = tuple[tuple[str, str, str, int, int], ...] +_BUFFERED_ROWS: Final = TypeAdapter(_BufferedRows) +_BUFFERED_ENTRIES: Final = TypeAdapter(tuple[str | bytes, ...]) +_NO_COUNTS: Final[GatewayRequestSnapshot] = MappingProxyType({}) def _utc_date() -> str: @@ -59,20 +83,54 @@ class GatewayRequestAccumulator: route) however long the database is unreachable. This buys at-least-once, not exactly-once, and the cost is worth stating. - The batch commits inside its context manager's ``__aexit__``, so a failure - raised after the transaction committed (a connection dropped while reading - the acknowledgement) restores counts that are already persisted, and the - next flush increments them a second time. Exactly-once would need a dedup - key the upserts could ignore on replay. For a traffic-volume metric a rare + The statement commits on the server before its acknowledgement is read, so + a failure raised after the commit (a connection dropped while reading the + acknowledgement) restores counts that are already persisted, and the next + flush increments them a second time. Exactly-once would need a dedup key + the upsert could ignore on replay. For a traffic-volume metric a rare overcount on a dropped acknowledgement beats losing a whole interval to every database blip, so the trade is deliberate. """ - for key, counts in snapshot.items(): - existing = self._counts.get(key, _EMPTY) - self._counts[key] = GatewayRequestCounts( - successful_requests=existing.successful_requests + counts.successful_requests, - failed_requests=existing.failed_requests + counts.failed_requests, - ) + self._counts = dict(fold_counts(chain(self._counts.items(), snapshot.items()))) # mutable-ok: fold replaced + + +def fold_counts(items: Iterable[tuple[GatewayRequestKey, GatewayRequestCounts]]) -> GatewayRequestSnapshot: + """Sum counts key-wise; the result stays bounded by (date x category x route).""" + folded: Final[dict[GatewayRequestKey, GatewayRequestCounts]] = {} # mutable-ok: local fold returned once + for key, counts in items: + existing = folded.get(key, _EMPTY) + folded[key] = GatewayRequestCounts( + successful_requests=existing.successful_requests + counts.successful_requests, + failed_requests=existing.failed_requests + counts.failed_requests, + ) + return folded + + +def build_gateway_requests_upsert(snapshot: GatewayRequestSnapshot) -> tuple[str, tuple[str | int, ...]]: + """ + One ``INSERT ... ON CONFLICT DO UPDATE`` that increments every (date, category, + route) in the snapshot. Rows are ordered by the conflict key so concurrent + writers lock rows in the same order and cannot deadlock. + """ + ordered: Final = sorted(snapshot.items(), key=lambda item: (item[0].date, item[0].category, item[0].route)) + rows: Final = ", ".join( + f"(${base + 1}::text, ${base + 2}::text, ${base + 3}::text, ${base + 4}::bigint, ${base + 5}::bigint, {_UTC_NOW})" + for base in range(0, len(ordered) * _COLUMNS_PER_ROW, _COLUMNS_PER_ROW) + ) + sql: Final = ( + f'INSERT INTO {_TABLE} ("date", "category", "route", "successful_requests", "failed_requests", "updated_at")\n' + f"VALUES {rows}\n" + 'ON CONFLICT ("date", "category", "route") DO UPDATE SET\n' + f' "successful_requests" = {_TABLE}."successful_requests" + EXCLUDED."successful_requests",\n' + f' "failed_requests" = {_TABLE}."failed_requests" + EXCLUDED."failed_requests",\n' + f' "updated_at" = {_UTC_NOW}' + ) + params: Final[tuple[str | int, ...]] = tuple( + value + for key, counts in ordered + for value in (key.date, key.category, key.route, counts.successful_requests, counts.failed_requests) + ) + return sql, params async def commit_gateway_requests_to_db( @@ -80,50 +138,130 @@ async def commit_gateway_requests_to_db( prisma_client: "PrismaClient", snapshot: GatewayRequestSnapshot, ) -> None: - """Upsert one incrementing row per (date, category, route).""" + """Increment every (date, category, route) in the snapshot with a single statement.""" if not snapshot: return - ordered: Final = sorted(snapshot.items(), key=lambda item: (item[0].date, item[0].category, item[0].route)) + sql, params = build_gateway_requests_upsert(snapshot) + await prisma_client.db.execute_raw(sql, *params) # pyright: ignore[reportAny] # untyped prisma client - # pyright: ignore[reportAny] on both lines -- prisma's generated client is untyped, - # so .db and every table action off it resolve to Any at this boundary. The dict - # literals below are the shape prisma's generated inputs require. - async with prisma_client.db.batch_() as batcher: # pyright: ignore[reportAny] # untyped prisma client - for key, counts in ordered: - columns = asdict(key) - batcher.litellm_dailygatewayrequests.upsert( # pyright: ignore[reportAny] # untyped prisma client - where={"date_category_route": columns}, # mutable-ok: prisma input is dict-shaped - data={ # mutable-ok: prisma input is dict-shaped - "create": { # mutable-ok: prisma input is dict-shaped - **columns, - "successful_requests": counts.successful_requests, - "failed_requests": counts.failed_requests, - }, - "update": { # mutable-ok: prisma input is dict-shaped - "successful_requests": {"increment": counts.successful_requests}, # mutable-ok: as above - "failed_requests": {"increment": counts.failed_requests}, # mutable-ok: as above - }, - }, + verbose_proxy_logger.debug( + "Gateway request tracking - committed %d aggregated rows in one statement", len(snapshot) + ) + + +class GatewayRequestRedisBuffer: + """ + Folds every worker's snapshot through one Redis list so a single pod per + interval writes the table, mirroring the spend writer's transaction buffer. + + Each entry is one worker's snapshot as JSON rows; the lock holder pops them, + sums them, and commits one statement. A commit failure pushes the summed + rows back so the next holder retries, keeping the at-least-once guarantee. + If that push fails too, the rows go back to the holder's own accumulator so + they ride along with its next flush instead of vanishing with the pop. + """ + + def __init__(self, *, redis_cache: RedisCache, pod_lock_manager: PodLockManager) -> None: + self._redis_cache: Final = redis_cache + self._pod_lock_manager: Final = pod_lock_manager + + async def push(self, snapshot: GatewayRequestSnapshot) -> None: + if not snapshot: + return + rows: Final[_BufferedRows] = tuple( + (key.date, key.category, key.route, counts.successful_requests, counts.failed_requests) + for key, counts in snapshot.items() + ) + await self._redis_cache.async_rpush(key=REDIS_GATEWAY_REQUESTS_BUFFER_KEY, values=(json.dumps(rows),)) + + async def _pop_batch(self) -> tuple[str | bytes, ...]: + popped: Final[object] = await self._redis_cache.async_lpop( # pyright: ignore[reportAny] # redis returns Any + key=REDIS_GATEWAY_REQUESTS_BUFFER_KEY, count=MAX_REDIS_BUFFER_DEQUEUE_COUNT + ) + if not popped: + return () + return _BUFFERED_ENTRIES.validate_python(popped if isinstance(popped, list) else (popped,)) + + async def _pop_all(self) -> AsyncIterator[str | bytes]: + while True: + batch = await self._pop_batch() + for entry in batch: + yield entry + if len(batch) < MAX_REDIS_BUFFER_DEQUEUE_COUNT: + return + + async def pop(self) -> GatewayRequestSnapshot: + entries: Final = tuple([entry async for entry in self._pop_all()]) + return fold_counts( + ( + GatewayRequestKey(date=date, category=category, route=route), + GatewayRequestCounts(successful_requests=succeeded, failed_requests=failed), ) + for entry in entries + for date, category, route, succeeded, failed in _BUFFERED_ROWS.validate_json(entry) + ) - verbose_proxy_logger.debug("Gateway request tracking - committed %d aggregated rows", len(ordered)) + async def commit_if_leader(self, prisma_client: "PrismaClient") -> GatewayRequestSnapshot: + """ + Drain the list and write it as one statement, but only on the pod holding the job lock. + + The lock is a lease, never released: the holder re-enters it on every flush and + keeps committing alone until the TTL lapses, so the primary sees one statement + per flush interval deployment-wide instead of one per worker. + + Returns the popped rows that could be neither committed nor re-queued, for the + caller to keep in memory. Empty on success. + """ + if not await self._pod_lock_manager.acquire_lock(cronjob_id=GATEWAY_REQUESTS_JOB_NAME): + return _NO_COUNTS + buffered: Final = await self.pop() + try: + await commit_gateway_requests_to_db(prisma_client=prisma_client, snapshot=buffered) + except Exception: # noqa: BLE001 -- a failed commit must not stop the scheduler + verbose_proxy_logger.warning( + "Gateway request tracking - failed to commit %d buffered rows, re-queuing to Redis for the next flush", + len(buffered), + exc_info=True, + ) + return await self._requeue(buffered) + return _NO_COUNTS + + async def _requeue(self, snapshot: GatewayRequestSnapshot) -> GatewayRequestSnapshot: + try: + await self.push(snapshot) + except Exception: # noqa: BLE001 -- the rows go back to the caller's accumulator instead + verbose_proxy_logger.warning( + "Gateway request tracking - Redis re-queue failed, keeping %d rows in memory for the next flush", + len(snapshot), + exc_info=True, + ) + return snapshot + return _NO_COUNTS async def flush_gateway_requests( prisma_client: "PrismaClient", accumulator: GatewayRequestAccumulator, + redis_buffer: GatewayRequestRedisBuffer | None = None, ) -> None: """ Scheduler entrypoint. Never raises: a metering failure must not kill the job. + With ``redis_buffer`` the snapshot goes to Redis and only the lease holder + writes to Postgres. Shutdown passes no buffer so a departing worker writes its + own counts directly instead of parking them behind a lease it may not hold. + ``CancelledError`` is deliberately not caught, so a flush cancelled during shutdown drops its snapshot rather than restoring counts onto an accumulator the process is about to discard. """ snapshot: Final = accumulator.drain() try: - await commit_gateway_requests_to_db(prisma_client=prisma_client, snapshot=snapshot) + if redis_buffer is None: + await commit_gateway_requests_to_db(prisma_client=prisma_client, snapshot=snapshot) + else: + await redis_buffer.push(snapshot) except Exception: # noqa: BLE001 -- a failed flush must not stop the scheduler accumulator.restore(snapshot) verbose_proxy_logger.warning( @@ -131,3 +269,13 @@ async def flush_gateway_requests( len(snapshot), exc_info=True, ) + return + if redis_buffer is None: + return + try: + accumulator.restore(await redis_buffer.commit_if_leader(prisma_client)) + except Exception: # noqa: BLE001 -- entries still in Redis are drained by the next flush + verbose_proxy_logger.warning( + "Gateway request tracking - leader drain failed, buffered rows stay in Redis for the next flush", + exc_info=True, + ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c241e66049b..09e43eb74e1 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -424,6 +424,7 @@ from litellm.proxy.db.exception_handler import ( ) from litellm.proxy.db.gateway_request_tracking import ( GatewayRequestAccumulator, + GatewayRequestRedisBuffer, flush_gateway_requests, ) from litellm.proxy.db.proxy_worker_heartbeat import ( @@ -2355,6 +2356,17 @@ open_telemetry_logger: OpenTelemetry | None = None gateway_request_accumulator: Final = GatewayRequestAccumulator() ### INITIALIZE GLOBAL LOGGING OBJECT ### proxy_logging_obj: ProxyLogging = ProxyLogging(user_api_key_cache=user_api_key_cache, premium_user=premium_user) + + +def _gateway_request_redis_buffer() -> GatewayRequestRedisBuffer | None: + """Shares the spend writer's transaction-buffer Redis and pod lock when use_redis_transaction_buffer is on.""" + writer: Final = proxy_logging_obj.db_spend_update_writer + redis_cache: Final = writer.redis_update_buffer.redis_cache + if redis_cache is None or not writer.redis_update_buffer._should_commit_spend_updates_to_redis(): + return None + return GatewayRequestRedisBuffer(redis_cache=redis_cache, pod_lock_manager=writer.pod_lock_manager) + + ### REDIS QUEUE ### async_result: Final = None celery_app_conn: Final = None @@ -9633,7 +9645,7 @@ class ProxyStartupEvent: flush_gateway_requests, "interval", seconds=batch_writing_interval, - args=(prisma_client, gateway_request_accumulator), + args=(prisma_client, gateway_request_accumulator, _gateway_request_redis_buffer()), id="update_gateway_requests_job", replace_existing=True, misfire_grace_time=APSCHEDULER_MISFIRE_GRACE_TIME, diff --git a/tests/test_litellm/proxy/db/test_gateway_request_tracking.py b/tests/test_litellm/proxy/db/test_gateway_request_tracking.py index 93a11a914cb..045261e2d53 100644 --- a/tests/test_litellm/proxy/db/test_gateway_request_tracking.py +++ b/tests/test_litellm/proxy/db/test_gateway_request_tracking.py @@ -8,8 +8,11 @@ from datetime import datetime, timezone import pytest +from litellm.constants import MAX_REDIS_BUFFER_DEQUEUE_COUNT, REDIS_GATEWAY_REQUESTS_BUFFER_KEY from litellm.proxy.db.gateway_request_tracking import ( + GATEWAY_REQUESTS_JOB_NAME, GatewayRequestAccumulator, + GatewayRequestRedisBuffer, commit_gateway_requests_to_db, flush_gateway_requests, ) @@ -83,40 +86,47 @@ def test_drain_snapshot_is_not_mutated_by_later_records(): # ── commit ──────────────────────────────────────────────────────────────────── -class FakeTable: - def __init__(self) -> None: - self.upserts: list[dict] = [] - - def upsert(self, *, where: dict, data: dict) -> None: - self.upserts.append({"where": where, "data": data}) - - -class FakeBatcher: - def __init__(self, table: FakeTable) -> None: - self.litellm_dailygatewayrequests = table - - async def __aenter__(self) -> "FakeBatcher": - return self - - async def __aexit__(self, *args: object) -> bool: - return False - - class FakeDB: - def __init__(self, table: FakeTable) -> None: - self._table = table + def __init__(self) -> None: + self.statements: list[tuple[str, tuple[object, ...]]] = [] - def batch_(self) -> FakeBatcher: - return FakeBatcher(self._table) + async def execute_raw(self, query: str, *args: object) -> int: + self.statements.append((query, args)) + return len(args) // 5 class FakePrismaClient: def __init__(self) -> None: - self.table = FakeTable() - self.db = FakeDB(self.table) + self.db = FakeDB() -def test_commit_upserts_one_incrementing_row_per_key(): +def _rows_written(client: FakePrismaClient) -> list[tuple[object, ...]]: + """Every (date, category, route, successful, failed) tuple the database received, in statement order.""" + return [params[i : i + 5] for _, params in client.db.statements for i in range(0, len(params), 5)] + + +def test_commit_increments_with_a_single_statement_for_the_whole_snapshot(): + """One statement per flush is the whole point: the previous per-key upsert cost + the primary (workers x routes) statements per interval.""" + client = FakePrismaClient() + snapshot = { + GatewayRequestKey(date="2026-08-01", category="llm", route=route): ( + GatewayRequestCounts(successful_requests=7, failed_requests=2) + ) + for route in ("/chat/completions", "/embeddings", "/responses", "/v1/messages", "/mcp") + } + + asyncio.run(commit_gateway_requests_to_db(prisma_client=client, snapshot=snapshot)) + + assert len(client.db.statements) == 1 + sql, params = client.db.statements[0] + assert sql.count("ON CONFLICT") == 1 + assert sql.count("(NOW() AT TIME ZONE 'UTC'))") == 5 + assert len(params) == 25 + + +def test_commit_sql_adds_to_the_existing_row_instead_of_replacing_it(): + """A worker only knows its own share; the SQL must add EXCLUDED onto the stored count.""" client = FakePrismaClient() snapshot = { GatewayRequestKey(date="2026-08-01", category="llm", route="/chat/completions"): ( @@ -126,20 +136,36 @@ def test_commit_upserts_one_incrementing_row_per_key(): asyncio.run(commit_gateway_requests_to_db(prisma_client=client, snapshot=snapshot)) - assert len(client.table.upserts) == 1 - written = client.table.upserts[0] - assert written["where"] == { - "date_category_route": { - "date": "2026-08-01", - "category": "llm", - "route": "/chat/completions", - } + sql, params = client.db.statements[0] + assert 'INSERT INTO "LiteLLM_DailyGatewayRequests"' in sql + assert 'ON CONFLICT ("date", "category", "route") DO UPDATE SET' in sql + assert ( + '"successful_requests" = "LiteLLM_DailyGatewayRequests"."successful_requests" + EXCLUDED."successful_requests"' + in sql + ) + assert '"failed_requests" = "LiteLLM_DailyGatewayRequests"."failed_requests" + EXCLUDED."failed_requests"' in sql + assert params == ("2026-08-01", "llm", "/chat/completions", 7, 2) + + +def test_commit_placeholders_line_up_with_params(): + """$n positions are generated per row; a drift here silently swaps a route for a count.""" + client = FakePrismaClient() + snapshot = { + GatewayRequestKey(date="2026-08-01", category="llm", route="/chat/completions"): ( + GatewayRequestCounts(successful_requests=1, failed_requests=0) + ), + GatewayRequestKey(date="2026-08-01", category="mcp", route="/mcp"): ( + GatewayRequestCounts(successful_requests=0, failed_requests=3) + ), } - assert written["data"]["update"] == { - "successful_requests": {"increment": 7}, - "failed_requests": {"increment": 2}, - } - assert written["data"]["create"]["successful_requests"] == 7 + + asyncio.run(commit_gateway_requests_to_db(prisma_client=client, snapshot=snapshot)) + + sql, params = client.db.statements[0] + assert "($1::text, $2::text, $3::text, $4::bigint, $5::bigint," in sql + assert "($6::text, $7::text, $8::text, $9::bigint, $10::bigint," in sql + assert "$11" not in sql + assert params == ("2026-08-01", "llm", "/chat/completions", 1, 0, "2026-08-01", "mcp", "/mcp", 0, 3) def test_commit_is_deterministically_ordered(): @@ -154,17 +180,14 @@ def test_commit_is_deterministically_ordered(): asyncio.run(commit_gateway_requests_to_db(prisma_client=client, snapshot=snapshot)) - written_order = [ - (row["where"]["date_category_route"]["date"], row["where"]["date_category_route"]["category"]) - for row in client.table.upserts - ] + written_order = [(row[0], row[1]) for row in _rows_written(client)] assert written_order == [("2026-08-01", "llm"), ("2026-08-01", "mcp"), ("2026-08-02", "llm")] def test_commit_skips_the_database_entirely_when_nothing_accumulated(): client = FakePrismaClient() asyncio.run(commit_gateway_requests_to_db(prisma_client=client, snapshot={})) - assert client.table.upserts == [] + assert client.db.statements == [] # ── flush ───────────────────────────────────────────────────────────────────── @@ -177,12 +200,12 @@ def test_flush_drains_and_commits(): asyncio.run(flush_gateway_requests(client, acc)) - assert len(client.table.upserts) == 1 + assert len(client.db.statements) == 1 assert acc.drain() == {} class ExplodingDB: - def batch_(self): + async def execute_raw(self, query: str, *args: object) -> int: raise RuntimeError("db gone") @@ -208,10 +231,7 @@ def test_failed_flush_keeps_counts_for_the_next_attempt(): client = FakePrismaClient() asyncio.run(flush_gateway_requests(client, acc)) - assert client.table.upserts[0]["data"]["update"] == { - "successful_requests": {"increment": 1}, - "failed_requests": {"increment": 1}, - } + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", 1, 1)] def test_restored_counts_merge_with_requests_recorded_meanwhile(): @@ -223,5 +243,272 @@ def test_restored_counts_merge_with_requests_recorded_meanwhile(): client = FakePrismaClient() asyncio.run(flush_gateway_requests(client, acc)) - assert len(client.table.upserts) == 1 - assert client.table.upserts[0]["data"]["update"]["successful_requests"] == {"increment": 2} + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", 2, 0)] + + +class ExplodingDBWithInFlightRequest: + """Fails the write after a request has been recorded while it was in flight.""" + + def __init__(self, accumulator: GatewayRequestAccumulator) -> None: + self.accumulator = accumulator + + async def execute_raw(self, query: str, *args: object) -> int: + _record(self.accumulator, 500) + raise RuntimeError("db gone") + + +class ExplodingClientWithInFlightRequest: + def __init__(self, accumulator: GatewayRequestAccumulator) -> None: + self.db = ExplodingDBWithInFlightRequest(accumulator) + + +def test_restore_keeps_requests_recorded_while_the_failed_write_was_in_flight(): + acc = GatewayRequestAccumulator() + _record(acc, 200) + asyncio.run(flush_gateway_requests(ExplodingClientWithInFlightRequest(acc), acc)) + + client = FakePrismaClient() + asyncio.run(flush_gateway_requests(client, acc)) + + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", 1, 1)] + + +# ── redis buffer ────────────────────────────────────────────────────────────── + + +class FakeRedis: + def __init__(self) -> None: + self.lists: dict[str, list[str]] = {} + + async def async_rpush(self, key: str, values: list[str]) -> int: + self.lists.setdefault(key, []).extend(values) + return len(self.lists[key]) + + async def async_lpop(self, key: str, count: int) -> list[str] | None: + queue = self.lists.get(key, []) + if not queue: + return None + popped, self.lists[key] = queue[:count], queue[count:] + return popped + + +class FakePodLock: + def __init__(self, *, leader: bool) -> None: + self.leader = leader + self.held: list[str] = [] + self.released: list[str] = [] + + async def acquire_lock(self, cronjob_id: str) -> bool: + self.held.append(cronjob_id) + return self.leader + + async def release_lock(self, cronjob_id: str) -> None: + self.released.append(cronjob_id) + + +class FakeLease: + """Redis-side view of the job lock: SET NX by pod id, re-entrant for the holder, freed only by release or TTL.""" + + def __init__(self) -> None: + self.holder: str | None = None + + +class FakeLeasePodLock: + def __init__(self, lease: FakeLease, pod_id: str) -> None: + self.lease = lease + self.pod_id = pod_id + + async def acquire_lock(self, cronjob_id: str) -> bool: + if self.lease.holder is None: + self.lease.holder = self.pod_id + return self.lease.holder == self.pod_id + + async def release_lock(self, cronjob_id: str) -> None: + if self.lease.holder == self.pod_id: + self.lease.holder = None + + +def _buffer(redis: FakeRedis, *, leader: bool) -> tuple[GatewayRequestRedisBuffer, FakePodLock]: + lock = FakePodLock(leader=leader) + return GatewayRequestRedisBuffer(redis_cache=redis, pod_lock_manager=lock), lock # pyright: ignore[reportArgumentType] # duck-typed fakes + + +def test_non_leader_workers_push_to_redis_and_never_touch_the_database(): + redis = FakeRedis() + client = FakePrismaClient() + for _ in range(3): + acc = GatewayRequestAccumulator() + _record(acc, 200) + buffer, _ = _buffer(redis, leader=False) + asyncio.run(flush_gateway_requests(client, acc, buffer)) + + assert client.db.statements == [] + assert len(redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY]) == 3 + + +def test_leader_folds_every_workers_snapshot_into_one_statement(): + """Fifty workers each flushing the same routes must cost the primary one statement, not fifty.""" + redis = FakeRedis() + client = FakePrismaClient() + for _ in range(50): + acc = GatewayRequestAccumulator() + _record(acc, 200) + _record(acc, 500, route="/responses") + buffer, _ = _buffer(redis, leader=False) + asyncio.run(flush_gateway_requests(client, acc, buffer)) + + leader_acc = GatewayRequestAccumulator() + _record(leader_acc, 200) + leader, lock = _buffer(redis, leader=True) + asyncio.run(flush_gateway_requests(client, leader_acc, leader)) + + assert len(client.db.statements) == 1 + assert _rows_written(client) == [ + (_today(), "llm", "/chat/completions", 51, 0), + (_today(), "llm", "/responses", 0, 50), + ] + assert redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY] == [] + assert lock.held == [GATEWAY_REQUESTS_JOB_NAME] + assert lock.released == [] + + +def test_leader_keeps_the_lease_so_staggered_pods_cost_one_statement_per_interval(): + """Pods flush on their own clocks; without the lease each one would win the lock in turn and commit alone.""" + redis = FakeRedis() + client = FakePrismaClient() + lease = FakeLease() + pods = tuple( + GatewayRequestRedisBuffer(redis_cache=redis, pod_lock_manager=FakeLeasePodLock(lease, f"pod-{i}")) # pyright: ignore[reportArgumentType] # duck-typed fakes + for i in range(4) + ) + + for _interval in range(3): + for pod in pods: + acc = GatewayRequestAccumulator() + _record(acc, 200) + asyncio.run(flush_gateway_requests(client, acc, pod)) + + assert lease.holder == "pod-0" + assert len(client.db.statements) == 3 + assert [row[3] for row in _rows_written(client)] == [1, 4, 4] + assert len(redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY]) == 3 + + +def test_leader_drains_a_backlog_deeper_than_one_capped_pop(): + """More workers than MAX_REDIS_BUFFER_DEQUEUE_COUNT must not leave a growing tail queued behind the cap.""" + redis = FakeRedis() + client = FakePrismaClient() + workers = MAX_REDIS_BUFFER_DEQUEUE_COUNT * 2 + 1 + for _ in range(workers): + acc = GatewayRequestAccumulator() + _record(acc, 200) + buffer, _ = _buffer(redis, leader=False) + asyncio.run(flush_gateway_requests(client, acc, buffer)) + + leader, _ = _buffer(redis, leader=True) + asyncio.run(flush_gateway_requests(client, GatewayRequestAccumulator(), leader)) + + assert len(client.db.statements) == 1 + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", workers, 0)] + assert redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY] == [] + + +def test_leader_with_nothing_buffered_writes_nothing(): + redis = FakeRedis() + client = FakePrismaClient() + leader, lock = _buffer(redis, leader=True) + + asyncio.run(flush_gateway_requests(client, GatewayRequestAccumulator(), leader)) + + assert client.db.statements == [] + assert lock.released == [] + + +def test_leader_requeues_to_redis_when_the_database_commit_fails(): + """Counts popped from Redis are gone from every worker; a failed commit must put them back.""" + redis = FakeRedis() + acc = GatewayRequestAccumulator() + _record(acc, 200) + _record(acc, 200) + leader, lock = _buffer(redis, leader=True) + + asyncio.run(flush_gateway_requests(ExplodingClient(), acc, leader)) + + assert len(redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY]) == 1 + assert lock.released == [] + assert acc.drain() == {} + + client = FakePrismaClient() + retry, _ = _buffer(redis, leader=True) + asyncio.run(flush_gateway_requests(client, GatewayRequestAccumulator(), retry)) + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", 2, 0)] + + +class ExplodingRedis(FakeRedis): + async def async_rpush(self, key: str, values: list[str]) -> int: + raise RuntimeError("redis gone") + + +class UnreadableRedis(FakeRedis): + async def async_lpop(self, key: str, count: int) -> list[str] | None: + raise RuntimeError("redis gone mid-flush") + + +class UnwritableRedis(FakeRedis): + """Pops succeed, pushes fail: a Redis that went read-only between the leader's pop and its re-queue.""" + + async def async_rpush(self, key: str, values: list[str]) -> int: + raise RuntimeError("redis read-only") + + +def test_leader_keeps_popped_counts_in_memory_when_both_the_database_and_the_requeue_fail(): + """The pop removed the only copy; if Redis will not take it back the leader itself must carry it.""" + redis = FakeRedis() + worker_acc = GatewayRequestAccumulator() + _record(worker_acc, 200) + _record(worker_acc, 200) + worker, _ = _buffer(redis, leader=False) + asyncio.run(flush_gateway_requests(FakePrismaClient(), worker_acc, worker)) + + degraded = UnwritableRedis() + degraded.lists = redis.lists + leader_acc = GatewayRequestAccumulator() + leader, _ = _buffer(degraded, leader=True) + asyncio.run(flush_gateway_requests(ExplodingClient(), leader_acc, leader)) + assert degraded.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY] == [] + + client = FakePrismaClient() + retry, _ = _buffer(redis, leader=True) + asyncio.run(flush_gateway_requests(client, leader_acc, retry)) + assert _rows_written(client) == [(_today(), "llm", "/chat/completions", 2, 0)] + + +def test_leader_whose_redis_read_fails_leaves_the_pushed_rows_for_the_next_flush(): + """The scheduler job must not raise, and nothing is popped so nothing needs restoring anywhere.""" + redis = UnreadableRedis() + acc = GatewayRequestAccumulator() + _record(acc, 200) + client = FakePrismaClient() + leader, _ = _buffer(redis, leader=True) + + asyncio.run(flush_gateway_requests(client, acc, leader)) + + assert client.db.statements == [] + assert acc.drain() == {} + assert len(redis.lists[REDIS_GATEWAY_REQUESTS_BUFFER_KEY]) == 1 + + +def test_failed_redis_push_keeps_counts_locally_for_the_next_flush(): + acc = GatewayRequestAccumulator() + _record(acc, 200) + _record(acc, 500) + buffer, lock = _buffer(ExplodingRedis(), leader=True) + + asyncio.run(flush_gateway_requests(FakePrismaClient(), acc, buffer)) + + assert lock.held == [] + assert acc.drain() == { + GatewayRequestKey(date=_today(), category="llm", route="/chat/completions"): ( + GatewayRequestCounts(successful_requests=1, failed_requests=1) + ) + } From 6195fbf5f3aaa656d916f0a7c3013ada34e87752 Mon Sep 17 00:00:00 2001 From: Animesh Kumar Date: Wed, 9 Sep 2026 23:24:12 +0530 Subject: [PATCH 101/105] test: isolate bedrock aws tests from ambient SSL env vars Nine cases assert the sts client is built with verify=True, but get_ssl_verify reads SSL_CERT_FILE and SSL_VERIFY, so the argument depended on the ambient environment. The published images set SSL_CERT_FILE, so the suite failed there while passing in CI. Fixes #40357 --- tests/test_litellm/llms/bedrock/test_base_aws_llm.py | 8 ++++++++ 1 file changed, 8 insertions(+) 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 f854d806bdc..a1c28f36e70 100644 --- a/tests/test_litellm/llms/bedrock/test_base_aws_llm.py +++ b/tests/test_litellm/llms/bedrock/test_base_aws_llm.py @@ -38,6 +38,14 @@ def flush_shared_bedrock_iam_cache(): yield +@pytest.fixture(autouse=True) +def _clean_ssl_env(monkeypatch): + """get_ssl_verify reads these, so the sts client's verify= would otherwise depend on + the ambient environment. The published images set SSL_CERT_FILE.""" + for env_var in ("SSL_CERT_FILE", "SSL_VERIFY"): + monkeypatch.delenv(env_var, raising=False) + + def test_base_aws_llm_instances_share_process_wide_iam_cache(): """Regression LIT-2662: new instances must reuse iam_cache (Bedrock passthrough is per-request).""" first = BaseAWSLLM() From c82c9cbcedf43661521270740273d6437d76c098 Mon Sep 17 00:00:00 2001 From: tin-berri Date: Wed, 9 Sep 2026 11:04:11 -0700 Subject: [PATCH 102/105] fix(router): strip encrypted reasoning on an auto-router tier change instead of a 503 (#40280) A Responses API follow-up that replays reasoning.encrypted_content is pinned to the deployment that minted it. Behind an auto-router the pre-routing hook rebinds the model to the tier it picked before the candidate pool is built, so a turn that classifies into a different tier never finds the origin and the affinity check raised its fail-fast 503, whose text claims a cooldown that does not exist When the deployment that minted the reasoning is not a member of the model group this turn is routed to, strip the encrypted reasoning (keeping any readable summary, string or block form) and dispatch to the routed group. Membership is tested by deployment id against the candidate set the router itself resolved for the route (routing group, model_name, team, and pattern alike), not by model-group name, so an alias, a provider-qualified spelling, a team-public name, or a pattern route of the same group is not misread as a tier change. An unknown origin (a removed deployment, or a forged/unauthenticated marker) is handled the same as a cross-group one and its reasoning is stripped, so a real cross-group id and a nonexistent id return the same response and cannot be used to enumerate deployment ids. Unavailability within the origin's own group keeps the existing 429/503 fail-fast, so the cooldown contract is unchanged Resolves LIT-7195 Claude-Session: https://claude.ai/code/session_01KAumQbhzk6jdWWHFLA8Jar Co-authored-by: Claude Opus 4.8 --- litellm/responses/utils.py | 43 ++ litellm/router.py | 34 ++ .../encrypted_content_affinity_check.py | 73 ++- .../test_encrypted_content_affinity_check.py | 561 ++++++++++++------ tests/test_litellm/test_router.py | 42 ++ 5 files changed, 557 insertions(+), 196 deletions(-) diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 540d492beec..599e978df6a 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -543,6 +543,49 @@ class ResponsesAPIRequestUtils: return request_input + @staticmethod + def strip_encrypted_reasoning_from_input(request_input: object) -> None: + """Drop reasoning items the routed deployment cannot decrypt, keeping their readable summary. + + Mutates ``request_input`` in place: the router's fallback snapshot shares this + list object, so a rebound list would replay the stripped items on the fallback hop. + """ + if not isinstance(request_input, list): + return + items: Final = cast(list[object], request_input) # cast-ok: untyped client json + stripped: Final = tuple(ResponsesAPIRequestUtils._without_encrypted_reasoning(item) for item in items) + items[:] = (item for item in stripped if item is not None) # rebind-ok: list shared with fallback snapshot + + @staticmethod + def _without_encrypted_reasoning(item: object) -> object | None: + if not isinstance(item, dict): + return item + reasoning: Final = cast(Mapping[str, object], item) # cast-ok: untyped client json + if reasoning.get("type") != "reasoning" or not reasoning.get("encrypted_content"): + return reasoning + readable: Final = any( + ResponsesAPIRequestUtils._has_readable_text(reasoning.get(key)) for key in ("summary", "content") + ) + if not readable: + return None + kept: Final[dict[str, object]] = { # mutable-ok: request item rebuilt without the undecryptable keys + key: value for key, value in reasoning.items() if key not in ("encrypted_content", "id") + } + return kept + + @staticmethod + def _has_readable_text(value: object) -> bool: + """A reasoning item's ``summary``/``content`` carries readable text: a non-empty string, or a + list holding at least one block with a non-empty ``text`` field (summary_text / output_text).""" + if isinstance(value, str): + return bool(value.strip()) + if isinstance(value, list): + return any( + isinstance(block, dict) and bool(cast(Mapping[str, object], block).get("text")) # cast-ok: untyped json + for block in value + ) + return False + @staticmethod def _build_responses_api_response_id( custom_llm_provider: str | None, diff --git a/litellm/router.py b/litellm/router.py index 2fbdf541487..68ace283949 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -11167,6 +11167,40 @@ class Router: return ids + def get_candidate_model_ids_for_route(self, model: str, team_id: str | None = None) -> frozenset[str]: + """ + Deployment ids that could serve ``model`` for ``team_id``, unioned across the paths + the router resolves a route through: ``model_group_alias``, a routing group, the + ``model_name`` and team indexes, and wildcard pattern routes. Read-only and + side-effect-free, unlike ``_common_checks_available_deployment`` which also applies + fallbacks and can raise. Lets a pre-call check tell a genuine cross-group route from + same-group unavailability without re-deriving that precedence at the call site, and + without leaking deployment ids into request kwargs bound for the provider. + """ + resolved: Final = self._get_model_from_alias(model=model) or model + routing_group_members: Final = self._get_routing_group_deployments(model=resolved, team_id=team_id) + if routing_group_members is not None: + return self._deployment_ids(routing_group_members) + if resolved in self.model_names: + return self._deployment_ids(self._get_all_deployments(model_name=resolved, team_id=team_id)) + team_router: Final = self.team_pattern_routers.get(team_id) if team_id is not None else None + return self._deployment_ids( + ( + *self._get_all_deployments(model_name=resolved, team_id=team_id), + *(self.pattern_router.route(resolved) or ()), + *((team_router.route(resolved) or ()) if team_router is not None else ()), + ) + ) + + @staticmethod + def _deployment_ids(deployments: Sequence[Mapping[str, object]]) -> frozenset[str]: + return frozenset( + str(model_info["id"]) + for deployment in deployments + for model_info in (deployment.get("model_info"),) + if isinstance(model_info, Mapping) and model_info.get("id") is not None + ) + def has_model_id(self, candidate_id: str) -> bool: """ O(1) membership check for a deployment ID without allocating large lists. diff --git a/litellm/router_utils/pre_call_checks/encrypted_content_affinity_check.py b/litellm/router_utils/pre_call_checks/encrypted_content_affinity_check.py index b623e31ce06..d10153881d9 100644 --- a/litellm/router_utils/pre_call_checks/encrypted_content_affinity_check.py +++ b/litellm/router_utils/pre_call_checks/encrypted_content_affinity_check.py @@ -37,13 +37,13 @@ Safe to enable globally: """ import time +from collections.abc import Mapping from typing import TYPE_CHECKING, Final, Optional, Protocol, cast import httpx from litellm._logging import verbose_router_logger from litellm.exceptions import ( - BadRequestError, RateLimitError, ServiceUnavailableError, ) @@ -158,6 +158,23 @@ class EncryptedContentAffinityCheck(CustomLogger): return deployment return None + @staticmethod + def _request_team_id(request_kwargs: Mapping[str, object]) -> str | None: + containers: Final = (request_kwargs.get("metadata"), request_kwargs.get("litellm_metadata")) + team_ids: Final = (c.get("user_api_key_team_id") for c in containers if isinstance(c, Mapping)) + return next((tid for tid in team_ids if isinstance(tid, str)), None) + + def _routed_group_candidate_model_ids(self, request_kwargs: Mapping[str, object], model: str) -> frozenset[str]: + """ + Deployment ids that could serve this turn's routed ``model``, as the router + resolves a route (model_group_alias / routing group / model_name / team / + pattern). Delegates to the router so the full precedence is not re-derived here + and no deployment ids are written into request kwargs bound for the provider. + """ + if self.router is None: + return frozenset() + return self.router.get_candidate_model_ids_for_route(model=model, team_id=self._request_team_id(request_kwargs)) + @staticmethod def _encryption_boundary_key( litellm_params: object, @@ -225,10 +242,14 @@ class EncryptedContentAffinityCheck(CustomLogger): """ If the request ``input`` contains litellm-encoded item IDs, decode the embedded ``model_id`` and pin the request to that deployment. Raises - ``RateLimitError`` / ``ServiceUnavailableError`` / ``BadRequestError`` - when the originating deployment is unavailable and no encryption-boundary - peer exists, rather than dispatching a doomed request to a non-peer - deployment. The 429/503 split mirrors the originating cooldown's status: + ``RateLimitError`` / ``ServiceUnavailableError`` when the originating + deployment is a member of the routed model group but currently unavailable + and no encryption-boundary peer exists, rather than dispatching a doomed + request to a non-peer deployment. When the origin is not a member of the + routed group (an auto-router tier change, a model switch with no peer, a + removed deployment, or an unknown/forged marker), the encrypted reasoning is + stripped and the request dispatches with its readable history instead. The + 429/503 split mirrors the originating cooldown's status: a 429-induced cooldown surfaces as 429 (with ``Retry-After`` set to the remaining cooldown window) so OpenAI-compatible clients back off and retry after the deployment is eligible again. @@ -285,12 +306,34 @@ class EncryptedContentAffinityCheck(CustomLogger): request_kwargs["_encrypted_content_affinity_pinned"] = True return boundary_matches - # Dispatching to a non-peer would guarantee an upstream - # `invalid_encrypted_content` 400, so fail fast with a clearer error. + # The origin cannot serve this turn's routed group and no peer shares the boundary, so its + # encrypted reasoning can never decrypt here. Strip it, keep the readable history, and dispatch + # to the routed group instead of failing. Membership is tested by deployment id against the set + # the router actually resolved for this route, not by model-group name, so an alias, a + # provider-qualified spelling, a team-public name, or a pattern route of the same group is not + # mistaken for a tier change. An unknown origin (a removed deployment, or a forged marker) is + # treated the same as a cross-group one, which also denies an authenticated caller a + # deployment-id existence oracle: a real cross-group id and a nonexistent id both strip and + # dispatch rather than returning distinguishable responses. Only a genuine same-group member + # that is currently unavailable falls through to the fail-fast, preserving the cooldown contract. + routed_group_model_ids: Final = ( + self._routed_group_candidate_model_ids(request_kwargs, model) if originating is not None else frozenset() + ) + if str(model_id) not in routed_group_model_ids: + verbose_router_logger.debug( + "EncryptedContentAffinityCheck: model_id=%s is not a candidate for the routed group %s; " + "forwarding without its encrypted reasoning", + model_id, + model, + ) + ResponsesAPIRequestUtils.strip_encrypted_reasoning_from_input(request_input) + return typed_healthy_deployments + + # The origin is a member of the routed group but currently unavailable (cooled down); fail fast + # rather than dispatching to a non-peer, which would guarantee an upstream 400. raise await self._unavailable_origin_error( model=model, model_id=model_id, - originating=originating, parent_otel_span=parent_otel_span, ) @@ -298,25 +341,11 @@ class EncryptedContentAffinityCheck(CustomLogger): self, model: str, model_id: str, - originating: Deployment | None, parent_otel_span: Span | None, ) -> Exception: # Public error messages intentionally omit the originating ``model_id`` so # an authenticated caller forging encrypted-content markers cannot use the # error surface to enumerate which deployment IDs exist on this router. - if originating is None: - return BadRequestError( - message=( - "The deployment that produced this encrypted_content is no " - "longer configured on this router, and no deployment on the " - "same encryption boundary is available. Re-issue the request " - "without the stale encrypted_content items, or restore the " - "originating deployment." - ), - model=model, - llm_provider="", - ) - cooldown: Final = await self._get_origin_cooldown(model_id=model_id, parent_otel_span=parent_otel_span) if cooldown is not None and str(cooldown.get("status_code")) == "429": diff --git a/tests/test_litellm/router_utils/pre_call_checks/test_encrypted_content_affinity_check.py b/tests/test_litellm/router_utils/pre_call_checks/test_encrypted_content_affinity_check.py index dac991a41c4..3961c8d74c2 100644 --- a/tests/test_litellm/router_utils/pre_call_checks/test_encrypted_content_affinity_check.py +++ b/tests/test_litellm/router_utils/pre_call_checks/test_encrypted_content_affinity_check.py @@ -16,12 +16,10 @@ The mechanism works without any cache and supports two encoding strategies: """ import time -from typing import List, Optional from unittest.mock import AsyncMock, patch import pytest - import litellm from litellm.responses.utils import ResponsesAPIRequestUtils from litellm.types.llms.openai import ResponsesAPIResponse @@ -68,9 +66,7 @@ class TestEncryptedItemIdCodec: def test_roundtrip(self): model_id = "deployment-1" original_item_id = "rs_abc123def456" - encoded = ResponsesAPIRequestUtils._build_encrypted_item_id( - model_id, original_item_id - ) + encoded = ResponsesAPIRequestUtils._build_encrypted_item_id(model_id, original_item_id) assert encoded.startswith("encitem_") decoded = ResponsesAPIRequestUtils._decode_encrypted_item_id(encoded) assert decoded is not None @@ -81,9 +77,7 @@ class TestEncryptedItemIdCodec: """Decoding must succeed even if base64 padding (=) was stripped in transit.""" model_id = "gpt-5.1-codex-openai-2" original_item_id = "rs_0efb96cb222403210069a01d5d52588196a9dc394ffdb89d00" - encoded = ResponsesAPIRequestUtils._build_encrypted_item_id( - model_id, original_item_id - ) + encoded = ResponsesAPIRequestUtils._build_encrypted_item_id(model_id, original_item_id) # Strip any trailing '=' to simulate what happens in transit stripped = encoded.rstrip("=") decoded = ResponsesAPIRequestUtils._decode_encrypted_item_id(stripped) @@ -100,9 +94,7 @@ class TestEncryptedItemIdCodec: """item_id values containing ';' must survive the roundtrip.""" model_id = "deployment-1" original_item_id = "rs_part1;part2;part3" - encoded = ResponsesAPIRequestUtils._build_encrypted_item_id( - model_id, original_item_id - ) + encoded = ResponsesAPIRequestUtils._build_encrypted_item_id(model_id, original_item_id) decoded = ResponsesAPIRequestUtils._decode_encrypted_item_id(encoded) assert decoded is not None assert decoded["item_id"] == original_item_id @@ -118,11 +110,7 @@ class TestUpdateEncryptedContentItemIds: {"id": "rs_xyz", "type": "reasoning", "encrypted_content": "secret"}, ], } - result = ( - ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response( - response, model_id - ) - ) + result = ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response(response, model_id) # Plain message item untouched assert result["output"][0]["id"] == "msg_abc" # Reasoning item with encrypted_content gets encoded @@ -133,16 +121,8 @@ class TestUpdateEncryptedContentItemIds: assert decoded["item_id"] == "rs_xyz" def test_no_op_when_model_id_is_none(self): - response = { - "output": [ - {"id": "rs_xyz", "type": "reasoning", "encrypted_content": "secret"} - ] - } - result = ( - ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response( - response, None - ) - ) + response = {"output": [{"id": "rs_xyz", "type": "reasoning", "encrypted_content": "secret"}]} + result = ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response(response, None) assert result["output"][0]["id"] == "rs_xyz" @@ -151,9 +131,7 @@ class TestEncryptedContentWrapping: """Test wrapping encrypted_content with model_id metadata.""" model_id = "deployment-1" original_content = "gAAAAABpnW_yEYmSNEyOG_original_encrypted_data" - wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id( - original_content, model_id - ) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id(original_content, model_id) assert wrapped.startswith("litellm_enc:") assert wrapped != original_content @@ -170,9 +148,7 @@ class TestEncryptedContentWrapping: ( model_id, content, - ) = ResponsesAPIRequestUtils._unwrap_encrypted_content_with_model_id( - plain_content - ) + ) = ResponsesAPIRequestUtils._unwrap_encrypted_content_with_model_id(plain_content) assert model_id is None assert content == plain_content @@ -189,11 +165,7 @@ class TestEncryptedContentWrapping: }, ], } - result = ( - ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response( - response, model_id - ) - ) + result = ResponsesAPIRequestUtils._update_encrypted_content_item_ids_in_response(response, model_id) assert result["output"][0].get("encrypted_content") is None wrapped = result["output"][1]["encrypted_content"] assert wrapped.startswith("litellm_enc:") @@ -210,19 +182,13 @@ class TestRestoreEncryptedContentItemIds: def test_restores_encoded_ids(self): model_id = "deployment-1" original_id = "rs_encrypted_item_456" - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - model_id, original_id - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id(model_id, original_id) request_input = [ {"type": "message", "id": "msg_abc123", "role": "assistant"}, {"type": "reasoning", "id": encoded_id, "encrypted_content": "secret"}, ] - restored = ( - ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input( - request_input - ) - ) + restored = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input(request_input) assert restored[0]["id"] == "msg_abc123" assert restored[1]["id"] == original_id @@ -230,33 +196,21 @@ class TestRestoreEncryptedContentItemIds: """Test that wrapped encrypted_content is unwrapped before forwarding.""" model_id = "deployment-1" original_content = "gAAAAABpnW_yEYmSNEyOG_original" - wrapped_content = ( - ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id( - original_content, model_id - ) - ) + wrapped_content = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id(original_content, model_id) request_input = [ {"type": "reasoning", "encrypted_content": wrapped_content}, ] - restored = ( - ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input( - request_input - ) - ) + restored = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input(request_input) assert restored[0]["encrypted_content"] == original_content def test_no_op_for_plain_string_input(self): - result = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input( - "Hello world" - ) + result = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input("Hello world") assert result == "Hello world" def test_no_op_for_unencoded_ids(self): request_input = [{"type": "message", "id": "msg_plain"}] - result = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input( - request_input - ) + result = ResponsesAPIRequestUtils._restore_encrypted_content_item_ids_in_input(request_input) assert result[0]["id"] == "msg_plain" @@ -283,9 +237,7 @@ async def test_encrypted_content_affinity_tracks_and_routes(): "id": "msg_abc123", "status": "completed", "role": "assistant", - "content": [ - {"type": "output_text", "text": "Hello!", "annotations": []} - ], + "content": [{"type": "output_text", "text": "Hello!", "annotations": []}], }, { "type": "reasoning", @@ -347,9 +299,9 @@ async def test_encrypted_content_affinity_tracks_and_routes(): # The response must have rewritten the encrypted item's ID to encoded form encoded_item_id = _extract_encoded_item_id(first_response) - assert encoded_item_id.startswith( - "encitem_" - ), f"Expected output item ID to be rewritten to encitem_... but got {encoded_item_id!r}" + assert encoded_item_id.startswith("encitem_"), ( + f"Expected output item ID to be rewritten to encitem_... but got {encoded_item_id!r}" + ) # Verify the encoded ID decodes back to the correct deployment + original ID decoded = ResponsesAPIRequestUtils._decode_encrypted_item_id(encoded_item_id) @@ -371,9 +323,9 @@ async def test_encrypted_content_affinity_tracks_and_routes(): ) second_model_id = second_response._hidden_params["model_id"] - assert ( - second_model_id == first_model_id - ), f"Expected affinity to route to {first_model_id}, but got {second_model_id}" + assert second_model_id == first_model_id, ( + f"Expected affinity to route to {first_model_id}, but got {second_model_id}" + ) @pytest.mark.asyncio @@ -478,9 +430,7 @@ async def test_encrypted_content_affinity_bypasses_rpm_limits(): # Extract encoded item ID from the first response output encoded_item_id = _extract_encoded_item_id(first_response) - assert encoded_item_id.startswith( - "encitem_" - ), f"Expected encitem_... but got {encoded_item_id!r}" + assert encoded_item_id.startswith("encitem_"), f"Expected encitem_... but got {encoded_item_id!r}" # Follow-up with the encoded item ID — should pin to same deployment second_response = await router.aresponses( @@ -628,17 +578,13 @@ async def test_encrypted_content_affinity_with_wrapped_content_no_id(): if hasattr(first_item, "encrypted_content") else first_item.get("encrypted_content") ) - assert wrapped_content.startswith( - "litellm_enc:" - ), f"Expected wrapped content but got {wrapped_content[:50]}..." + assert wrapped_content.startswith("litellm_enc:"), f"Expected wrapped content but got {wrapped_content[:50]}..." # Verify we can extract model_id from wrapped content ( extracted_model_id, _, - ) = ResponsesAPIRequestUtils._unwrap_encrypted_content_with_model_id( - wrapped_content - ) + ) = ResponsesAPIRequestUtils._unwrap_encrypted_content_with_model_id(wrapped_content) assert extracted_model_id == first_model_id # Second request: use wrapped encrypted_content WITHOUT an ID (Codex behavior) @@ -653,9 +599,9 @@ async def test_encrypted_content_affinity_with_wrapped_content_no_id(): ) second_model_id = second_response._hidden_params["model_id"] - assert ( - second_model_id == first_model_id - ), f"Expected affinity to route to {first_model_id}, but got {second_model_id}" + assert second_model_id == first_model_id, ( + f"Expected affinity to route to {first_model_id}, but got {second_model_id}" + ) def test_encrypted_content_wrapping_preserves_original_content(): @@ -664,13 +610,9 @@ def test_encrypted_content_wrapping_preserves_original_content(): This is critical for streaming responses where content must round-trip correctly. """ model_id = "test-deployment-1" - original_encrypted_content = ( - "gAAAAABpnW_yEYmSNEyOG_streaming_test_content_with_special_chars==+/" - ) + original_encrypted_content = "gAAAAABpnW_yEYmSNEyOG_streaming_test_content_with_special_chars==+/" - wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id( - original_encrypted_content, model_id - ) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id(original_encrypted_content, model_id) assert wrapped.startswith("litellm_enc:") assert wrapped != original_encrypted_content @@ -691,9 +633,7 @@ def test_encrypted_content_wrapping_with_multiple_semicolons(): model_id = "deployment-with-semicolons" original_content = "gAAAAAB;some;content;with;semicolons" - wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id( - original_content, model_id - ) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id(original_content, model_id) ( extracted_model_id, @@ -764,9 +704,7 @@ async def test_encrypted_content_affinity_preserves_litellm_metadata_for_respons request_kwargs=request_kwargs, ) - assert ( - request_kwargs["litellm_metadata"]["encrypted_content_affinity_enabled"] is True - ) + assert request_kwargs["litellm_metadata"]["encrypted_content_affinity_enabled"] is True assert request_kwargs["litellm_metadata"]["model_info"] == {"id": "dep-1"} @@ -777,9 +715,7 @@ def test_encrypted_content_wrapping_empty_string(): model_id = "test-deployment" original_content = "" - wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id( - original_content, model_id - ) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id(original_content, model_id) assert wrapped.startswith("litellm_enc:") @@ -1132,9 +1068,7 @@ def test_boundary_key_accepts_pydantic_litellm_params_instance(): "api_key": "fake-azure-resource-key-a", } - pydantic_key = EncryptedContentAffinityCheck._encryption_boundary_key( - pydantic_params - ) + pydantic_key = EncryptedContentAffinityCheck._encryption_boundary_key(pydantic_params) plain_key = EncryptedContentAffinityCheck._encryption_boundary_key(plain_params) assert pydantic_key is not None @@ -1161,18 +1095,8 @@ def test_boundary_key_rejects_non_dict_like_inputs(): for bad in (None, [], "not a dict", 42, object()): assert EncryptedContentAffinityCheck._encryption_boundary_key(bad) is None - assert ( - EncryptedContentAffinityCheck._encryption_boundary_key( - {"api_base": "", "api_key": "k"} - ) - is None - ) - assert ( - EncryptedContentAffinityCheck._encryption_boundary_key( - {"api_base": "https://x"} - ) - is None - ) + assert EncryptedContentAffinityCheck._encryption_boundary_key({"api_base": "", "api_key": "k"}) is None + assert EncryptedContentAffinityCheck._encryption_boundary_key({"api_base": "https://x"}) is None # --------------------------------------------------------------------------- @@ -1180,10 +1104,11 @@ def test_boundary_key_rejects_non_dict_like_inputs(): # --------------------------------------------------------------------------- -def _make_originating_mock(api_base: str, api_key: str): +def _make_originating_mock(api_base: str, api_key: str, model_name: str = "gpt-5.4"): from unittest.mock import MagicMock originating = MagicMock() + originating.model_name = model_name originating.litellm_params.model_dump.return_value = { "api_base": api_base, "api_key": api_key, @@ -1192,19 +1117,23 @@ def _make_originating_mock(api_base: str, api_key: str): def _make_router_mock_with_cooldown( - originating, cooldown_entries: Optional[List[tuple]] = None + originating, + cooldown_entries: list[tuple] | None = None, + routed_group_model_ids: list[str] | None = None, ): """ Build a MagicMock router whose ``cooldown_cache.async_get_active_cooldowns`` - returns ``cooldown_entries`` (defaulting to ``[]`` — no active cooldown). + returns ``cooldown_entries`` (defaulting to ``[]`` — no active cooldown), and + whose ``get_candidate_model_ids_for_route`` returns ``routed_group_model_ids`` + (the deployment ids the router resolves for the routed model; defaulting to ``[]`` + — origin absent from the routed group, i.e. a tier change). """ from unittest.mock import AsyncMock, MagicMock mock_router = MagicMock() mock_router.get_deployment.return_value = originating - mock_router.cooldown_cache.async_get_active_cooldowns = AsyncMock( - return_value=list(cooldown_entries or []) - ) + mock_router.cooldown_cache.async_get_active_cooldowns = AsyncMock(return_value=list(cooldown_entries or [])) + mock_router.get_candidate_model_ids_for_route.return_value = frozenset(routed_group_model_ids or []) return mock_router @@ -1235,15 +1164,15 @@ async def test_affinity_raises_service_unavailable_when_origin_cooled_for_non_42 }, ) ], + routed_group_model_ids=["deployment-a-cooled", "deployment-b"], ) check = EncryptedContentAffinityCheck(router=mock_router) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-a-cooled", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-a-cooled", "rs_test") healthy_only_b = [ { "model_info": {"id": "deployment-b"}, + "model_name": "gpt-5.4", "litellm_params": { "api_base": "https://account-b.openai.azure.com/", "api_key": "key-b", @@ -1297,15 +1226,15 @@ async def test_affinity_raises_rate_limit_with_retry_after_when_origin_cooled_fo }, ) ], + routed_group_model_ids=["deployment-a-cooled-429", "deployment-b"], ) check = EncryptedContentAffinityCheck(router=mock_router) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-a-cooled-429", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-a-cooled-429", "rs_test") healthy_only_b = [ { "model_info": {"id": "deployment-b"}, + "model_name": "gpt-5.4", "litellm_params": { "api_base": "https://account-b.openai.azure.com/", "api_key": "key-b", @@ -1345,15 +1274,16 @@ async def test_affinity_raises_service_unavailable_when_origin_filtered_without_ ) originating = _make_originating_mock("https://account-a.openai.azure.com/", "key-a") - mock_router = _make_router_mock_with_cooldown(originating, cooldown_entries=[]) + mock_router = _make_router_mock_with_cooldown( + originating, cooldown_entries=[], routed_group_model_ids=["deployment-a-filtered", "deployment-b"] + ) check = EncryptedContentAffinityCheck(router=mock_router) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-a-filtered", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-a-filtered", "rs_test") healthy_only_b = [ { "model_info": {"id": "deployment-b"}, + "model_name": "gpt-5.4", "litellm_params": { "api_base": "https://account-b.openai.azure.com/", "api_key": "key-b", @@ -1377,15 +1307,18 @@ async def test_affinity_raises_service_unavailable_when_origin_filtered_without_ @pytest.mark.asyncio -async def test_affinity_raises_bad_request_when_origin_removed(): +async def test_affinity_strips_and_dispatches_when_origin_is_unknown_or_removed(): """ - Originating deployment was removed from the router config and no boundary - peer is available. This is permanent (the stale encrypted_content cannot - be honored), so surface a 400 with actionable text. + A removed deployment, or a forged/unknown affinity marker, resolves to no + originating deployment. It is handled like a cross-group origin: the encrypted + reasoning is stripped and the request dispatches with its readable history, + rather than returning a distinguishable error. That uniform handling denies an + authenticated caller a deployment-id existence oracle, an existing cross-group id + and a nonexistent id both strip and proceed, so responses cannot be told apart. + The membership lookup is skipped entirely when the origin is unknown. """ from unittest.mock import MagicMock - from litellm.exceptions import BadRequestError from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import ( EncryptedContentAffinityCheck, ) @@ -1394,12 +1327,11 @@ async def test_affinity_raises_bad_request_when_origin_removed(): mock_router.get_deployment.return_value = None check = EncryptedContentAffinityCheck(router=mock_router) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-removed", "rs_test" - ) - healthy_only_b = [ + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-removed") + routed_pool = [ { "model_info": {"id": "deployment-b"}, + "model_name": "gpt-5.4", "litellm_params": { "api_base": "https://account-b.openai.azure.com/", "api_key": "key-b", @@ -1408,18 +1340,28 @@ async def test_affinity_raises_bad_request_when_origin_removed(): } ] request_kwargs = { - "input": [{"id": encoded_id, "type": "reasoning"}], + "litellm_metadata": {}, + "input": [ + {"role": "user", "content": "why is the sky blue?"}, + { + "type": "reasoning", + "encrypted_content": wrapped, + "summary": [{"type": "summary_text", "text": "scattering"}], + }, + {"role": "user", "content": "and sunsets?"}, + ], } - with pytest.raises(BadRequestError) as excinfo: - await check.async_filter_deployments( - model="gpt-5.4", - healthy_deployments=healthy_only_b, - messages=None, - request_kwargs=request_kwargs, - ) + result = await check.async_filter_deployments( + model="gpt-5.4", + healthy_deployments=routed_pool, + messages=None, + request_kwargs=request_kwargs, + ) - assert "deployment-removed" not in str(excinfo.value) + assert result is routed_pool + assert not any(isinstance(item, dict) and item.get("encrypted_content") for item in request_kwargs["input"]) + mock_router.get_candidate_model_ids_for_route.assert_not_called() @pytest.mark.asyncio @@ -1444,9 +1386,7 @@ async def test_affinity_does_not_raise_when_boundary_peer_available(): mock_router.get_deployment.return_value = originating check = EncryptedContentAffinityCheck(router=mock_router) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-a", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-a", "rs_test") peer = { "model_info": {"id": "deployment-a-peer"}, "litellm_params": { @@ -1490,9 +1430,7 @@ async def test_model_group_affinity_config_enables_encrypted_content_affinity(): }, target_deployment, ] - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-b", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-b", "rs_test") request_kwargs = { "input": [{"type": "reasoning", "id": encoded_id}], "litellm_metadata": {}, @@ -1536,9 +1474,7 @@ async def test_model_group_affinity_config_does_not_disable_global_encrypted_con }, target_deployment, ] - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-b", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-b", "rs_test") request_kwargs = { "input": [{"type": "reasoning", "id": encoded_id}], "litellm_metadata": {}, @@ -1600,15 +1536,9 @@ async def test_model_group_encrypted_content_affinity_overrides_global_deploymen try: callbacks = router.optional_callbacks or [] - deployment_callback = next( - cb for cb in callbacks if isinstance(cb, DeploymentAffinityCheck) - ) - encrypted_content_callback = next( - cb for cb in callbacks if isinstance(cb, EncryptedContentAffinityCheck) - ) - assert callbacks.index(encrypted_content_callback) < callbacks.index( - deployment_callback - ) + deployment_callback = next(cb for cb in callbacks if isinstance(cb, DeploymentAffinityCheck)) + encrypted_content_callback = next(cb for cb in callbacks if isinstance(cb, EncryptedContentAffinityCheck)) + assert callbacks.index(encrypted_content_callback) < callbacks.index(deployment_callback) assert encrypted_content_callback.enable_global_affinity is False cache_key = DeploymentAffinityCheck.get_affinity_cache_key( @@ -1620,9 +1550,7 @@ async def test_model_group_encrypted_content_affinity_overrides_global_deploymen value={"model_id": "deployment-a"}, ttl=60, ) - encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id( - "deployment-b", "rs_test" - ) + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-b", "rs_test") request_kwargs = { "input": [ { @@ -1643,16 +1571,301 @@ async def test_model_group_encrypted_content_affinity_overrides_global_deploymen ) assert after_deployment_affinity == [deployment_a, deployment_b] - after_encrypted_content_affinity = ( - await encrypted_content_callback.async_filter_deployments( - model=model_group, - healthy_deployments=after_deployment_affinity, - messages=None, - request_kwargs=request_kwargs, - ) + after_encrypted_content_affinity = await encrypted_content_callback.async_filter_deployments( + model=model_group, + healthy_deployments=after_deployment_affinity, + messages=None, + request_kwargs=request_kwargs, ) assert after_encrypted_content_affinity == [deployment_b] assert request_kwargs.get("_encrypted_content_affinity_pinned") is True finally: router.discard() + + +class TestStripEncryptedReasoningFromInput: + def test_keeps_summary_and_drops_encrypted_content_and_id(self): + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-a") + encoded_id = ResponsesAPIRequestUtils._build_encrypted_item_id("deployment-a", "rs_1") + request_input = [ + {"role": "user", "content": "first turn"}, + { + "type": "reasoning", + "id": encoded_id, + "encrypted_content": wrapped, + "summary": [{"type": "summary_text", "text": "thought about it"}], + }, + {"type": "reasoning", "id": encoded_id, "encrypted_content": wrapped}, + {"type": "reasoning", "encrypted_content": wrapped, "summary": []}, + {"type": "message", "id": "msg_1", "role": "assistant", "content": "hi"}, + {"role": "user", "content": "second turn"}, + ] + ResponsesAPIRequestUtils.strip_encrypted_reasoning_from_input(request_input) + assert request_input == [ + {"role": "user", "content": "first turn"}, + { + "type": "reasoning", + "summary": [{"type": "summary_text", "text": "thought about it"}], + }, + {"type": "message", "id": "msg_1", "role": "assistant", "content": "hi"}, + {"role": "user", "content": "second turn"}, + ] + + def test_keeps_string_form_summary_when_stripping(self): + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-a") + request_input = [ + {"type": "reasoning", "encrypted_content": wrapped, "summary": "plain string thought"}, + { + "type": "reasoning", + "encrypted_content": wrapped, + "content": [{"type": "output_text", "text": "in content"}], + }, + {"type": "reasoning", "encrypted_content": wrapped, "summary": "", "content": []}, + ] + ResponsesAPIRequestUtils.strip_encrypted_reasoning_from_input(request_input) + assert request_input == [ + {"type": "reasoning", "summary": "plain string thought"}, + {"type": "reasoning", "content": [{"type": "output_text", "text": "in content"}]}, + ] + + def test_leaves_input_untouched_when_no_encrypted_reasoning(self): + request_input = [ + {"role": "user", "content": "first turn"}, + {"type": "reasoning", "summary": [{"type": "summary_text", "text": "no blob here"}]}, + {"role": "user", "content": "second turn"}, + ] + before = [dict(item) for item in request_input] + ResponsesAPIRequestUtils.strip_encrypted_reasoning_from_input(request_input) + assert request_input == before + + +def _cross_group_request_kwargs(): + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-a") + return { + "litellm_metadata": {}, + "input": [ + {"role": "user", "content": "ZEBRA: why is the sky blue?"}, + { + "type": "reasoning", + "encrypted_content": wrapped, + "summary": [{"type": "summary_text", "text": "scattering"}], + }, + {"type": "message", "role": "assistant", "content": "Rayleigh scattering."}, + {"role": "user", "content": "KIWI: and sunsets?"}, + ], + } + + +@pytest.mark.asyncio +async def test_affinity_strips_encrypted_reasoning_when_routed_to_another_model_group(): + """ + An auto-router tier change (or a model switch with no boundary peer): the + routed pool holds no deployment of the origin's model group. The origin is + healthy, so a 503 would be wrong; the follow-up dispatches to the routed + pool with the origin's encrypted reasoning stripped and its summary kept. + """ + from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import ( + EncryptedContentAffinityCheck, + ) + + originating = _make_originating_mock(None, "key-a", model_name="gpt-reasoning-tier") + mock_router = _make_router_mock_with_cooldown( + originating, cooldown_entries=[], routed_group_model_ids=["deployment-b"] + ) + check = EncryptedContentAffinityCheck(router=mock_router) + routed_pool = [ + { + "model_info": {"id": "deployment-b"}, + "model_name": "gpt-simple-tier", + "litellm_params": { + "api_base": "https://gateway.example/v1", + "api_key": "key-b", + "model": "openai/gpt-5-nano", + }, + } + ] + request_kwargs = _cross_group_request_kwargs() + original_input = request_kwargs["input"] + + result = await check.async_filter_deployments( + model="gpt-simple-tier", + healthy_deployments=routed_pool, + messages=None, + request_kwargs=request_kwargs, + ) + + assert result is routed_pool + assert "_encrypted_content_affinity_pinned" not in request_kwargs + assert request_kwargs["litellm_metadata"]["encrypted_content_affinity_enabled"] is True + assert request_kwargs["input"] is original_input + assert [item.get("type") or item["role"] for item in original_input] == [ + "user", + "reasoning", + "message", + "user", + ] + assert original_input[1] == { + "type": "reasoning", + "summary": [{"type": "summary_text", "text": "scattering"}], + } + assert not any(isinstance(item, dict) and item.get("encrypted_content") for item in original_input) + + +@pytest.mark.asyncio +async def test_affinity_fails_fast_within_the_origins_own_group(): + """ + Negative class for the tier-change discriminator: the routed group IS the + origin's group (a same-group cooldown, not a tier change), so even with a + healthy non-origin sibling that cannot decrypt the content, the request + still fails fast and the encrypted reasoning is left intact rather than + stripped. Preserves the LIT-3051 cooldown contract. + """ + from litellm.exceptions import ServiceUnavailableError + from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import ( + EncryptedContentAffinityCheck, + ) + + originating = _make_originating_mock( + "https://account-a.openai.azure.com/", "key-a", model_name="gpt-reasoning-tier" + ) + mock_router = _make_router_mock_with_cooldown( + originating, cooldown_entries=[], routed_group_model_ids=["deployment-a", "deployment-a2"] + ) + check = EncryptedContentAffinityCheck(router=mock_router) + sibling_pool = [ + { + "model_info": {"id": "deployment-a2"}, + "model_name": "gpt-reasoning-tier", + "litellm_params": { + "api_base": "https://account-a2.openai.azure.com/", + "api_key": "key-a2", + "model": "azure/gpt-5.4", + }, + } + ] + request_kwargs = _cross_group_request_kwargs() + + with pytest.raises(ServiceUnavailableError): + await check.async_filter_deployments( + model="gpt-reasoning-tier", + healthy_deployments=sibling_pool, + messages=None, + request_kwargs=request_kwargs, + ) + + assert request_kwargs["input"][1].get("encrypted_content") + + +@pytest.mark.asyncio +async def test_affinity_does_not_strip_when_group_is_spelled_differently_but_same_by_id(): + """ + The discriminator must key on deployment-id membership, not on the model-group + name string. Here the origin's configured group is spelled ``openai/gpt-5.4-mini`` + while the routed group is the canonical ``gpt-5.4-mini``: same group, different + spelling. A name compare (``originating.model_name != model``) would read this as + a tier change and strip the reasoning it did not have to. Because the origin's id + is a member of the routed group, this is a same-group cooldown instead: the request + fails fast and the encrypted reasoning is left intact. + """ + from litellm.exceptions import ServiceUnavailableError + from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import ( + EncryptedContentAffinityCheck, + ) + + originating = _make_originating_mock(None, "key-a", model_name="openai/gpt-5.4-mini") + mock_router = _make_router_mock_with_cooldown( + originating, cooldown_entries=[], routed_group_model_ids=["deployment-mini-a", "deployment-mini-b"] + ) + check = EncryptedContentAffinityCheck(router=mock_router) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-mini-a") + sibling_pool = [ + { + "model_info": {"id": "deployment-mini-b"}, + "model_name": "gpt-5.4-mini", + "litellm_params": { + "api_base": "https://gateway.example/v1", + "api_key": "key-b", + "model": "openai/gpt-5.4-mini", + }, + } + ] + request_kwargs = { + "litellm_metadata": {}, + "input": [ + {"role": "user", "content": "why is the sky blue?"}, + { + "type": "reasoning", + "encrypted_content": wrapped, + "summary": [{"type": "summary_text", "text": "scattering"}], + }, + {"role": "user", "content": "and sunsets?"}, + ], + } + + with pytest.raises(ServiceUnavailableError): + await check.async_filter_deployments( + model="gpt-5.4-mini", + healthy_deployments=sibling_pool, + messages=None, + request_kwargs=request_kwargs, + ) + + assert request_kwargs["input"][1].get("encrypted_content") + + +@pytest.mark.asyncio +async def test_affinity_honors_router_candidate_ids_for_team_and_pattern_routes(): + """ + The exact `model_name` index does not include team-public or pattern routes, so a + same-group cooldown reached only through one of those would be misread as a tier change + and stripped. The check asks the router for the candidate ids it resolves for the route + (`get_candidate_model_ids_for_route`), which covers those paths, rather than the bare + index. Here that set marks the origin as a candidate, so the request fails fast with its + reasoning intact, and the routed group and team are passed through to the router. + """ + from litellm.exceptions import ServiceUnavailableError + from litellm.router_utils.pre_call_checks.encrypted_content_affinity_check import ( + EncryptedContentAffinityCheck, + ) + + originating = _make_originating_mock(None, "key-a", model_name="model_name_teamA_uuid") + mock_router = _make_router_mock_with_cooldown( + originating, cooldown_entries=[], routed_group_model_ids=["deployment-team-a", "deployment-team-b"] + ) + check = EncryptedContentAffinityCheck(router=mock_router) + wrapped = ResponsesAPIRequestUtils._wrap_encrypted_content_with_model_id("gAAAAA-blob", "deployment-team-a") + sibling_pool = [ + { + "model_info": {"id": "deployment-team-b"}, + "model_name": "team-public-model", + "litellm_params": { + "api_base": "https://gateway.example/v1", + "api_key": "key-b", + "model": "openai/gpt-5.4-mini", + }, + } + ] + request_kwargs = { + "litellm_metadata": {"user_api_key_team_id": "teamA"}, + "input": [ + {"role": "user", "content": "why is the sky blue?"}, + { + "type": "reasoning", + "encrypted_content": wrapped, + "summary": [{"type": "summary_text", "text": "scattering"}], + }, + {"role": "user", "content": "and sunsets?"}, + ], + } + + with pytest.raises(ServiceUnavailableError): + await check.async_filter_deployments( + model="team-public-model", + healthy_deployments=sibling_pool, + messages=None, + request_kwargs=request_kwargs, + ) + + assert request_kwargs["input"][1].get("encrypted_content") + mock_router.get_candidate_model_ids_for_route.assert_called_once_with(model="team-public-model", team_id="teamA") diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index bc79c5f6589..80da922724d 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -14728,3 +14728,45 @@ def test_router_stays_quiet_when_a_deployment_drop_params_is_a_flag(value, caplo ) assert "is not a flag value" not in caplog.text + + +def test_get_candidate_model_ids_for_route_covers_model_name_and_pattern(): + """ + get_candidate_model_ids_for_route resolves a route the way the router does, so a + pre-call check can tell a genuine cross-group route from same-group unavailability. + A concrete model group returns its member ids; a wildcard/pattern deployment is + included for a concrete model it matches, which the bare model_name index misses. + Regression guard for the LIT-7195 tier-change discriminator's team/pattern gaps. + """ + router = Router( + model_list=[ + { + "model_name": "grp", + "litellm_params": {"model": "openai/gpt-4o", "api_key": "sk-a", "api_base": "https://x.invalid"}, + "model_info": {"id": "dep-a"}, + }, + { + "model_name": "grp", + "litellm_params": {"model": "openai/gpt-4o-mini", "api_key": "sk-b", "api_base": "https://x.invalid"}, + "model_info": {"id": "dep-b"}, + }, + { + "model_name": "openai/*", + "litellm_params": {"model": "openai/*", "api_key": "sk-c", "api_base": "https://x.invalid"}, + "model_info": {"id": "dep-wild"}, + }, + ] + ) + + assert router.get_candidate_model_ids_for_route(model="grp") == frozenset({"dep-a", "dep-b"}) + assert "dep-wild" in router.get_candidate_model_ids_for_route(model="openai/gpt-4o-some-new-model") + + +def test_deployment_ids_stringifies_ids_and_skips_entries_without_a_model_info_id(): + deployments = ( + {"model_info": {"id": "a"}}, + {"model_info": {"id": 2}}, + {"model_info": {}}, + {"no_model_info": True}, + ) + assert Router._deployment_ids(deployments) == frozenset({"a", "2"}) From 096984bfc2f146e6e4bb6c259e8daab885a5e5f6 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:19:29 -0700 Subject: [PATCH 103/105] fix(proxy): pin multi-root CA bundle to the server's root before handing it to Prisma (#40428) Co-authored-by: yassin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/db/db_url_settings.py | 107 +++++++++- .../proxy/db/test_db_url_settings.py | 199 ++++++++++++++++-- 2 files changed, 282 insertions(+), 24 deletions(-) diff --git a/litellm/proxy/db/db_url_settings.py b/litellm/proxy/db/db_url_settings.py index 4a0231ad9df..d1e4b3e92b8 100644 --- a/litellm/proxy/db/db_url_settings.py +++ b/litellm/proxy/db/db_url_settings.py @@ -34,12 +34,20 @@ writer's connection params (pool size, timeouts, pgbouncer mode) for the ones the reader URL does not pin itself. """ +import _ssl +import hashlib import os +import socket +import ssl +import struct +import sys +import tempfile import urllib.parse -from collections.abc import Mapping +from collections.abc import Callable, Mapping, Sequence from functools import partial +from pathlib import Path from types import MappingProxyType -from typing import Annotated, Final, cast +from typing import Annotated, Final, Protocol, TypeAlias, cast from pydantic import AliasChoices, BeforeValidator, Field from pydantic_settings import BaseSettings, SettingsConfigDict @@ -126,21 +134,100 @@ def add_missing_query_params(url: str, params: Mapping[str, str | int | float]) LIBPQ_VERIFY_SSLMODES: Final[frozenset[str]] = frozenset({"verify-ca", "verify-full"}) +PEM_CERT_HEADER: Final = b"-----BEGIN CERTIFICATE-----" +PG_SSL_REQUEST: Final = struct.pack("!ii", 8, 80877103) +TLS_PROBE_TIMEOUT_SECONDS: Final = 10.0 + +RootCertResolver: TypeAlias = Callable[[str, str, int], str] # mutable-ok: Callable parameter syntax -def translate_libpq_ssl_params(url: str) -> str: +class _VerifiedChainSource(Protocol): + def get_verified_chain(self) -> Sequence[_ssl.Certificate] | None: ... + + +def _verified_chain_der(tls: ssl.SSLSocket) -> tuple[bytes, ...]: + if sys.version_info >= (3, 13): + return tuple(tls.get_verified_chain()) + legacy: Final = cast( # cast-ok: the stub omits _sslobj, the C object has get_verified_chain since 3.10 + "_VerifiedChainSource | None", + tls._sslobj, # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType] # public API only from 3.13 + ) + chain: Final = () if legacy is None else legacy.get_verified_chain() or () + return tuple(cert.public_bytes(_ssl.ENCODING_DER) for cert in chain) + + +def _server_trust_anchor(cafile: str, host: str, port: int) -> bytes | None: + try: + context: Final = ssl.create_default_context(cafile=cafile) + with socket.create_connection((host, port), timeout=TLS_PROBE_TIMEOUT_SECONDS) as raw: + raw.sendall(PG_SSL_REQUEST) + if raw.recv(1) != b"S": + return None + with context.wrap_socket(raw, server_hostname=host) as tls: + chain: Final = _verified_chain_der(tls) + except (OSError, ValueError): + return None + return chain[-1] if chain else None + + +def pin_bundle_root(cert_path: str, host: str, port: int) -> str: + """Reduce a multi-root CA bundle to the one root that verifies ``host``. + + Prisma's ``sslcert`` loads a single PEM certificate (native-tls + ``Certificate::from_pem``), so pointing it at a bundle such as the AWS RDS + global bundle trusts only the first of its 108 regional roots and the + handshake fails with "unable to get local issuer certificate" for every + other region. A single-certificate file is returned as is. For a bundle, + one verifying handshake (chain and hostname, whole bundle as trust store) + identifies the trust anchor the server actually chains to, which is + written to a single-certificate file for Prisma. If the probe fails the + bundle path is returned unchanged, so Prisma fails closed exactly as + before rather than trusting anything the bundle would not. + """ + try: + if Path(cert_path).read_bytes().count(PEM_CERT_HEADER) < 2: + return cert_path + except OSError: + return cert_path + root: Final = _server_trust_anchor(cert_path, host, port) + if root is None: + return cert_path + pinned: Final = Path(tempfile.gettempdir()) / f"litellm-sslcert-{hashlib.sha256(root).hexdigest()[:16]}.pem" + return str(pinned) if _replace_file(pinned, ssl.DER_cert_to_PEM_cert(root)) else cert_path + + +def _replace_file(target: Path, content: str) -> bool: + """Write ``content`` to a private temp file and rename it over ``target``, so + readers never see a partial file and a symlink planted at ``target`` is + replaced rather than followed.""" + try: + fd, staged = tempfile.mkstemp(dir=target.parent, prefix=f"{target.name}.") + except OSError: + return False + try: + with os.fdopen(fd, "w") as handle: + handle.write(content) + os.replace(staged, target) + except OSError: + Path(staged).unlink(missing_ok=True) + return False + return True + + +def translate_libpq_ssl_params(url: str, resolve_root_cert: RootCertResolver = pin_bundle_root) -> str: """Rewrite libpq's certificate-verification params into Prisma's dialect. Prisma's engine only knows ``sslmode=disable|prefer|require``, ``sslcert`` - (the CA bundle) and ``sslaccept=strict``. It silently discards + (a single CA certificate) and ``sslaccept=strict``. It silently discards ``sslrootcert`` and downgrades ``sslmode=verify-ca`` / ``verify-full`` to ``prefer``, so a URL copied from libpq / RDS docs connects over TLS with no certificate check at all. ``verify-ca`` and ``verify-full`` both become ``require`` (Prisma has no CA-only mode), ``sslrootcert`` becomes - ``sslcert``, and either one turns on ``sslaccept=strict`` (chain and - hostname), matching libpq where a root cert makes ``require`` verify. - Prisma params the operator pinned themselves win; anything else is left - untouched. + ``sslcert`` (run through ``resolve_root_cert``, which pins a multi-root + bundle down to the server's root), and either one turns on + ``sslaccept=strict`` (chain and hostname), matching libpq where a root + cert makes ``require`` verify. Prisma params the operator pinned + themselves win; anything else is left untouched. """ parsed: Final = urllib.parse.urlsplit(url) pairs: Final = tuple(urllib.parse.parse_qsl(parsed.query, keep_blank_values=True)) @@ -154,7 +241,9 @@ def translate_libpq_ssl_params(url: str) -> str: if key != "sslrootcert" ) root_cert: Final = tuple( - ("sslcert", value) for key, value in pairs if key == "sslrootcert" and "sslcert" not in keys + ("sslcert", resolve_root_cert(value, parsed.hostname or "", parsed.port or int(DEFAULT_POSTGRES_PORT))) + for key, value in pairs + if key == "sslrootcert" and "sslcert" not in keys ) strict: Final = () if "sslaccept" in keys else (("sslaccept", "strict"),) query: Final = urllib.parse.urlencode(translated + root_cert + strict) diff --git a/tests/test_litellm/proxy/db/test_db_url_settings.py b/tests/test_litellm/proxy/db/test_db_url_settings.py index ba342342366..875dca4bee3 100644 --- a/tests/test_litellm/proxy/db/test_db_url_settings.py +++ b/tests/test_litellm/proxy/db/test_db_url_settings.py @@ -11,15 +11,30 @@ clobber a pre-existing ``DATABASE_URL_READ_REPLICA``. A pre-existing ``DATABASE_URL`` (password auth) is likewise left untouched. """ +import datetime +import hashlib import os +import socket +import ssl +import tempfile +import threading import urllib.parse +from collections.abc import Iterator +from dataclasses import dataclass +from pathlib import Path +from typing import Final from unittest.mock import patch import pytest +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec from pydantic import ValidationError from litellm.proxy.db.db_url_settings import ( + PG_SSL_REQUEST, DatabaseURLSettings, + translate_libpq_ssl_params, unsupported_db_scheme, unsupported_db_scheme_message, ) @@ -381,9 +396,7 @@ def test_writer_password_is_percent_encoded(monkeypatch): def test_writer_url_not_clobbered_when_already_set(monkeypatch): """An operator-pinned DATABASE_URL (e.g. helm's $(VAR) assembly) always wins over the discrete fields.""" - monkeypatch.setenv( - "DATABASE_URL", "postgresql://pinned:url@db.example.com:5432/litellm_db" - ) + monkeypatch.setenv("DATABASE_URL", "postgresql://pinned:url@db.example.com:5432/litellm_db") monkeypatch.setenv("DATABASE_HOST", "writer.example.com") monkeypatch.setenv("DATABASE_USER", "litellm") monkeypatch.setenv("DATABASE_NAME", "litellm_db") @@ -515,9 +528,7 @@ def test_apply_to_env_rejects_pinned_sqlite_direct_url(monkeypatch): def test_apply_to_env_rejects_pinned_non_postgres_reader(monkeypatch): monkeypatch.setenv("DATABASE_URL", "postgresql://u:p@writer.example.com:5432/db") - monkeypatch.setenv( - "DATABASE_URL_READ_REPLICA", "mysql://u:p@reader.example.com:3306/db" - ) + monkeypatch.setenv("DATABASE_URL_READ_REPLICA", "mysql://u:p@reader.example.com:3306/db") with pytest.raises(RuntimeError, match=r"DATABASE_URL_READ_REPLICA.*mysql"): _apply() @@ -542,15 +553,11 @@ def test_reader_inherits_writer_connection_params(monkeypatch): "DATABASE_URL", "postgresql://u:p@writer.example.com:5432/db?connection_limit=3&pool_timeout=20&pgbouncer=true", ) - monkeypatch.setenv( - "DATABASE_URL_READ_REPLICA", "postgresql://u:p@reader.example.com:5432/db" - ) + monkeypatch.setenv("DATABASE_URL_READ_REPLICA", "postgresql://u:p@reader.example.com:5432/db") _apply() - query = urllib.parse.parse_qs( - urllib.parse.urlsplit(os.environ["DATABASE_URL_READ_REPLICA"]).query - ) + query = urllib.parse.parse_qs(urllib.parse.urlsplit(os.environ["DATABASE_URL_READ_REPLICA"]).query) assert query["connection_limit"] == ["3"] assert query["pool_timeout"] == ["20"] assert query["pgbouncer"] == ["true"] @@ -568,9 +575,7 @@ def test_reader_keeps_its_own_pinned_connection_params(monkeypatch): _apply() - query = urllib.parse.parse_qs( - urllib.parse.urlsplit(os.environ["DATABASE_URL_READ_REPLICA"]).query - ) + query = urllib.parse.parse_qs(urllib.parse.urlsplit(os.environ["DATABASE_URL_READ_REPLICA"]).query) assert query["connection_limit"] == ["50"] assert query["pool_timeout"] == ["20"] @@ -776,6 +781,170 @@ def test_libpq_verify_full_and_sslrootcert_become_prisma_strict_sslcert(monkeypa } +def _issue_cert( + subject: str, issuer: x509.Certificate | None, issuer_key: ec.EllipticCurvePrivateKey | None, ca: bool +) -> tuple[x509.Certificate, ec.EllipticCurvePrivateKey]: + key: Final = ec.generate_private_key(ec.SECP256R1()) + name: Final = x509.Name((x509.NameAttribute(x509.NameOID.COMMON_NAME, subject),)) + now: Final = datetime.datetime.now(datetime.timezone.utc) + builder: Final = ( + x509.CertificateBuilder() + .subject_name(name) + .issuer_name(issuer.subject if issuer else name) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now - datetime.timedelta(minutes=5)) + .not_valid_after(now + datetime.timedelta(days=1)) + .add_extension(x509.BasicConstraints(ca=ca, path_length=None), critical=True) + .add_extension(x509.SubjectAlternativeName((x509.DNSName("localhost"),)), critical=False) + ) + return builder.sign(issuer_key or key, hashes.SHA256()), key + + +def _pem(cert: x509.Certificate) -> bytes: + return cert.public_bytes(serialization.Encoding.PEM) + + +class _TlsPostgresStub: + """Answers one libpq ``SSLRequest`` with ``S`` and serves ``leaf + intermediate``.""" + + def __init__(self, chain_pem: Path, key_pem: Path) -> None: + self.context: Final = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.context.load_cert_chain(str(chain_pem), str(key_pem)) + self.listener: Final = socket.create_server(("127.0.0.1", 0)) + self.port: Final[int] = self.listener.getsockname()[1] + self.thread: Final = threading.Thread(target=self._serve, daemon=True) + self.thread.start() + + def _serve(self) -> None: + with self.listener: + while True: + try: + conn: socket.socket = self.listener.accept()[0] + except OSError: + return + with conn: + try: + if conn.recv(8) == PG_SSL_REQUEST: + conn.sendall(b"S") + with self.context.wrap_socket(conn, server_side=True) as tls: + tls.recv(1) + except OSError: + continue + + +@dataclass(frozen=True, slots=True) +class _RdsLikePki: + bundle: Path + wrong_bundle: Path + root: Path + port: int + + +@pytest.fixture +def rds_like_pki(tmp_path: Path) -> Iterator[_RdsLikePki]: + """An RDS-shaped trust setup: the server sends leaf + intermediate, the + bundle holds only self-signed roots, and the right root is not first.""" + root, root_key = _issue_cert("Real Root CA", None, None, ca=True) + decoys: Final = tuple(_issue_cert(f"Decoy Root CA {i}", None, None, ca=True)[0] for i in range(3)) + intermediate, intermediate_key = _issue_cert("Intermediate CA", root, root_key, ca=True) + leaf, leaf_key = _issue_cert("localhost", intermediate, intermediate_key, ca=False) + chain_pem: Final = tmp_path / "server-chain.pem" + chain_pem.write_bytes(_pem(leaf) + _pem(intermediate)) + key_pem: Final = tmp_path / "server.key" + key_pem.write_bytes( + leaf_key.private_bytes( + serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption() + ) + ) + bundle: Final = tmp_path / "global-bundle.pem" + bundle.write_bytes(b"".join(_pem(decoy) for decoy in decoys) + _pem(root)) + wrong_bundle: Final = tmp_path / "wrong-bundle.pem" + wrong_bundle.write_bytes(b"".join(_pem(decoy) for decoy in decoys)) + root_pem: Final = tmp_path / "root.pem" + root_pem.write_bytes(_pem(root)) + stub: Final = _TlsPostgresStub(chain_pem, key_pem) + yield _RdsLikePki(bundle=bundle, wrong_bundle=wrong_bundle, root=root_pem, port=stub.port) + stub.listener.close() + + +def _params(url: str) -> tuple[tuple[str, str], ...]: + return tuple(urllib.parse.parse_qsl(urllib.parse.urlsplit(url).query, keep_blank_values=True)) + + +def test_multi_root_bundle_is_pinned_to_the_root_the_server_chains_to( + monkeypatch: pytest.MonkeyPatch, rds_like_pki: _RdsLikePki +): + """Prisma's ``sslcert`` loads only the first certificate of the file, so + handing it the whole RDS bundle trusts one region's root and fails with + "unable to get local issuer certificate" everywhere else. The URL Prisma + receives must point at a single-certificate file holding the server's root.""" + monkeypatch.setenv( + "DATABASE_URL", + f"postgresql://u:p@localhost:{rds_like_pki.port}/litellm_db?sslmode=verify-full&sslrootcert={rds_like_pki.bundle}", + ) + + _apply() + + (sslmode, sslcert, sslaccept, _) = _params(os.environ["DATABASE_URL"]) + assert (sslmode, sslaccept) == (("sslmode", "require"), ("sslaccept", "strict")) + assert sslcert[0] == "sslcert" and sslcert[1] != str(rds_like_pki.bundle) + assert Path(sslcert[1]).read_bytes() == rds_like_pki.root.read_bytes() + + +def test_pinned_root_replaces_a_planted_symlink_instead_of_writing_through_it( + monkeypatch: pytest.MonkeyPatch, rds_like_pki: _RdsLikePki, tmp_path: Path +): + """The pinned file has a predictable name in a shared temp dir, so a symlink + planted there must not redirect the write onto its target.""" + monkeypatch.setattr(tempfile, "tempdir", str(tmp_path)) + root_der: Final = x509.load_pem_x509_certificate(rds_like_pki.root.read_bytes()).public_bytes( + serialization.Encoding.DER + ) + pinned: Final = tmp_path / f"litellm-sslcert-{hashlib.sha256(root_der).hexdigest()[:16]}.pem" + victim: Final = tmp_path / "victim.txt" + victim.write_text("untouched") + pinned.symlink_to(victim) + monkeypatch.setenv( + "DATABASE_URL", + f"postgresql://u:p@localhost:{rds_like_pki.port}/litellm_db?sslmode=verify-full&sslrootcert={rds_like_pki.bundle}", + ) + + _apply() + + assert ("sslcert", str(pinned)) in _params(os.environ["DATABASE_URL"]) + assert victim.read_text() == "untouched" + assert not pinned.is_symlink() and pinned.read_bytes() == rds_like_pki.root.read_bytes() + + +def test_bundle_without_the_servers_root_is_passed_through_unchanged( + monkeypatch: pytest.MonkeyPatch, rds_like_pki: _RdsLikePki +): + """Nothing in the bundle verifies the server, so no root is pinned and + Prisma keeps rejecting the connection instead of trusting a root the + operator never shipped.""" + monkeypatch.setenv( + "DATABASE_URL", + f"postgresql://u:p@localhost:{rds_like_pki.port}/litellm_db" + f"?sslmode=verify-full&sslrootcert={rds_like_pki.wrong_bundle}", + ) + + _apply() + + assert ("sslcert", str(rds_like_pki.wrong_bundle)) in _params(os.environ["DATABASE_URL"]) + + +def test_root_cert_resolver_receives_the_urls_host_and_default_port(): + def resolver(cert_path: str, host: str, port: int) -> str: + return f"/pinned/{host}/{port}{cert_path}" + + url: Final = translate_libpq_ssl_params( + "postgresql://u:p@db.example.com/litellm_db?sslmode=verify-full&sslrootcert=/certs/bundle.pem", resolver + ) + + assert ("sslcert", "/pinned/db.example.com/5432/certs/bundle.pem") in _params(url) + + def test_libpq_verify_ca_becomes_prisma_strict(monkeypatch): monkeypatch.setenv("DATABASE_URL", "postgresql://u:p@db.example.com:5432/litellm_db?sslmode=verify-ca") From 34d2d010c3f0f08ab9c13700c7004d98377309fa Mon Sep 17 00:00:00 2001 From: kerry Date: Wed, 9 Sep 2026 19:33:59 +0000 Subject: [PATCH 104/105] test(cli): drop lite e2e tests, the e2e runner does not install the package Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/e2e/coverage_registry/other.yaml | 2 - tests/e2e/other/test_cli_cost_map_e2e.py | 104 ----------------------- 2 files changed, 106 deletions(-) delete mode 100644 tests/e2e/other/test_cli_cost_map_e2e.py diff --git a/tests/e2e/coverage_registry/other.yaml b/tests/e2e/coverage_registry/other.yaml index a7ba0dbb8cb..814ebae2e0b 100644 --- a/tests/e2e/coverage_registry/other.yaml +++ b/tests/e2e/coverage_registry/other.yaml @@ -48,5 +48,3 @@ - {id: other.a2a.message_send.bridge_invokes, module: other, tier: P1, area: a2a, assertions: [bridge_invokes], source: "a2a_protocol/litellm_completion_bridge/handler.py", rationale: "A2A message/send routes through the completion bridge to a real provider and logs an asend_message spend row"} - {id: other.a2a.version.serves_pinned_0_3, module: other, tier: P1, area: a2a, assertions: [serves_pinned_0_3], source: "agent_endpoints/a2a_endpoints.py _served_version", rationale: "An agent pinning 0.3 returns the flat 0.3 message shape (parts on the result)"} - {id: other.a2a.version.serves_pinned_1_0, module: other, tier: P1, area: a2a, assertions: [serves_pinned_1_0], source: "agent_endpoints/a2a_endpoints.py _served_version", rationale: "An agent pinning 1.0 returns the nested 1.0 message shape (result.message with ROLE_AGENT)"} -- {id: other.cli.model_cost_map.version_skips_fetch, module: other, tier: P1, area: cli, assertions: [version_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite version command succeeds without requesting the remote model-cost map"} -- {id: other.cli.model_cost_map.models_list_skips_fetch, module: other, tier: P1, area: cli, assertions: [models_list_skips_fetch], source: "get_model_cost_map.py _is_cli_process / LIT-7385", fail_before_fix: proven, rationale: "The lite models list command uses the proxy without requesting the remote model-cost map"} diff --git a/tests/e2e/other/test_cli_cost_map_e2e.py b/tests/e2e/other/test_cli_cost_map_e2e.py deleted file mode 100644 index 62cd3d6c3f5..00000000000 --- a/tests/e2e/other/test_cli_cost_map_e2e.py +++ /dev/null @@ -1,104 +0,0 @@ -from __future__ import annotations - -import os -import shutil -import subprocess -import threading -from collections.abc import Mapping -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path -from typing import Final - -import pytest -from e2e_config import MASTER_KEY, PROXY_BASE_URL -from proxy_client import ProxyClient - -pytestmark = pytest.mark.e2e - - -def _start_cost_map_server(request_log: Path) -> tuple[ThreadingHTTPServer, threading.Thread]: - class CostMapHandler(BaseHTTPRequestHandler): - def do_GET(self) -> None: - with request_log.open("a", encoding="utf-8") as log_file: - log_file.write(f"{self.path}\n") - body: Final = b'{"test-model": {"litellm_provider": "openai", "mode": "chat"}}' - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(body))) - self.end_headers() - self.wfile.write(body) - - def log_message(self, format: str, *args: object) -> None: - return - - server: Final = ThreadingHTTPServer(("127.0.0.1", 0), CostMapHandler) - thread: Final = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() - return server, thread - - -def _lite_env(server: ThreadingHTTPServer, api_key: str | None) -> dict[str, str]: - base_env: Final = {key: value for key, value in os.environ.items() if key != "LITELLM_LOCAL_MODEL_COST_MAP"} - return { - **base_env, - "LITELLM_MODEL_COST_MAP_URL": f"http://127.0.0.1:{server.server_port}/map.json", - "LITELLM_PROXY_URL": PROXY_BASE_URL, - **({"LITELLM_PROXY_API_KEY": api_key} if api_key is not None else {}), - } - - -def _run_lite( - args: tuple[str, ...], - server: ThreadingHTTPServer, - env: Mapping[str, str], -) -> subprocess.CompletedProcess[str]: - lite_path: Final = shutil.which("lite") - assert lite_path is not None, "the installed lite executable is required for e2e coverage" - try: - return subprocess.run( - [lite_path, *args], - capture_output=True, - text=True, - timeout=60, - env=env, - ) - finally: - server.shutdown() - server.server_close() - - -def _request_count(request_log: Path) -> int: - return request_log.read_text(encoding="utf-8").count("\n") if request_log.exists() else 0 - - -class TestLiteCliCostMapFetch: - @pytest.mark.covers("other.cli.model_cost_map.version_skips_fetch") - def test_lite_version_makes_no_cost_map_request(self, tmp_path: Path) -> None: - request_log: Final = tmp_path / "requests.log" - server, thread = _start_cost_map_server(request_log) - env: Final = _lite_env(server, None) - try: - result: Final = _run_lite(("--version",), server, env) - finally: - thread.join(timeout=10) - - assert result.returncode == 0 - assert "LiteLLM Proxy CLI Version" in result.stdout - assert _request_count(request_log) == 0 - - @pytest.mark.covers("other.cli.model_cost_map.models_list_skips_fetch") - def test_lite_models_list_uses_proxy_not_cost_map(self, tmp_path: Path, proxy: ProxyClient) -> None: - model_names: Final = tuple(entry.model_name for entry in proxy.model_info()) - assert model_names - request_log: Final = tmp_path / "requests.log" - server, thread = _start_cost_map_server(request_log) - env: Final = _lite_env(server, MASTER_KEY) - try: - result: Final = _run_lite(("models", "list"), server, env) - finally: - thread.join(timeout=10) - - assert result.returncode == 0 - assert result.stdout.strip() - assert any(model_name in result.stdout for model_name in model_names) - assert _request_count(request_log) == 0 From 8bb6d8c120e2c116a719495ecf2401a2bab9dd5a Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:34:08 -0700 Subject: [PATCH 105/105] chore(lint): bring ANN202 and BLE001 back under the strict-rule budget Annotate the return types of dispatch_async and transform_then_dispatch in llm_http_handler and _send_batch in azure_sentinel, and mark four legitimate broad catches with the repo's noqa convention, so the promote PR's lint job passes the strict gate again. Supersedes #40328. --- litellm/integrations/azure_sentinel/azure_sentinel.py | 4 +++- litellm/litellm_core_utils/get_model_cost_map.py | 2 +- litellm/litellm_core_utils/llm_cost_calc/utils.py | 2 +- litellm/llms/custom_httpx/llm_http_handler.py | 4 ++-- litellm/proxy/_experimental/mcp_server/server.py | 2 +- litellm/proxy/management_endpoints/cost_tracking_settings.py | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/litellm/integrations/azure_sentinel/azure_sentinel.py b/litellm/integrations/azure_sentinel/azure_sentinel.py index eba6c862f7a..db5f790615f 100644 --- a/litellm/integrations/azure_sentinel/azure_sentinel.py +++ b/litellm/integrations/azure_sentinel/azure_sentinel.py @@ -21,6 +21,8 @@ from types import MappingProxyType from typing import Final, TypeVar from urllib.parse import urlparse +import httpx + from litellm._logging import verbose_logger from litellm.integrations.batch_utils import ( BatchSendCancelled, @@ -418,7 +420,7 @@ class AzureSentinelLogger(CustomBatchLogger): "Content-Type": "application/json", } - async def _send_batch(batch: Sequence[_QueuedPayload]): + async def _send_batch(batch: Sequence[_QueuedPayload]) -> httpx.Response: body: Final = safe_dumps(batch) return await self.async_httpx_client.post( url=api_endpoint, diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index f81ddbfee2e..37375f25e46 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -585,7 +585,7 @@ def _retry_remote_fetch_in_background( _cost_map_source_info.fallback_reason = None _cost_map_source_info.loaded_at = datetime.now(timezone.utc) adopt_model_cost_map(finalized) - except Exception as e: + except Exception as e: # noqa: BLE001 # a failed background retry must not kill the task; the backup stays verbose_logger.warning("LiteLLM: Background model cost map retry failed: %s", e) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 5675c59733d..e5977ca4156 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -1444,7 +1444,7 @@ def get_billed_token_rates( return _custom_pricing_rates(custom_cost_per_token) try: model_info: Final = get_model_info(model=model, custom_llm_provider=custom_llm_provider) - except Exception: + except Exception: # noqa: BLE001 # get_model_info raises a bare Exception for an unmapped model: no rates return None return _cost_map_billed_rates( model_info=model_info, diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 7587b963a38..84b03e1955a 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -579,7 +579,7 @@ class BaseLLMHTTPHandler: data: dict[str, object], # mutable-ok: async_completion takes dict signed_headers: dict[str, object], # mutable-ok: async_completion takes dict signed_json_body: bytes | None, - ): + ) -> Coroutine[object, object, ModelResponse | CustomStreamWrapper]: async_client: Final = client if isinstance(client, AsyncHTTPHandler) else None if stream is True: return self.acompletion_stream_function( @@ -626,7 +626,7 @@ class BaseLLMHTTPHandler: if acompletion is True and provider_config.uses_async_transform_request: - async def transform_then_dispatch(): + async def transform_then_dispatch() -> ModelResponse | CustomStreamWrapper: transformed: Final = cast( # cast-ok: async_transform_request is declared as a bare dict "dict[str, object]", await provider_config.async_transform_request( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 9a52da0cab1..f8f1c563811 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -1053,7 +1053,7 @@ if MCP_AVAILABLE: route="/mcp/call_tool", traceback_str=failure_traceback, ) - except Exception: + except Exception: # noqa: BLE001 # a failing failure hook must not mask the tool call's own error verbose_logger.exception("Error logging failed MCP proxy tool call") raise if proxy_logging_obj is not None: diff --git a/litellm/proxy/management_endpoints/cost_tracking_settings.py b/litellm/proxy/management_endpoints/cost_tracking_settings.py index 493f75008c3..dc0da63555f 100644 --- a/litellm/proxy/management_endpoints/cost_tracking_settings.py +++ b/litellm/proxy/management_endpoints/cost_tracking_settings.py @@ -643,7 +643,7 @@ async def estimate_cost( custom_cost_per_token=resolved.custom_cost_per_token, litellm_logging_obj=litellm_logging_obj, ) - except Exception as e: + except Exception as e: # noqa: BLE001 # completion_cost raises a bare Exception for an unpriceable model raise HTTPException( status_code=404, detail={