codexLB-up 1.25.0b10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +10 -0
- app/admin_cli.py +494 -0
- app/cli.py +339 -0
- app/codex_sessions_retag.py +487 -0
- app/core/__init__.py +0 -0
- app/core/audit/__init__.py +0 -0
- app/core/audit/service.py +216 -0
- app/core/audit/types.py +94 -0
- app/core/auth/__init__.py +236 -0
- app/core/auth/api_key_cache.py +65 -0
- app/core/auth/dashboard_access.py +429 -0
- app/core/auth/dashboard_mode.py +171 -0
- app/core/auth/dashboard_session_ttl.py +74 -0
- app/core/auth/dashboard_users_cache.py +120 -0
- app/core/auth/dependencies.py +710 -0
- app/core/auth/external_identity.py +78 -0
- app/core/auth/guardian.py +374 -0
- app/core/auth/models.py +49 -0
- app/core/auth/providers/__init__.py +129 -0
- app/core/auth/providers/oidc.py +838 -0
- app/core/auth/providers/registry.py +118 -0
- app/core/auth/refresh.py +394 -0
- app/core/auth/step_up.py +117 -0
- app/core/auth/totp.py +81 -0
- app/core/balancer/__init__.py +71 -0
- app/core/balancer/logic.py +1612 -0
- app/core/balancer/rendezvous_hash.py +21 -0
- app/core/balancer/types.py +21 -0
- app/core/bootstrap.py +172 -0
- app/core/cache/invalidation.py +426 -0
- app/core/clients/__init__.py +0 -0
- app/core/clients/codex.py +996 -0
- app/core/clients/codex_version.py +187 -0
- app/core/clients/codex_version_snapshot.py +2 -0
- app/core/clients/files.py +534 -0
- app/core/clients/headers.py +29 -0
- app/core/clients/http.py +511 -0
- app/core/clients/model_fetcher.py +276 -0
- app/core/clients/native_egress.py +1304 -0
- app/core/clients/oauth.py +477 -0
- app/core/clients/proxy.py +6089 -0
- app/core/clients/proxy_websocket.py +1411 -0
- app/core/clients/rate_limit_reset_credits.py +412 -0
- app/core/clients/stream_errors.py +12 -0
- app/core/clients/thread_cache_identity.py +347 -0
- app/core/clients/usage.py +467 -0
- app/core/clock.py +170 -0
- app/core/config/__init__.py +0 -0
- app/core/config/background_jobs.py +73 -0
- app/core/config/context_window_overrides.py +166 -0
- app/core/config/dashboard_overrides.py +145 -0
- app/core/config/inheritable.py +47 -0
- app/core/config/key_fingerprint.py +115 -0
- app/core/config/settings.py +872 -0
- app/core/config/settings_cache.py +100 -0
- app/core/config/spool_retention.py +217 -0
- app/core/config/tiers.py +174 -0
- app/core/conversation_archive.py +568 -0
- app/core/crypto.py +37 -0
- app/core/draining.py +11 -0
- app/core/errors.py +365 -0
- app/core/exceptions.py +155 -0
- app/core/handlers/__init__.py +3 -0
- app/core/handlers/exceptions.py +478 -0
- app/core/http_protocol.py +176 -0
- app/core/http_protocol_httptools.py +204 -0
- app/core/ingress_limits.py +17 -0
- app/core/metadata_store.py +18 -0
- app/core/metrics/__init__.py +0 -0
- app/core/metrics/middleware.py +74 -0
- app/core/metrics/prometheus.py +604 -0
- app/core/middleware/__init__.py +25 -0
- app/core/middleware/api_firewall.py +111 -0
- app/core/middleware/app_version.py +37 -0
- app/core/middleware/dashboard_auth_proxy.py +55 -0
- app/core/middleware/dashboard_csrf.py +177 -0
- app/core/middleware/dashboard_gzip.py +41 -0
- app/core/middleware/dashboard_overrides.py +59 -0
- app/core/middleware/firewall_cache.py +91 -0
- app/core/middleware/inflight.py +126 -0
- app/core/middleware/multipart_content_encoding.py +129 -0
- app/core/middleware/path_rewrite.py +148 -0
- app/core/middleware/request_body_limit.py +196 -0
- app/core/middleware/request_decompression.py +209 -0
- app/core/middleware/request_id.py +72 -0
- app/core/middleware/required_capability_http.py +116 -0
- app/core/middleware/trusted_proxy_headers.py +36 -0
- app/core/multipart.py +237 -0
- app/core/multipart_fields.py +91 -0
- app/core/openai/__init__.py +0 -0
- app/core/openai/chat_requests.py +493 -0
- app/core/openai/chat_responses.py +931 -0
- app/core/openai/contracts.py +46 -0
- app/core/openai/exceptions.py +24 -0
- app/core/openai/host_models.py +14 -0
- app/core/openai/images.py +413 -0
- app/core/openai/message_coercion.py +376 -0
- app/core/openai/model_refresh_scheduler.py +423 -0
- app/core/openai/model_registry.py +997 -0
- app/core/openai/model_registry_store.py +548 -0
- app/core/openai/models.py +172 -0
- app/core/openai/parsing.py +97 -0
- app/core/openai/requests.py +1980 -0
- app/core/openai/strict_schema.py +199 -0
- app/core/openai/tool_call_safety.py +99 -0
- app/core/openai/v1_requests.py +187 -0
- app/core/plan_types.py +90 -0
- app/core/prestop.py +202 -0
- app/core/rate_limiter/__init__.py +0 -0
- app/core/rate_limiter/db_rate_limiter.py +215 -0
- app/core/request_locality.py +473 -0
- app/core/resilience/__init__.py +0 -0
- app/core/resilience/backpressure.py +57 -0
- app/core/resilience/bulkhead.py +202 -0
- app/core/resilience/circuit_breaker.py +150 -0
- app/core/resilience/degradation.py +55 -0
- app/core/resilience/loop_lag_monitor.py +60 -0
- app/core/resilience/memory_monitor.py +137 -0
- app/core/resilience/network_recovery.py +256 -0
- app/core/resilience/overload.py +98 -0
- app/core/resilience/toggles.py +114 -0
- app/core/retention/__init__.py +0 -0
- app/core/retention/job.py +240 -0
- app/core/retention/scheduler.py +69 -0
- app/core/runtime_logging.py +601 -0
- app/core/scheduling/__init__.py +0 -0
- app/core/scheduling/leader_election.py +993 -0
- app/core/scheduling/leader_election_handle.py +25 -0
- app/core/server.py +140 -0
- app/core/shutdown.py +326 -0
- app/core/socket_peer.py +22 -0
- app/core/startup.py +41 -0
- app/core/timeout_invariants.py +347 -0
- app/core/tracing/__init__.py +0 -0
- app/core/tracing/otel.py +207 -0
- app/core/types.py +6 -0
- app/core/upstream_proxy/__init__.py +13 -0
- app/core/upstream_proxy/cache.py +130 -0
- app/core/upstream_proxy/resolver.py +189 -0
- app/core/upstream_proxy/types.py +104 -0
- app/core/usage/__init__.py +407 -0
- app/core/usage/depletion.py +109 -0
- app/core/usage/live_hub.py +41 -0
- app/core/usage/live_snapshots.py +174 -0
- app/core/usage/logs.py +212 -0
- app/core/usage/metadata_scheduler.py +118 -0
- app/core/usage/models.py +54 -0
- app/core/usage/pricing.py +576 -0
- app/core/usage/pricing_catalog.py +241 -0
- app/core/usage/pricing_snapshot.json +821 -0
- app/core/usage/quota.py +133 -0
- app/core/usage/refresh_policy.py +21 -0
- app/core/usage/refresh_scheduler.py +713 -0
- app/core/usage/reset_credits_refresh_scheduler.py +435 -0
- app/core/usage/types.py +168 -0
- app/core/utils/__init__.py +0 -0
- app/core/utils/json_guards.py +18 -0
- app/core/utils/locks.py +33 -0
- app/core/utils/masking.py +20 -0
- app/core/utils/proxy_env.py +104 -0
- app/core/utils/request_id.py +65 -0
- app/core/utils/retry.py +77 -0
- app/core/utils/shared_future.py +172 -0
- app/core/utils/sse.py +286 -0
- app/core/utils/time.py +24 -0
- app/db/__init__.py +0 -0
- app/db/account_identity_lock.py +56 -0
- app/db/alembic/__init__.py +1 -0
- app/db/alembic/env.py +70 -0
- app/db/alembic/revision_ids.py +43 -0
- app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
- app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
- app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
- app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
- app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
- app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
- app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
- app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
- app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
- app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
- app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
- app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
- app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
- app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
- app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
- app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
- app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
- app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
- app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
- app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
- app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
- app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
- app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
- app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
- app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
- app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
- app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
- app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
- app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
- app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
- app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
- app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
- app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
- app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
- app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
- app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
- app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
- app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
- app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
- app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
- app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
- app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
- app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
- app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
- app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
- app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
- app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
- app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
- app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
- app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
- app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
- app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
- app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
- app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
- app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
- app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
- app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
- app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
- app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
- app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
- app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
- app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
- app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
- app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
- app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
- app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
- app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
- app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
- app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
- app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
- app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
- app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
- app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
- app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
- app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
- app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
- app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
- app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
- app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
- app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
- app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
- app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
- app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
- app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
- app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
- app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
- app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
- app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
- app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
- app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
- app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
- app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
- app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
- app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
- app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
- app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
- app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
- app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
- app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
- app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
- app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
- app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
- app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
- app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
- app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
- app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
- app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
- app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
- app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
- app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
- app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
- app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
- app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
- app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
- app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
- app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
- app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
- app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
- app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
- app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
- app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
- app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
- app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
- app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
- app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
- app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
- app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
- app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
- app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
- app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
- app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
- app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
- app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
- app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
- app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
- app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
- app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
- app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
- app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
- app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
- app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
- app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
- app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
- app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
- app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
- app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
- app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
- app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
- app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
- app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
- app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
- app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
- app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
- app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
- app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
- app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
- app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
- app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
- app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
- app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
- app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
- app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
- app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
- app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
- app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
- app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
- app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
- app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
- app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
- app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
- app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
- app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
- app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
- app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
- app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
- app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
- app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
- app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
- app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
- app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
- app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
- app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
- app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
- app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
- app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
- app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
- app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
- app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
- app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
- app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
- app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
- app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
- app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
- app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
- app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
- app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
- app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
- app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
- app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
- app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
- app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
- app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
- app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
- app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
- app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
- app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
- app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
- app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
- app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
- app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
- app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
- app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
- app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
- app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
- app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
- app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
- app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
- app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
- app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
- app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
- app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
- app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
- app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
- app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
- app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
- app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
- app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
- app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
- app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
- app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
- app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
- app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
- app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
- app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
- app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
- app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
- app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
- app/db/alembic/versions/__init__.py +1 -0
- app/db/backup.py +64 -0
- app/db/migrate.py +1249 -0
- app/db/migration_lock.py +160 -0
- app/db/migration_url.py +18 -0
- app/db/models.py +3100 -0
- app/db/recover.py +244 -0
- app/db/session.py +1295 -0
- app/db/snapshot.py +46 -0
- app/db/sqlite_lock_retry.py +190 -0
- app/db/sqlite_utils.py +549 -0
- app/dependencies.py +462 -0
- app/main.py +1240 -0
- app/modules/__init__.py +0 -0
- app/modules/accounts/__init__.py +0 -0
- app/modules/accounts/api.py +426 -0
- app/modules/accounts/auth_manager.py +1439 -0
- app/modules/accounts/background_repository.py +160 -0
- app/modules/accounts/deletion.py +540 -0
- app/modules/accounts/mappers.py +654 -0
- app/modules/accounts/refresh_claims.py +352 -0
- app/modules/accounts/repository.py +1679 -0
- app/modules/accounts/schemas.py +308 -0
- app/modules/accounts/service.py +809 -0
- app/modules/accounts/usage_rollup.py +486 -0
- app/modules/accounts/usage_rollup_scheduler.py +75 -0
- app/modules/accounts/usage_time_rollup.py +1037 -0
- app/modules/accounts/usage_time_rollup_read.py +522 -0
- app/modules/api_keys/__init__.py +1 -0
- app/modules/api_keys/api.py +344 -0
- app/modules/api_keys/last_used_coalescer.py +216 -0
- app/modules/api_keys/limit_windows.py +41 -0
- app/modules/api_keys/repository.py +1200 -0
- app/modules/api_keys/reset_scheduler.py +85 -0
- app/modules/api_keys/schemas.py +135 -0
- app/modules/api_keys/service.py +2123 -0
- app/modules/audit/__init__.py +0 -0
- app/modules/audit/api.py +88 -0
- app/modules/audit/repository.py +74 -0
- app/modules/audit/schemas.py +33 -0
- app/modules/audit/service.py +71 -0
- app/modules/auth_providers/__init__.py +0 -0
- app/modules/auth_providers/api.py +127 -0
- app/modules/auth_providers/config.py +343 -0
- app/modules/auth_providers/repository.py +101 -0
- app/modules/auth_providers/schemas.py +88 -0
- app/modules/auth_providers/seed.py +105 -0
- app/modules/auth_providers/service.py +289 -0
- app/modules/automations/__init__.py +1 -0
- app/modules/automations/api.py +369 -0
- app/modules/automations/repository.py +1896 -0
- app/modules/automations/scheduler.py +107 -0
- app/modules/automations/schemas.py +158 -0
- app/modules/automations/service.py +2548 -0
- app/modules/cache_isolation_probe/__init__.py +0 -0
- app/modules/cache_isolation_probe/api.py +188 -0
- app/modules/cache_isolation_probe/prefix.py +144 -0
- app/modules/cache_isolation_probe/schemas.py +94 -0
- app/modules/cache_isolation_probe/sender.py +229 -0
- app/modules/cache_isolation_probe/service.py +525 -0
- app/modules/conversation_archive/__init__.py +1 -0
- app/modules/conversation_archive/api.py +101 -0
- app/modules/conversation_archive/schemas.py +28 -0
- app/modules/conversation_archive/service.py +165 -0
- app/modules/dashboard/__init__.py +1 -0
- app/modules/dashboard/api.py +106 -0
- app/modules/dashboard/builders.py +107 -0
- app/modules/dashboard/repository.py +304 -0
- app/modules/dashboard/schemas.py +148 -0
- app/modules/dashboard/service.py +534 -0
- app/modules/dashboard/timeframes.py +19 -0
- app/modules/dashboard/weekly_pace.py +617 -0
- app/modules/dashboard_auth/__init__.py +1 -0
- app/modules/dashboard_auth/api.py +1344 -0
- app/modules/dashboard_auth/oidc_api.py +753 -0
- app/modules/dashboard_auth/oidc_flows.py +330 -0
- app/modules/dashboard_auth/repository.py +452 -0
- app/modules/dashboard_auth/schemas.py +232 -0
- app/modules/dashboard_auth/service.py +1384 -0
- app/modules/dashboard_roles/__init__.py +0 -0
- app/modules/dashboard_roles/api.py +64 -0
- app/modules/dashboard_roles/repository.py +54 -0
- app/modules/dashboard_roles/schemas.py +29 -0
- app/modules/dashboard_roles/seed.py +80 -0
- app/modules/dashboard_roles/service.py +142 -0
- app/modules/dashboard_users/__init__.py +0 -0
- app/modules/dashboard_users/api.py +278 -0
- app/modules/dashboard_users/break_glass.py +169 -0
- app/modules/dashboard_users/credentials.py +18 -0
- app/modules/dashboard_users/identity_resolver.py +665 -0
- app/modules/dashboard_users/repository.py +725 -0
- app/modules/dashboard_users/schemas.py +117 -0
- app/modules/dashboard_users/service.py +1054 -0
- app/modules/firewall/__init__.py +1 -0
- app/modules/firewall/api.py +78 -0
- app/modules/firewall/repository.py +51 -0
- app/modules/firewall/schemas.py +28 -0
- app/modules/firewall/service.py +93 -0
- app/modules/fleet/__init__.py +1 -0
- app/modules/fleet/api.py +177 -0
- app/modules/fleet/mappers.py +56 -0
- app/modules/fleet/observability.py +372 -0
- app/modules/fleet/schemas.py +119 -0
- app/modules/health/__init__.py +0 -0
- app/modules/health/api.py +246 -0
- app/modules/health/schemas.py +27 -0
- app/modules/limit_warmup/__init__.py +1 -0
- app/modules/limit_warmup/repository.py +159 -0
- app/modules/limit_warmup/service.py +1001 -0
- app/modules/model_sources/__init__.py +1 -0
- app/modules/model_sources/api.py +99 -0
- app/modules/model_sources/catalog.py +307 -0
- app/modules/model_sources/forwarding.py +1435 -0
- app/modules/model_sources/projection.py +55 -0
- app/modules/model_sources/repository.py +201 -0
- app/modules/model_sources/schemas.py +79 -0
- app/modules/model_sources/selection.py +166 -0
- app/modules/model_sources/service.py +236 -0
- app/modules/oauth/__init__.py +0 -0
- app/modules/oauth/api.py +101 -0
- app/modules/oauth/repository.py +238 -0
- app/modules/oauth/schemas.py +49 -0
- app/modules/oauth/service.py +1196 -0
- app/modules/oauth/templates/oauth_success.html +122 -0
- app/modules/proxy/__init__.py +0 -0
- app/modules/proxy/_load_balancer/__init__.py +1 -0
- app/modules/proxy/_load_balancer/error_rate.py +116 -0
- app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
- app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
- app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
- app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
- app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
- app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
- app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
- app/modules/proxy/_load_balancer/tunables.py +97 -0
- app/modules/proxy/_load_balancer/types.py +96 -0
- app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
- app/modules/proxy/_service/__init__.py +6 -0
- app/modules/proxy/_service/api_key_usage.py +745 -0
- app/modules/proxy/_service/clock_budget.py +38 -0
- app/modules/proxy/_service/codex_control.py +611 -0
- app/modules/proxy/_service/compact.py +2211 -0
- app/modules/proxy/_service/file_ops.py +749 -0
- app/modules/proxy/_service/http_bridge/__init__.py +3 -0
- app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
- app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
- app/modules/proxy/_service/http_bridge/activity.py +178 -0
- app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
- app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
- app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
- app/modules/proxy/_service/http_bridge/protocol.py +167 -0
- app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
- app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
- app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
- app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
- app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
- app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
- app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
- app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
- app/modules/proxy/_service/observability.py +410 -0
- app/modules/proxy/_service/rate_limit.py +361 -0
- app/modules/proxy/_service/realtime_live.py +655 -0
- app/modules/proxy/_service/refresh.py +154 -0
- app/modules/proxy/_service/request_log.py +585 -0
- app/modules/proxy/_service/response_create.py +930 -0
- app/modules/proxy/_service/streaming/__init__.py +3 -0
- app/modules/proxy/_service/streaming/helpers.py +1317 -0
- app/modules/proxy/_service/streaming/mixin.py +1100 -0
- app/modules/proxy/_service/streaming/protocol.py +32 -0
- app/modules/proxy/_service/streaming/retry.py +3762 -0
- app/modules/proxy/_service/support.py +2122 -0
- app/modules/proxy/_service/transcribe.py +434 -0
- app/modules/proxy/_service/warmup.py +483 -0
- app/modules/proxy/_service/websocket/__init__.py +3 -0
- app/modules/proxy/_service/websocket/helpers.py +2360 -0
- app/modules/proxy/_service/websocket/mixin.py +7285 -0
- app/modules/proxy/_service/websocket/protocol.py +74 -0
- app/modules/proxy/account_cache.py +229 -0
- app/modules/proxy/account_eligibility.py +87 -0
- app/modules/proxy/additional_model_limits.py +40 -0
- app/modules/proxy/affinity.py +830 -0
- app/modules/proxy/affinity_observation.py +101 -0
- app/modules/proxy/api.py +10254 -0
- app/modules/proxy/api_key_usage.py +113 -0
- app/modules/proxy/cap_partitioning.py +280 -0
- app/modules/proxy/capability_lineage.py +65 -0
- app/modules/proxy/capability_lineage_repository.py +82 -0
- app/modules/proxy/capability_routing.py +253 -0
- app/modules/proxy/continuity.py +112 -0
- app/modules/proxy/downstream_delivery.py +277 -0
- app/modules/proxy/durable_bridge_coordinator.py +1217 -0
- app/modules/proxy/durable_bridge_repository.py +4516 -0
- app/modules/proxy/durable_bridge_runtime.py +9 -0
- app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
- app/modules/proxy/fair_share.py +131 -0
- app/modules/proxy/file_pin_repository.py +210 -0
- app/modules/proxy/helpers.py +439 -0
- app/modules/proxy/http_bridge_event_batcher.py +613 -0
- app/modules/proxy/http_bridge_forwarding.py +797 -0
- app/modules/proxy/http_continuation.py +76 -0
- app/modules/proxy/images_observability.py +62 -0
- app/modules/proxy/images_service.py +1054 -0
- app/modules/proxy/load_balancer.py +3016 -0
- app/modules/proxy/rate_limit_cache.py +46 -0
- app/modules/proxy/replay_safety.py +1063 -0
- app/modules/proxy/repo_bundle.py +31 -0
- app/modules/proxy/request_policy.py +1054 -0
- app/modules/proxy/ring_membership.py +251 -0
- app/modules/proxy/schemas.py +345 -0
- app/modules/proxy/selection_errors.py +41 -0
- app/modules/proxy/service.py +2600 -0
- app/modules/proxy/source_admission.py +125 -0
- app/modules/proxy/source_dispatch.py +999 -0
- app/modules/proxy/sticky_repository.py +831 -0
- app/modules/proxy/thread_anchors.py +491 -0
- app/modules/proxy/tool_call_dedupe.py +802 -0
- app/modules/proxy/types.py +54 -0
- app/modules/proxy/work_admission.py +121 -0
- app/modules/quota_planner/__init__.py +1 -0
- app/modules/quota_planner/api.py +276 -0
- app/modules/quota_planner/logic.py +743 -0
- app/modules/quota_planner/repository.py +722 -0
- app/modules/quota_planner/scheduler.py +212 -0
- app/modules/quota_planner/schemas.py +102 -0
- app/modules/quota_planner/warmup.py +693 -0
- app/modules/rate_limit_reset_credits/__init__.py +0 -0
- app/modules/rate_limit_reset_credits/api.py +601 -0
- app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
- app/modules/rate_limit_reset_credits/store.py +114 -0
- app/modules/reports/__init__.py +1 -0
- app/modules/reports/api.py +134 -0
- app/modules/reports/cache.py +74 -0
- app/modules/reports/filters.py +27 -0
- app/modules/reports/repository.py +525 -0
- app/modules/reports/rollup.py +150 -0
- app/modules/reports/rollup_read.py +148 -0
- app/modules/reports/schemas.py +123 -0
- app/modules/reports/service.py +287 -0
- app/modules/reports/thread_identity.py +256 -0
- app/modules/request_logs/__init__.py +0 -0
- app/modules/request_logs/api.py +224 -0
- app/modules/request_logs/cost_backfill.py +154 -0
- app/modules/request_logs/mappers.py +97 -0
- app/modules/request_logs/repository.py +1765 -0
- app/modules/request_logs/schemas.py +145 -0
- app/modules/request_logs/service.py +348 -0
- app/modules/role_mappings/__init__.py +0 -0
- app/modules/role_mappings/api.py +206 -0
- app/modules/role_mappings/matching.py +55 -0
- app/modules/role_mappings/repository.py +106 -0
- app/modules/role_mappings/schemas.py +67 -0
- app/modules/role_mappings/service.py +334 -0
- app/modules/runtime/__init__.py +1 -0
- app/modules/runtime/api.py +18 -0
- app/modules/runtime/schemas.py +14 -0
- app/modules/runtime/service.py +218 -0
- app/modules/scim/__init__.py +1 -0
- app/modules/scim/api.py +194 -0
- app/modules/scim/dependencies.py +112 -0
- app/modules/scim/errors.py +77 -0
- app/modules/scim/management_api.py +156 -0
- app/modules/scim/repository.py +212 -0
- app/modules/scim/schemas.py +145 -0
- app/modules/scim/service.py +786 -0
- app/modules/scim/tokens.py +43 -0
- app/modules/settings/__init__.py +1 -0
- app/modules/settings/api.py +1677 -0
- app/modules/settings/repository.py +606 -0
- app/modules/settings/schemas.py +496 -0
- app/modules/settings/service.py +769 -0
- app/modules/shared/__init__.py +0 -0
- app/modules/shared/schemas.py +22 -0
- app/modules/sticky_sessions/__init__.py +1 -0
- app/modules/sticky_sessions/api.py +118 -0
- app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
- app/modules/sticky_sessions/schemas.py +78 -0
- app/modules/sticky_sessions/service.py +196 -0
- app/modules/telemetry/__init__.py +5 -0
- app/modules/telemetry/api.py +115 -0
- app/modules/telemetry/clients.py +62 -0
- app/modules/telemetry/consent.py +115 -0
- app/modules/telemetry/scheduler.py +85 -0
- app/modules/telemetry/schemas.py +163 -0
- app/modules/telemetry/sender.py +221 -0
- app/modules/telemetry/snapshot.py +490 -0
- app/modules/usage/__init__.py +0 -0
- app/modules/usage/additional_quota_keys.py +379 -0
- app/modules/usage/api.py +45 -0
- app/modules/usage/background_repository.py +147 -0
- app/modules/usage/builders.py +559 -0
- app/modules/usage/depletion_service.py +277 -0
- app/modules/usage/live_ingest.py +356 -0
- app/modules/usage/mappers.py +29 -0
- app/modules/usage/plan_downgrade_observations.py +488 -0
- app/modules/usage/repository.py +1890 -0
- app/modules/usage/schemas.py +68 -0
- app/modules/usage/service.py +102 -0
- app/modules/usage/updater.py +1488 -0
- app/static/assets/Trans-Cj-J9mVA.js +1 -0
- app/static/assets/access-page-DHzZiXZi.js +1 -0
- app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
- app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
- app/static/assets/account-status-DIsU5sUS.js +1 -0
- app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
- app/static/assets/accounts-page-Bbai8ift.js +3 -0
- app/static/assets/api-Dwa7XxPe.js +1 -0
- app/static/assets/api-client-dugX1Pti.js +55 -0
- app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
- app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
- app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
- app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
- app/static/assets/apis-page-BvXz9c6i.js +2 -0
- app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
- app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
- app/static/assets/automations-page-DQgNAJOR.js +3 -0
- app/static/assets/badge-DqMb9es0.js +1 -0
- app/static/assets/blur-email-HEvKagG2.js +1 -0
- app/static/assets/button-BjVmmiGW.js +1 -0
- app/static/assets/checkbox-apsl9vzk.js +1 -0
- app/static/assets/chevron-left-1d_pHVa5.js +1 -0
- app/static/assets/circle-alert-BS0xd_o1.js +1 -0
- app/static/assets/clsx-DB0hHKMi.js +1 -0
- app/static/assets/colors-BBy8GEtr.js +1 -0
- app/static/assets/copy-button-D7nAvOKR.js +1 -0
- app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
- app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
- app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
- app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
- app/static/assets/donut-chart-Bxc69cra.js +1 -0
- app/static/assets/download-kQteIcsB.js +1 -0
- app/static/assets/ellipsis-B_5OgIre.js +1 -0
- app/static/assets/empty-state-JcA7tc8m.js +1 -0
- app/static/assets/es2015-bMH8YKZT.js +41 -0
- app/static/assets/es6-B264ni6W.js +69 -0
- app/static/assets/external-link-UVAOgMQ0.js +1 -0
- app/static/assets/i18n-BWqZ9Ua2.js +1 -0
- app/static/assets/inbox-B_RM2s1J.js +1 -0
- app/static/assets/index-DIpd264Z.js +23 -0
- app/static/assets/index-DMeLKRJx.css +2 -0
- app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
- app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
- app/static/assets/iso-Bpe5Ihbb.js +1 -0
- app/static/assets/label-CGaLmLNf.js +1 -0
- app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
- app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
- app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
- app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
- app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
- app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
- app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
- app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
- app/static/assets/payload-CIYnGZz9.js +1 -0
- app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
- app/static/assets/report-chart-card-BXDliwcc.js +1 -0
- app/static/assets/reports-page-CGCJeMHG.js +3 -0
- app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
- app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- app/static/assets/search-HfhqwERt.js +1 -0
- app/static/assets/select-Cl33bz-g.js +1 -0
- app/static/assets/settings-page-mZj01HYl.js +2 -0
- app/static/assets/shim-CZwyM4Q7.js +1 -0
- app/static/assets/skeleton-C_EUc4Rp.js +1 -0
- app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
- app/static/assets/status-badge-CHHA5X6c.js +1 -0
- app/static/assets/switch-CzxgokcM.js +1 -0
- app/static/assets/table-DXaNSQ0G.js +1 -0
- app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
- app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
- app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
- app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
- app/static/assets/trash-D3KyWr1h.js +1 -0
- app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
- app/static/assets/use-accounts-PWlVGw3z.js +1 -0
- app/static/assets/use-auth-CuDlW3iC.js +1 -0
- app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
- app/static/assets/use-model-sources-Bul24JBu.js +1 -0
- app/static/assets/use-models-DUCPUuZg.js +1 -0
- app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
- app/static/assets/useTranslation--56nHhlH.js +1 -0
- app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
- app/static/assets/users-uIC3TIlE.js +1 -0
- app/static/assets/vendor-query-_bHZuxEx.js +1 -0
- app/static/assets/vendor-react-BVL0YZPW.js +9 -0
- app/static/favicon.svg +10 -0
- app/static/fonts/GeistSans-Variable.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
- app/static/index.html +29 -0
- codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
- codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
- codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
- codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
- codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
- config/additional_quota_registry.json +11 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Iterable, Mapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
|
|
11
|
+
from sqlalchemy import CursorResult, select, update
|
|
12
|
+
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
|
13
|
+
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
14
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
15
|
+
|
|
16
|
+
from app.core.config.settings import get_settings
|
|
17
|
+
from app.core.openai.model_registry import (
|
|
18
|
+
ModelRegistryExport,
|
|
19
|
+
ModelRegistrySnapshot,
|
|
20
|
+
ReasoningLevel,
|
|
21
|
+
UpstreamModel,
|
|
22
|
+
get_model_registry,
|
|
23
|
+
)
|
|
24
|
+
from app.core.types import JsonValue
|
|
25
|
+
from app.core.utils.time import to_utc_naive, utcnow
|
|
26
|
+
from app.db.models import ModelRegistrySnapshotRecord
|
|
27
|
+
from app.db.session import get_background_session
|
|
28
|
+
from app.modules.proxy.account_cache import get_account_selection_cache
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Version of the persisted payload shape. MUST be bumped whenever the payload
|
|
33
|
+
# encoding changes (new/renamed fields, different container shapes); replicas
|
|
34
|
+
# ignore snapshots whose schema_version differs from their codec version.
|
|
35
|
+
SCHEMA_VERSION = 1
|
|
36
|
+
|
|
37
|
+
_SNAPSHOT_ROW_ID = 1
|
|
38
|
+
|
|
39
|
+
# Field-complete codec contracts: unit tests assert these sets equal the
|
|
40
|
+
# dataclass fields so a new field cannot ship without codec support.
|
|
41
|
+
UPSTREAM_MODEL_CODEC_FIELDS = frozenset(
|
|
42
|
+
{
|
|
43
|
+
"slug",
|
|
44
|
+
"display_name",
|
|
45
|
+
"description",
|
|
46
|
+
"context_window",
|
|
47
|
+
"input_modalities",
|
|
48
|
+
"supported_reasoning_levels",
|
|
49
|
+
"default_reasoning_level",
|
|
50
|
+
"supports_reasoning_summaries",
|
|
51
|
+
"support_verbosity",
|
|
52
|
+
"default_verbosity",
|
|
53
|
+
"prefer_websockets",
|
|
54
|
+
"supports_parallel_tool_calls",
|
|
55
|
+
"supported_in_api",
|
|
56
|
+
"minimal_client_version",
|
|
57
|
+
"priority",
|
|
58
|
+
"available_in_plans",
|
|
59
|
+
"base_instructions",
|
|
60
|
+
"source_kind",
|
|
61
|
+
"source_id",
|
|
62
|
+
"raw",
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ``fetched_at`` is monotonic-clock based and is persisted as the wall-clock
|
|
67
|
+
# ``refreshed_at`` column instead of appearing in the payload.
|
|
68
|
+
SNAPSHOT_CODEC_FIELDS = frozenset(
|
|
69
|
+
{
|
|
70
|
+
"models",
|
|
71
|
+
"model_plans",
|
|
72
|
+
"plan_models",
|
|
73
|
+
"model_service_tier_plans",
|
|
74
|
+
"model_service_tier_accounts",
|
|
75
|
+
"account_plans",
|
|
76
|
+
"fetched_at",
|
|
77
|
+
"model_accounts",
|
|
78
|
+
"account_catalogs_authoritative",
|
|
79
|
+
"bootstrap_floor_active",
|
|
80
|
+
"suppressed_model_slugs",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(slots=True)
|
|
86
|
+
class EncodedRegistrySnapshot:
|
|
87
|
+
payload: str
|
|
88
|
+
content_hash: str
|
|
89
|
+
refreshed_at: datetime
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(slots=True)
|
|
93
|
+
class StoredSnapshotHeader:
|
|
94
|
+
schema_version: int
|
|
95
|
+
content_hash: str
|
|
96
|
+
refreshed_at: datetime
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _encode_reasoning_level(level: ReasoningLevel) -> dict[str, JsonValue]:
|
|
100
|
+
return {"effort": level.effort, "description": level.description}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _encode_string_list(values: Iterable[str]) -> list[JsonValue]:
|
|
104
|
+
"""Re-type a string sequence as ``list[JsonValue]``.
|
|
105
|
+
|
|
106
|
+
``list`` is invariant, so e.g. ``sorted(...)``'s ``list[str]`` is not
|
|
107
|
+
assignable to the ``list[JsonValue]`` arm of ``JsonValue``.
|
|
108
|
+
"""
|
|
109
|
+
return [value for value in values]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _decode_reasoning_level(data: Mapping[str, JsonValue]) -> ReasoningLevel:
|
|
113
|
+
return ReasoningLevel(effort=str(data["effort"]), description=str(data["description"]))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _encode_model(model: UpstreamModel) -> dict[str, JsonValue]:
|
|
117
|
+
return {
|
|
118
|
+
"slug": model.slug,
|
|
119
|
+
"display_name": model.display_name,
|
|
120
|
+
"description": model.description,
|
|
121
|
+
"context_window": model.context_window,
|
|
122
|
+
"input_modalities": list(model.input_modalities),
|
|
123
|
+
"supported_reasoning_levels": [_encode_reasoning_level(level) for level in model.supported_reasoning_levels],
|
|
124
|
+
"default_reasoning_level": model.default_reasoning_level,
|
|
125
|
+
"supports_reasoning_summaries": model.supports_reasoning_summaries,
|
|
126
|
+
"support_verbosity": model.support_verbosity,
|
|
127
|
+
"default_verbosity": model.default_verbosity,
|
|
128
|
+
"prefer_websockets": model.prefer_websockets,
|
|
129
|
+
"supports_parallel_tool_calls": model.supports_parallel_tool_calls,
|
|
130
|
+
"supported_in_api": model.supported_in_api,
|
|
131
|
+
"minimal_client_version": model.minimal_client_version,
|
|
132
|
+
"priority": model.priority,
|
|
133
|
+
"available_in_plans": _encode_string_list(sorted(model.available_in_plans)),
|
|
134
|
+
"base_instructions": model.base_instructions,
|
|
135
|
+
"source_kind": model.source_kind,
|
|
136
|
+
"source_id": model.source_id,
|
|
137
|
+
"raw": model.raw,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _decode_model(data: Mapping[str, JsonValue]) -> UpstreamModel:
|
|
142
|
+
reasoning_levels = data["supported_reasoning_levels"]
|
|
143
|
+
input_modalities = data["input_modalities"]
|
|
144
|
+
available_in_plans = data["available_in_plans"]
|
|
145
|
+
raw = data["raw"]
|
|
146
|
+
if (
|
|
147
|
+
not isinstance(reasoning_levels, list)
|
|
148
|
+
or not isinstance(input_modalities, list)
|
|
149
|
+
or not isinstance(available_in_plans, list)
|
|
150
|
+
or not isinstance(raw, dict)
|
|
151
|
+
):
|
|
152
|
+
raise ValueError("Malformed persisted model entry")
|
|
153
|
+
# Reject a wrong-typed reasoning level rather than dropping it, so a corrupt
|
|
154
|
+
# entry surfaces as a decode failure instead of a silently-partial level set.
|
|
155
|
+
decoded_levels: list[ReasoningLevel] = []
|
|
156
|
+
for level in reasoning_levels:
|
|
157
|
+
if not isinstance(level, dict):
|
|
158
|
+
raise ValueError("Malformed persisted reasoning level")
|
|
159
|
+
decoded_levels.append(_decode_reasoning_level(level))
|
|
160
|
+
return UpstreamModel(
|
|
161
|
+
slug=str(data["slug"]),
|
|
162
|
+
display_name=str(data["display_name"]),
|
|
163
|
+
description=str(data["description"]),
|
|
164
|
+
context_window=int(_cast_int(data["context_window"])),
|
|
165
|
+
input_modalities=tuple(str(item) for item in input_modalities),
|
|
166
|
+
supported_reasoning_levels=tuple(decoded_levels),
|
|
167
|
+
default_reasoning_level=_optional_str(data["default_reasoning_level"]),
|
|
168
|
+
supports_reasoning_summaries=bool(data["supports_reasoning_summaries"]),
|
|
169
|
+
support_verbosity=bool(data["support_verbosity"]),
|
|
170
|
+
default_verbosity=_optional_str(data["default_verbosity"]),
|
|
171
|
+
prefer_websockets=bool(data["prefer_websockets"]),
|
|
172
|
+
supports_parallel_tool_calls=bool(data["supports_parallel_tool_calls"]),
|
|
173
|
+
supported_in_api=bool(data["supported_in_api"]),
|
|
174
|
+
minimal_client_version=_optional_str(data["minimal_client_version"]),
|
|
175
|
+
priority=int(_cast_int(data["priority"])),
|
|
176
|
+
available_in_plans=frozenset(str(item) for item in available_in_plans),
|
|
177
|
+
base_instructions=str(data["base_instructions"]),
|
|
178
|
+
source_kind=str(data["source_kind"]),
|
|
179
|
+
source_id=_optional_str(data["source_id"]),
|
|
180
|
+
raw=dict(raw),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _cast_int(value: JsonValue) -> int:
|
|
185
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
186
|
+
raise ValueError(f"Expected numeric value, got {type(value).__name__}")
|
|
187
|
+
return int(value)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _optional_str(value: JsonValue) -> str | None:
|
|
191
|
+
if value is None:
|
|
192
|
+
return None
|
|
193
|
+
return str(value)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _encode_slug_sets(mapping: dict[str, frozenset[str]]) -> dict[str, JsonValue]:
|
|
197
|
+
return {key: _encode_string_list(sorted(values)) for key, values in mapping.items()}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _decode_slug_sets(data: JsonValue) -> dict[str, frozenset[str]]:
|
|
201
|
+
if not isinstance(data, dict):
|
|
202
|
+
raise ValueError("Malformed persisted set mapping")
|
|
203
|
+
decoded: dict[str, frozenset[str]] = {}
|
|
204
|
+
for key, values in data.items():
|
|
205
|
+
# A wrong-typed value (e.g. a bare string where a list of slugs is
|
|
206
|
+
# expected) MUST reject the whole decode rather than silently dropping
|
|
207
|
+
# the entry: a dropped set-backed field would apply a partial catalog
|
|
208
|
+
# (model present, plan gating gone) instead of surfacing corruption to
|
|
209
|
+
# the poller. An empty list is a legitimately-empty set and is kept.
|
|
210
|
+
if not isinstance(values, list):
|
|
211
|
+
raise ValueError("Malformed persisted set mapping: expected a list of slugs")
|
|
212
|
+
decoded[str(key)] = frozenset(str(item) for item in values)
|
|
213
|
+
return decoded
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _encode_tier_sets(mapping: dict[str, dict[str, frozenset[str]]]) -> dict[str, JsonValue]:
|
|
217
|
+
return {slug: _encode_slug_sets(tiers) for slug, tiers in mapping.items()}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _decode_tier_sets(data: JsonValue) -> dict[str, dict[str, frozenset[str]]]:
|
|
221
|
+
if not isinstance(data, dict):
|
|
222
|
+
raise ValueError("Malformed persisted tier mapping")
|
|
223
|
+
return {str(slug): _decode_slug_sets(tiers) for slug, tiers in data.items()}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _encode_snapshot(snapshot: ModelRegistrySnapshot) -> dict[str, JsonValue]:
|
|
227
|
+
return {
|
|
228
|
+
"models": {slug: _encode_model(model) for slug, model in snapshot.models.items()},
|
|
229
|
+
"model_plans": _encode_slug_sets(snapshot.model_plans),
|
|
230
|
+
"plan_models": _encode_slug_sets(snapshot.plan_models),
|
|
231
|
+
"model_service_tier_plans": _encode_tier_sets(snapshot.model_service_tier_plans),
|
|
232
|
+
"model_service_tier_accounts": _encode_tier_sets(snapshot.model_service_tier_accounts),
|
|
233
|
+
"account_plans": dict(snapshot.account_plans),
|
|
234
|
+
"model_accounts": _encode_slug_sets(snapshot.model_accounts),
|
|
235
|
+
"account_catalogs_authoritative": snapshot.account_catalogs_authoritative,
|
|
236
|
+
"bootstrap_floor_active": snapshot.bootstrap_floor_active,
|
|
237
|
+
"suppressed_model_slugs": _encode_string_list(sorted(snapshot.suppressed_model_slugs)),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _decode_models(data: JsonValue) -> dict[str, UpstreamModel]:
|
|
242
|
+
if not isinstance(data, dict):
|
|
243
|
+
raise ValueError("Malformed persisted models mapping")
|
|
244
|
+
decoded: dict[str, UpstreamModel] = {}
|
|
245
|
+
for slug, entry in data.items():
|
|
246
|
+
# Reject a wrong-typed entry rather than dropping it, so a corrupt model
|
|
247
|
+
# surfaces as a decode failure instead of a silently-partial catalog.
|
|
248
|
+
if not isinstance(entry, dict):
|
|
249
|
+
raise ValueError("Malformed persisted model entry")
|
|
250
|
+
decoded[str(slug)] = _decode_model(entry)
|
|
251
|
+
return decoded
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _decode_account_plans(data: JsonValue) -> dict[str, str]:
|
|
255
|
+
if not isinstance(data, dict):
|
|
256
|
+
raise ValueError("Malformed persisted account plans")
|
|
257
|
+
return {str(key): str(value) for key, value in data.items()}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _decode_snapshot(data: dict[str, JsonValue], *, fetched_at: float) -> ModelRegistrySnapshot:
|
|
261
|
+
suppressed = data["suppressed_model_slugs"]
|
|
262
|
+
if not isinstance(suppressed, list):
|
|
263
|
+
raise ValueError("Malformed persisted suppression set")
|
|
264
|
+
return ModelRegistrySnapshot(
|
|
265
|
+
models=_decode_models(data["models"]),
|
|
266
|
+
model_plans=_decode_slug_sets(data["model_plans"]),
|
|
267
|
+
plan_models=_decode_slug_sets(data["plan_models"]),
|
|
268
|
+
model_service_tier_plans=_decode_tier_sets(data["model_service_tier_plans"]),
|
|
269
|
+
model_service_tier_accounts=_decode_tier_sets(data["model_service_tier_accounts"]),
|
|
270
|
+
account_plans=_decode_account_plans(data["account_plans"]),
|
|
271
|
+
fetched_at=fetched_at,
|
|
272
|
+
model_accounts=_decode_slug_sets(data["model_accounts"]),
|
|
273
|
+
account_catalogs_authoritative=bool(data["account_catalogs_authoritative"]),
|
|
274
|
+
bootstrap_floor_active=bool(data["bootstrap_floor_active"]),
|
|
275
|
+
suppressed_model_slugs=frozenset(str(item) for item in suppressed),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def encode_registry_export(export: ModelRegistryExport) -> EncodedRegistrySnapshot:
|
|
280
|
+
"""Serialize registry state to a canonical JSON payload plus content hash.
|
|
281
|
+
|
|
282
|
+
The payload deliberately excludes any timestamp so the content hash stays
|
|
283
|
+
stable across refresh ticks that fetch an identical catalog; the wall-clock
|
|
284
|
+
``refreshed_at`` is carried as a separate column.
|
|
285
|
+
"""
|
|
286
|
+
if export.snapshot is None:
|
|
287
|
+
document: dict[str, JsonValue] = {"cleared": True, "snapshot": None, "metadata_models": None}
|
|
288
|
+
refreshed_at = utcnow()
|
|
289
|
+
else:
|
|
290
|
+
document = {
|
|
291
|
+
"cleared": False,
|
|
292
|
+
"snapshot": _encode_snapshot(export.snapshot),
|
|
293
|
+
"metadata_models": (
|
|
294
|
+
{slug: _encode_model(model) for slug, model in export.metadata_models.items()}
|
|
295
|
+
if export.metadata_models is not None
|
|
296
|
+
else None
|
|
297
|
+
),
|
|
298
|
+
}
|
|
299
|
+
elapsed = max(0.0, time.monotonic() - export.snapshot.fetched_at)
|
|
300
|
+
refreshed_at = utcnow() - timedelta(seconds=elapsed)
|
|
301
|
+
payload = json.dumps(document, sort_keys=True, separators=(",", ":"))
|
|
302
|
+
content_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
303
|
+
return EncodedRegistrySnapshot(payload=payload, content_hash=content_hash, refreshed_at=refreshed_at)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def decode_registry_payload(payload: str, *, refreshed_at: datetime) -> ModelRegistryExport:
|
|
307
|
+
"""Deserialize a persisted payload, deriving the monotonic ``fetched_at``
|
|
308
|
+
from the wall-clock ``refreshed_at`` so TTL semantics survive import."""
|
|
309
|
+
document = json.loads(payload)
|
|
310
|
+
if not isinstance(document, dict):
|
|
311
|
+
raise ValueError("Malformed persisted model registry payload")
|
|
312
|
+
if document.get("cleared") is True:
|
|
313
|
+
return ModelRegistryExport(snapshot=None, metadata_models=None)
|
|
314
|
+
snapshot_data = document.get("snapshot")
|
|
315
|
+
if not isinstance(snapshot_data, dict):
|
|
316
|
+
raise ValueError("Malformed persisted model registry snapshot")
|
|
317
|
+
age_seconds = max(0.0, (utcnow() - to_utc_naive(refreshed_at)).total_seconds())
|
|
318
|
+
snapshot = _decode_snapshot(snapshot_data, fetched_at=time.monotonic() - age_seconds)
|
|
319
|
+
metadata_data = document.get("metadata_models")
|
|
320
|
+
metadata_models = _decode_models(metadata_data) if isinstance(metadata_data, dict) else None
|
|
321
|
+
return ModelRegistryExport(snapshot=snapshot, metadata_models=metadata_models)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
async def persist_registry_snapshot(
|
|
325
|
+
session: AsyncSession,
|
|
326
|
+
*,
|
|
327
|
+
encoded: EncodedRegistrySnapshot,
|
|
328
|
+
leader_id: str | None,
|
|
329
|
+
) -> bool:
|
|
330
|
+
"""Atomically upsert the single snapshot row; returns True when followers must re-apply.
|
|
331
|
+
|
|
332
|
+
Unchanged content only touches ``refreshed_at``/``leader_id`` (guarded by
|
|
333
|
+
``content_hash``) so snapshot age means "time since the leader last
|
|
334
|
+
confirmed this catalog" without rewriting a potentially multi-MB payload.
|
|
335
|
+
|
|
336
|
+
Returns True when the payload changed *or* when an existing row had aged past
|
|
337
|
+
``model_registry_snapshot_max_age_seconds`` before this refresh revived it:
|
|
338
|
+
once a row expires, followers clear their local registry and reset their
|
|
339
|
+
applied-content-hash marker, so an unchanged-content revival still needs a
|
|
340
|
+
``model_registry`` bump for them to re-apply within the cache-poll bound
|
|
341
|
+
instead of waiting for the non-leader scheduler backstop. Returns False only
|
|
342
|
+
when the row was already fresh and the content is unchanged.
|
|
343
|
+
"""
|
|
344
|
+
existing = (
|
|
345
|
+
await session.execute(
|
|
346
|
+
select(
|
|
347
|
+
ModelRegistrySnapshotRecord.content_hash,
|
|
348
|
+
ModelRegistrySnapshotRecord.refreshed_at,
|
|
349
|
+
).where(ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID)
|
|
350
|
+
)
|
|
351
|
+
).first()
|
|
352
|
+
if existing is not None and existing.content_hash == encoded.content_hash:
|
|
353
|
+
result = await session.execute(
|
|
354
|
+
update(ModelRegistrySnapshotRecord)
|
|
355
|
+
.where(
|
|
356
|
+
ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID,
|
|
357
|
+
ModelRegistrySnapshotRecord.content_hash == encoded.content_hash,
|
|
358
|
+
)
|
|
359
|
+
.values(
|
|
360
|
+
schema_version=SCHEMA_VERSION,
|
|
361
|
+
refreshed_at=encoded.refreshed_at,
|
|
362
|
+
leader_id=leader_id,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
if isinstance(result, CursorResult) and result.rowcount == 1:
|
|
366
|
+
await session.commit()
|
|
367
|
+
max_age_seconds = get_settings().model_registry_snapshot_max_age_seconds
|
|
368
|
+
prior_age_seconds = (utcnow() - to_utc_naive(existing.refreshed_at)).total_seconds()
|
|
369
|
+
# An expired row means followers already dropped to the bootstrap
|
|
370
|
+
# floor; treat the revival as bump-worthy so they re-apply promptly.
|
|
371
|
+
return prior_age_seconds > max_age_seconds
|
|
372
|
+
|
|
373
|
+
values = {
|
|
374
|
+
"id": _SNAPSHOT_ROW_ID,
|
|
375
|
+
"schema_version": SCHEMA_VERSION,
|
|
376
|
+
"content_hash": encoded.content_hash,
|
|
377
|
+
"payload": encoded.payload,
|
|
378
|
+
"refreshed_at": encoded.refreshed_at,
|
|
379
|
+
"leader_id": leader_id,
|
|
380
|
+
}
|
|
381
|
+
update_values = {key: value for key, value in values.items() if key != "id"}
|
|
382
|
+
dialect = session.get_bind().dialect.name
|
|
383
|
+
if dialect == "postgresql":
|
|
384
|
+
stmt = (
|
|
385
|
+
pg_insert(ModelRegistrySnapshotRecord)
|
|
386
|
+
.values(**values)
|
|
387
|
+
.on_conflict_do_update(index_elements=[ModelRegistrySnapshotRecord.id], set_=update_values)
|
|
388
|
+
)
|
|
389
|
+
await session.execute(stmt)
|
|
390
|
+
elif dialect == "sqlite":
|
|
391
|
+
stmt = (
|
|
392
|
+
sqlite_insert(ModelRegistrySnapshotRecord)
|
|
393
|
+
.values(**values)
|
|
394
|
+
.on_conflict_do_update(index_elements=[ModelRegistrySnapshotRecord.id], set_=update_values)
|
|
395
|
+
)
|
|
396
|
+
await session.execute(stmt)
|
|
397
|
+
else:
|
|
398
|
+
existing = await session.scalar(
|
|
399
|
+
select(ModelRegistrySnapshotRecord).where(ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID)
|
|
400
|
+
)
|
|
401
|
+
if existing is None:
|
|
402
|
+
session.add(ModelRegistrySnapshotRecord(**values))
|
|
403
|
+
else:
|
|
404
|
+
await session.execute(
|
|
405
|
+
update(ModelRegistrySnapshotRecord)
|
|
406
|
+
.where(ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID)
|
|
407
|
+
.values(**update_values)
|
|
408
|
+
)
|
|
409
|
+
await session.commit()
|
|
410
|
+
logger.info(
|
|
411
|
+
"Persisted model registry snapshot content_hash=%s payload_bytes=%d",
|
|
412
|
+
encoded.content_hash,
|
|
413
|
+
len(encoded.payload),
|
|
414
|
+
)
|
|
415
|
+
return True
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
async def _probe_header(session: AsyncSession) -> StoredSnapshotHeader | None:
|
|
419
|
+
row = (
|
|
420
|
+
await session.execute(
|
|
421
|
+
select(
|
|
422
|
+
ModelRegistrySnapshotRecord.schema_version,
|
|
423
|
+
ModelRegistrySnapshotRecord.content_hash,
|
|
424
|
+
ModelRegistrySnapshotRecord.refreshed_at,
|
|
425
|
+
).where(ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID)
|
|
426
|
+
)
|
|
427
|
+
).first()
|
|
428
|
+
if row is None:
|
|
429
|
+
return None
|
|
430
|
+
schema_version, content_hash, refreshed_at = row
|
|
431
|
+
return StoredSnapshotHeader(
|
|
432
|
+
schema_version=schema_version,
|
|
433
|
+
content_hash=content_hash,
|
|
434
|
+
refreshed_at=refreshed_at,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _snapshot_age_seconds(header: StoredSnapshotHeader) -> float:
|
|
439
|
+
return (utcnow() - to_utc_naive(header.refreshed_at)).total_seconds()
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
async def reconcile_model_registry_from_store(*, raise_on_error: bool = False) -> bool:
|
|
443
|
+
"""Apply the persisted snapshot to the local registry when it differs.
|
|
444
|
+
|
|
445
|
+
Shared by lifespan startup, the ``model_registry`` invalidation callback,
|
|
446
|
+
and the non-leader refresh-tick backstop. Returns True when a snapshot was
|
|
447
|
+
applied.
|
|
448
|
+
|
|
449
|
+
A load failure (transient DB read error or malformed payload) keeps the
|
|
450
|
+
current in-memory state (bootstrap floor at worst) and logs a warning. When
|
|
451
|
+
``raise_on_error`` is True the failure is re-raised after logging so the
|
|
452
|
+
caller can react to it: the ``model_registry`` invalidation callback passes
|
|
453
|
+
``raise_on_error=True`` so ``CacheInvalidationPoller._run_callbacks`` leaves
|
|
454
|
+
the namespace version unacknowledged and retries on the next poll cycle
|
|
455
|
+
(matching the ``account_routing`` refresh callback), rather than ACKing the
|
|
456
|
+
bump and leaving this replica on the stale catalog until the non-leader
|
|
457
|
+
scheduler backstop. The startup one-shot and refresh-tick backstop use the
|
|
458
|
+
default ``raise_on_error=False`` so a transient failure never fails startup
|
|
459
|
+
or the scheduler loop; those paths retry on their own cadence.
|
|
460
|
+
|
|
461
|
+
The account-selection cache clears here are always local
|
|
462
|
+
(``propagate=False``): reconcile only applies a change the leader already
|
|
463
|
+
published (which bumped ``model_registry`` to reach every replica), and
|
|
464
|
+
every replica clears its own selection cache on apply. Propagating would
|
|
465
|
+
make each follower durably re-bump ``account_selection``, amplifying bus
|
|
466
|
+
traffic without changing any peer's state.
|
|
467
|
+
"""
|
|
468
|
+
registry = get_model_registry()
|
|
469
|
+
try:
|
|
470
|
+
max_age_seconds = get_settings().model_registry_snapshot_max_age_seconds
|
|
471
|
+
async with get_background_session() as session:
|
|
472
|
+
header = await _probe_header(session)
|
|
473
|
+
if header is None:
|
|
474
|
+
if registry.get_snapshot() is not None or registry.applied_content_hash is not None:
|
|
475
|
+
# The store has no snapshot row but this replica still
|
|
476
|
+
# carries registry state: either an unpublished leader-local
|
|
477
|
+
# refresh whose persist failed before leadership was lost,
|
|
478
|
+
# or a previously applied row that was deleted out from
|
|
479
|
+
# under us. Other replicas are serving the bootstrap floor,
|
|
480
|
+
# so drop the local state to converge with them until a
|
|
481
|
+
# leader publishes a snapshot.
|
|
482
|
+
await registry.clear()
|
|
483
|
+
get_account_selection_cache().invalidate(propagate=False)
|
|
484
|
+
logger.warning(
|
|
485
|
+
"Dropped local model registry state: no persisted snapshot row exists; "
|
|
486
|
+
"reverting to bootstrap floor"
|
|
487
|
+
)
|
|
488
|
+
return False
|
|
489
|
+
if header.schema_version != SCHEMA_VERSION:
|
|
490
|
+
logger.warning(
|
|
491
|
+
"Ignoring persisted model registry snapshot with schema_version=%d (codec version %d)",
|
|
492
|
+
header.schema_version,
|
|
493
|
+
SCHEMA_VERSION,
|
|
494
|
+
)
|
|
495
|
+
return False
|
|
496
|
+
if _snapshot_age_seconds(header) > max_age_seconds:
|
|
497
|
+
if registry.get_snapshot() is not None or registry.applied_content_hash is not None:
|
|
498
|
+
# The only valid published row has aged past the staleness
|
|
499
|
+
# cap, so no replica can converge on it. This replica still
|
|
500
|
+
# carries registry state: either state imported from (or
|
|
501
|
+
# persisted to) the store, or an unpublished leader-local
|
|
502
|
+
# refresh whose persist failed before leadership was lost
|
|
503
|
+
# (no applied hash). Reconcile runs off the non-leader path
|
|
504
|
+
# and, under a prolonged upstream outage, on the leader's
|
|
505
|
+
# all-fetch-failed path, so a current or former leader
|
|
506
|
+
# holding a stale or unpublished catalog must not keep
|
|
507
|
+
# serving it while other replicas drop to the bootstrap
|
|
508
|
+
# floor. Drop the local state to converge until a leader
|
|
509
|
+
# publishes a fresh snapshot.
|
|
510
|
+
await registry.clear()
|
|
511
|
+
get_account_selection_cache().invalidate(propagate=False)
|
|
512
|
+
logger.warning(
|
|
513
|
+
"Dropped local model registry snapshot: persisted entry older than %ds "
|
|
514
|
+
"(refreshed_at=%s); reverting to bootstrap floor",
|
|
515
|
+
max_age_seconds,
|
|
516
|
+
header.refreshed_at,
|
|
517
|
+
)
|
|
518
|
+
return False
|
|
519
|
+
logger.warning(
|
|
520
|
+
"Ignoring persisted model registry snapshot older than %ds (refreshed_at=%s)",
|
|
521
|
+
max_age_seconds,
|
|
522
|
+
header.refreshed_at,
|
|
523
|
+
)
|
|
524
|
+
return False
|
|
525
|
+
if header.content_hash == registry.applied_content_hash:
|
|
526
|
+
return False
|
|
527
|
+
payload = await session.scalar(
|
|
528
|
+
select(ModelRegistrySnapshotRecord.payload).where(ModelRegistrySnapshotRecord.id == _SNAPSHOT_ROW_ID)
|
|
529
|
+
)
|
|
530
|
+
if payload is None:
|
|
531
|
+
return False
|
|
532
|
+
state = decode_registry_payload(payload, refreshed_at=header.refreshed_at)
|
|
533
|
+
except Exception:
|
|
534
|
+
logger.warning("Failed to load persisted model registry snapshot", exc_info=True)
|
|
535
|
+
if raise_on_error:
|
|
536
|
+
raise
|
|
537
|
+
return False
|
|
538
|
+
|
|
539
|
+
await registry.import_state(state, content_hash=header.content_hash)
|
|
540
|
+
get_account_selection_cache().invalidate(propagate=False)
|
|
541
|
+
total_models = len(state.snapshot.models) if state.snapshot is not None else 0
|
|
542
|
+
logger.info(
|
|
543
|
+
"Applied persisted model registry snapshot content_hash=%s total_models=%d cleared=%s",
|
|
544
|
+
header.content_hash,
|
|
545
|
+
total_models,
|
|
546
|
+
state.snapshot is None,
|
|
547
|
+
)
|
|
548
|
+
return True
|