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,73 @@
|
|
|
1
|
+
"""Dashboard-managed background job toggles (M2, slop-removal 0908).
|
|
2
|
+
|
|
3
|
+
``auth_guardian_enabled``, ``automations_scheduler_enabled`` and
|
|
4
|
+
``rate_limit_reset_credits_refresh_enabled`` are nullable ``dashboard_settings``
|
|
5
|
+
columns that inherit the deprecated ``CODEX_LB_*`` environment alias and then
|
|
6
|
+
the code default, resolved through :func:`resolve_inheritable`.
|
|
7
|
+
|
|
8
|
+
The schedulers always start their loop; each cycle reads the ``SettingsCache``
|
|
9
|
+
snapshot at its entry and skips the pass while the effective value is False, so
|
|
10
|
+
a dashboard change takes effect on the next tick without a restart. Nothing
|
|
11
|
+
here reads the database inside a runtime lock or per-item loop: the snapshot is
|
|
12
|
+
taken once per cycle, before the scheduler's lock.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Final
|
|
18
|
+
|
|
19
|
+
from app.core.config.inheritable import resolve_inheritable
|
|
20
|
+
from app.core.config.settings import Settings, get_settings
|
|
21
|
+
|
|
22
|
+
# ``dashboard_settings`` column, ``Settings`` field and provenance key share
|
|
23
|
+
# one name (configuration-tiers).
|
|
24
|
+
BACKGROUND_JOB_SETTINGS: Final[tuple[str, ...]] = (
|
|
25
|
+
"auth_guardian_enabled",
|
|
26
|
+
"automations_scheduler_enabled",
|
|
27
|
+
"rate_limit_reset_credits_refresh_enabled",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resolve_background_job_toggle(
|
|
32
|
+
dashboard_settings: object | None,
|
|
33
|
+
name: str,
|
|
34
|
+
*,
|
|
35
|
+
startup_settings: object | None = None,
|
|
36
|
+
) -> bool:
|
|
37
|
+
"""Effective value of one background job toggle from a dashboard-settings snapshot.
|
|
38
|
+
|
|
39
|
+
``dashboard_settings`` is the cached ``DashboardSettings`` row (or any object
|
|
40
|
+
exposing the same attribute; a missing attribute reads as NULL).
|
|
41
|
+
``startup_settings`` supplies the environment layer when the caller already
|
|
42
|
+
holds it; it defaults to ``get_settings()``.
|
|
43
|
+
"""
|
|
44
|
+
default = bool(Settings.model_fields[name].default)
|
|
45
|
+
startup = get_settings() if startup_settings is None else startup_settings
|
|
46
|
+
return bool(
|
|
47
|
+
resolve_inheritable(
|
|
48
|
+
getattr(dashboard_settings, name, None),
|
|
49
|
+
bool(getattr(startup, name, default)),
|
|
50
|
+
default,
|
|
51
|
+
).value
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def background_job_enabled(name: str, *, startup_settings: object | None = None) -> bool:
|
|
56
|
+
"""Effective toggle from the shared ``SettingsCache`` snapshot (one read per scheduler cycle)."""
|
|
57
|
+
from app.core.config.settings_cache import get_settings_cache
|
|
58
|
+
|
|
59
|
+
return resolve_background_job_toggle(await get_settings_cache().get(), name, startup_settings=startup_settings)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def auth_guardian_blocked_by_topology(settings: Settings | None = None) -> bool:
|
|
63
|
+
"""True when the static topology forbids the Auth Guardian regardless of its toggle.
|
|
64
|
+
|
|
65
|
+
Concurrent force token refreshes across replicas can invalidate rotated
|
|
66
|
+
refresh tokens, so without leader election the guardian must not run in a
|
|
67
|
+
multi-replica ring at all (usage-refresh-policy "Multi-replica leader
|
|
68
|
+
guard"). The dashboard toggle cannot override this; the settings API
|
|
69
|
+
reports it so the UI can say why the guardian is idle.
|
|
70
|
+
"""
|
|
71
|
+
startup = get_settings() if settings is None else settings
|
|
72
|
+
multi_replica = len(startup.http_responses_session_bridge_instance_ring) > 1
|
|
73
|
+
return multi_replica and not startup.leader_election_enabled
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Per-model context window overrides: dashboard rows over the environment dict.
|
|
2
|
+
|
|
3
|
+
``CODEX_LB_MODEL_CONTEXT_WINDOW_OVERRIDES`` (``slug -> window``) is a T3
|
|
4
|
+
setting whose dashboard home is the ``model_context_window_overrides`` table
|
|
5
|
+
(one row per slug) rather than a ``dashboard_settings`` column, because the
|
|
6
|
+
value is a mapping, not a scalar. The precedence of ``configuration-tiers``
|
|
7
|
+
still applies per slug: a dashboard row wins, a slug without a row inherits
|
|
8
|
+
the environment entry, and a slug with neither has no override (the catalog
|
|
9
|
+
reports the upstream window). There is no code default for a slug, so this
|
|
10
|
+
module carries its own per-slug resolver instead of ``resolve_inheritable``.
|
|
11
|
+
|
|
12
|
+
The catalog endpoints (``GET /v1/models``, ``GET /backend-api/codex/models``)
|
|
13
|
+
read the dashboard rows through ``ModelContextWindowOverridesCache`` (TTL +
|
|
14
|
+
the cross-replica ``settings`` invalidation namespace, like
|
|
15
|
+
``SettingsCache``) — never per model entry and never inside a lock.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
from collections.abc import Mapping
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Literal
|
|
24
|
+
|
|
25
|
+
import anyio
|
|
26
|
+
|
|
27
|
+
from app.core.cache.invalidation import NAMESPACE_SETTINGS, get_cache_invalidation_poller
|
|
28
|
+
|
|
29
|
+
type ContextWindowOverrideSource = Literal["dashboard", "env"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class ContextWindowOverride:
|
|
34
|
+
"""Effective override for one slug and where it came from.
|
|
35
|
+
|
|
36
|
+
``env_value`` is the environment entry for the slug (``None`` when the
|
|
37
|
+
environment has none), reported even while a dashboard row wins so the
|
|
38
|
+
dashboard can show what clearing the row returns to.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
slug: str
|
|
42
|
+
context_window: int
|
|
43
|
+
source: ContextWindowOverrideSource
|
|
44
|
+
env_value: int | None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_context_window_overrides(
|
|
48
|
+
dashboard: Mapping[str, int], environment: Mapping[str, int]
|
|
49
|
+
) -> dict[str, ContextWindowOverride]:
|
|
50
|
+
"""Combine dashboard rows and the environment dict, per slug, dashboard first.
|
|
51
|
+
|
|
52
|
+
This is the only place that combines the two layers; the catalog and the
|
|
53
|
+
settings API both derive their view from it.
|
|
54
|
+
"""
|
|
55
|
+
resolved: dict[str, ContextWindowOverride] = {}
|
|
56
|
+
for slug in sorted(set(dashboard) | set(environment)):
|
|
57
|
+
env_value = environment.get(slug)
|
|
58
|
+
if slug in dashboard:
|
|
59
|
+
resolved[slug] = ContextWindowOverride(slug, dashboard[slug], "dashboard", env_value)
|
|
60
|
+
else:
|
|
61
|
+
assert env_value is not None
|
|
62
|
+
resolved[slug] = ContextWindowOverride(slug, env_value, "env", env_value)
|
|
63
|
+
return resolved
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def effective_context_window_overrides(dashboard: Mapping[str, int], environment: Mapping[str, int]) -> dict[str, int]:
|
|
67
|
+
"""``slug -> effective window`` (dashboard row, else environment entry)."""
|
|
68
|
+
return {
|
|
69
|
+
slug: override.context_window
|
|
70
|
+
for slug, override in resolve_context_window_overrides(dashboard, environment).items()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ModelContextWindowOverridesCache:
|
|
75
|
+
"""Process-wide snapshot of the dashboard override rows (``slug -> window``).
|
|
76
|
+
|
|
77
|
+
Mirrors ``SettingsCache``: a short TTL bounds staleness for out-of-band
|
|
78
|
+
database edits, and the settings API invalidates durably through the
|
|
79
|
+
``settings`` namespace so every replica drops its snapshot at once.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# An expired snapshot is never fresh, whatever ``time.monotonic()`` returns
|
|
83
|
+
# this early after boot; 0.0 would read as fresh for the first TTL seconds
|
|
84
|
+
# of process uptime.
|
|
85
|
+
_EXPIRED = float("-inf")
|
|
86
|
+
|
|
87
|
+
def __init__(self, *, ttl_seconds: float = 5.0) -> None:
|
|
88
|
+
if ttl_seconds <= 0:
|
|
89
|
+
raise ValueError("ttl_seconds must be positive")
|
|
90
|
+
self._ttl_seconds = ttl_seconds
|
|
91
|
+
self._cached: dict[str, int] | None = None
|
|
92
|
+
self._cached_at = self._EXPIRED
|
|
93
|
+
self._lock = anyio.Lock()
|
|
94
|
+
|
|
95
|
+
async def get(self) -> Mapping[str, int]:
|
|
96
|
+
now = time.monotonic()
|
|
97
|
+
if self._cached is not None and now - self._cached_at < self._ttl_seconds:
|
|
98
|
+
return self._cached
|
|
99
|
+
|
|
100
|
+
async with self._lock:
|
|
101
|
+
now = time.monotonic()
|
|
102
|
+
if self._cached is not None and now - self._cached_at < self._ttl_seconds:
|
|
103
|
+
return self._cached
|
|
104
|
+
# Imported lazily: ``app.db.session`` builds the engine from the
|
|
105
|
+
# process settings on import, which unit tests of the resolver
|
|
106
|
+
# must not trigger.
|
|
107
|
+
from app.db.session import SessionLocal
|
|
108
|
+
from app.modules.settings.repository import ModelContextWindowOverridesRepository
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
async with SessionLocal() as session:
|
|
112
|
+
rows = await ModelContextWindowOverridesRepository(session).by_slug()
|
|
113
|
+
except Exception:
|
|
114
|
+
# The catalog must not 500 because the database is briefly
|
|
115
|
+
# unreachable: keep serving the last known rows (like
|
|
116
|
+
# ``SettingsCache.cached_row``) and retry on the next call. The
|
|
117
|
+
# timestamp is deliberately not refreshed.
|
|
118
|
+
if self._cached is None:
|
|
119
|
+
raise
|
|
120
|
+
return self._cached
|
|
121
|
+
self._cached = rows
|
|
122
|
+
self._cached_at = now
|
|
123
|
+
return rows
|
|
124
|
+
|
|
125
|
+
def clear(self) -> None:
|
|
126
|
+
"""Forget the snapshot entirely, without taking the lock.
|
|
127
|
+
|
|
128
|
+
Only for process-local resets with no concurrent load (test teardown);
|
|
129
|
+
every runtime path goes through ``invalidate``, which keeps the last
|
|
130
|
+
known rows as a fallback.
|
|
131
|
+
"""
|
|
132
|
+
self._cached = None
|
|
133
|
+
self._cached_at = self._EXPIRED
|
|
134
|
+
|
|
135
|
+
async def invalidate(self, *, propagate: bool = True) -> None:
|
|
136
|
+
"""Drop the snapshot and, unless ``propagate`` is False, durably bump the
|
|
137
|
+
cross-replica ``settings`` namespace before returning.
|
|
138
|
+
|
|
139
|
+
Only the snapshot's *freshness* is dropped, not the rows: the next
|
|
140
|
+
``get`` always reloads, but a load that fails while the database is
|
|
141
|
+
briefly unreachable can still fall back to the last known rows instead
|
|
142
|
+
of failing the catalog request. Settings-namespace bumps are frequent
|
|
143
|
+
and mostly unrelated to this table, so forgetting the rows on every one
|
|
144
|
+
of them would make the fallback almost never available.
|
|
145
|
+
|
|
146
|
+
The expiry happens under the lock, like ``SettingsCache``: a catalog
|
|
147
|
+
build already inside ``get`` and awaiting the database must finish
|
|
148
|
+
before the snapshot is expired, otherwise it would install its pre-write
|
|
149
|
+
rows afterwards and keep serving the old window until the TTL expires.
|
|
150
|
+
The poller callback registered for the namespace passes
|
|
151
|
+
``propagate=False`` so a remote bump never re-bumps (feedback-loop
|
|
152
|
+
prevention).
|
|
153
|
+
"""
|
|
154
|
+
async with self._lock:
|
|
155
|
+
self._cached_at = self._EXPIRED
|
|
156
|
+
if propagate:
|
|
157
|
+
poller = get_cache_invalidation_poller()
|
|
158
|
+
if poller is not None:
|
|
159
|
+
await poller.bump(NAMESPACE_SETTINGS)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
_cache = ModelContextWindowOverridesCache()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_model_context_window_overrides_cache() -> ModelContextWindowOverridesCache:
|
|
166
|
+
return _cache
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Request-scoped dashboard overrides for ``Settings`` fields that moved to the dashboard.
|
|
2
|
+
|
|
3
|
+
Precedence is fixed by ``configuration-tiers``: code default < environment <
|
|
4
|
+
dashboard. ``Settings`` already folds the first two layers; this module adds the
|
|
5
|
+
third for the fields that have a nullable ``dashboard_settings`` column of the
|
|
6
|
+
same name (a non-NULL column wins, NULL inherits the environment value).
|
|
7
|
+
|
|
8
|
+
Hot paths must not read the database: the request entry point (the
|
|
9
|
+
``DashboardOverridesMiddleware``) reads the ``SettingsCache`` snapshot once and
|
|
10
|
+
binds the overrides to a ``ContextVar``; consumers then call
|
|
11
|
+
``with_dashboard_overrides(get_settings())`` (the proxy service facade does this
|
|
12
|
+
for every ``_service_get_settings()`` caller) and read fields as before. Outside
|
|
13
|
+
a bound context — startup, schedulers, unit tests — the base ``Settings`` is
|
|
14
|
+
returned unchanged, so the environment fallback keeps applying there.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import contextlib
|
|
20
|
+
import contextvars
|
|
21
|
+
from collections.abc import Iterator, Mapping
|
|
22
|
+
from typing import TYPE_CHECKING, Final
|
|
23
|
+
|
|
24
|
+
from app.core.config.settings import Settings
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from app.db.models import DashboardSettings
|
|
28
|
+
|
|
29
|
+
# C2-1 timeouts: upstream timeouts and request budgets managed from the
|
|
30
|
+
# dashboard (Settings -> Advanced -> Upstream timeouts). The ``Settings`` field
|
|
31
|
+
# is the deprecated env alias; the ``dashboard_settings`` column of the same
|
|
32
|
+
# name wins when it is non-NULL.
|
|
33
|
+
DASHBOARD_TIMEOUT_SETTINGS: Final[tuple[str, ...]] = (
|
|
34
|
+
"upstream_connect_timeout_seconds",
|
|
35
|
+
"proxy_request_budget_seconds",
|
|
36
|
+
"compact_request_budget_seconds",
|
|
37
|
+
"transcription_request_budget_seconds",
|
|
38
|
+
"stream_idle_timeout_seconds",
|
|
39
|
+
"proxy_downstream_websocket_idle_timeout_seconds",
|
|
40
|
+
"sse_keepalive_interval_seconds",
|
|
41
|
+
# M1 stream/bridge budgets: the Responses stream request budget and the
|
|
42
|
+
# HTTP session bridge request budget share the Upstream timeouts card.
|
|
43
|
+
"http_responses_stream_request_budget_seconds",
|
|
44
|
+
"http_responses_session_bridge_request_budget_seconds",
|
|
45
|
+
# end M1 stream/bridge budgets
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# M3 codex prewarm: dashboard-managed behaviour switches, overlaid exactly like
|
|
49
|
+
# the timeouts. Keeping them here means their single consumer reads the field
|
|
50
|
+
# off ``Settings`` as before -- no extra snapshot read, and in particular no
|
|
51
|
+
# ``await`` in the hot path that reads them.
|
|
52
|
+
DASHBOARD_SWITCH_SETTINGS: Final[tuple[str, ...]] = ("http_responses_session_bridge_codex_prewarm_enabled",)
|
|
53
|
+
|
|
54
|
+
# Dashboard-managed *enumerated* values, overlaid the same way. Unlike the
|
|
55
|
+
# switches these are strings, which is why ``DashboardOverrideValue`` admits
|
|
56
|
+
# ``str``: the thread cache identity mode has to be overlaid here and not only
|
|
57
|
+
# folded into the settings API response, or the proxy would keep serving
|
|
58
|
+
# ``shared`` while ``GET /api/settings`` reported the operator's choice as the
|
|
59
|
+
# effective value.
|
|
60
|
+
DASHBOARD_MODE_SETTINGS: Final[tuple[str, ...]] = ("thread_cache_identity_mode",)
|
|
61
|
+
|
|
62
|
+
# Every ``Settings`` field whose dashboard column overrides the environment
|
|
63
|
+
# value at runtime (the account-capacity caps have their own consumer path
|
|
64
|
+
# through ``SettingsService``, and the resilience toggles their own task-bound
|
|
65
|
+
# ``ResilienceToggles``; neither is overlaid here).
|
|
66
|
+
DASHBOARD_OVERRIDE_SETTINGS: Final[tuple[str, ...]] = (
|
|
67
|
+
DASHBOARD_TIMEOUT_SETTINGS + DASHBOARD_SWITCH_SETTINGS + DASHBOARD_MODE_SETTINGS
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
type DashboardOverrideValue = float | bool | str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dashboard_overrides(row: DashboardSettings) -> dict[str, DashboardOverrideValue]:
|
|
74
|
+
"""Non-NULL dashboard values keyed by ``Settings`` field name."""
|
|
75
|
+
overrides: dict[str, DashboardOverrideValue] = {}
|
|
76
|
+
for name in DASHBOARD_OVERRIDE_SETTINGS:
|
|
77
|
+
value = getattr(row, name, None)
|
|
78
|
+
if value is not None:
|
|
79
|
+
overrides[name] = value
|
|
80
|
+
return overrides
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DashboardOverlay:
|
|
84
|
+
"""Overrides bound to one request, with the overlaid ``Settings`` memoised per base object.
|
|
85
|
+
|
|
86
|
+
``Settings`` is a process-wide cached singleton, so the copy is built once
|
|
87
|
+
per request; a test that swaps ``get_settings`` gets its own overlay.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
__slots__ = ("_applied", "_base", "overrides")
|
|
91
|
+
|
|
92
|
+
def __init__(self, overrides: Mapping[str, DashboardOverrideValue]) -> None:
|
|
93
|
+
self.overrides: Mapping[str, DashboardOverrideValue] = dict(overrides)
|
|
94
|
+
self._base: Settings | None = None
|
|
95
|
+
self._applied: Settings | None = None
|
|
96
|
+
|
|
97
|
+
def apply(self, base: Settings) -> Settings:
|
|
98
|
+
if not self.overrides:
|
|
99
|
+
return base
|
|
100
|
+
if self._applied is not None and self._base is base:
|
|
101
|
+
return self._applied
|
|
102
|
+
applied = base.model_copy(update=dict(self.overrides))
|
|
103
|
+
self._base = base
|
|
104
|
+
self._applied = applied
|
|
105
|
+
return applied
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_OVERLAY: contextvars.ContextVar[DashboardOverlay | None] = contextvars.ContextVar(
|
|
109
|
+
"dashboard_settings_overlay", default=None
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def with_dashboard_overrides(base: Settings) -> Settings:
|
|
114
|
+
"""``base`` with the request-bound dashboard values applied (``base`` itself when none are bound)."""
|
|
115
|
+
overlay = _OVERLAY.get()
|
|
116
|
+
if overlay is None:
|
|
117
|
+
return base
|
|
118
|
+
return overlay.apply(base)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def effective_settings(row: DashboardSettings, base: Settings) -> Settings:
|
|
122
|
+
"""``base`` with ``row``'s non-NULL dashboard values applied, independent of the bound context.
|
|
123
|
+
|
|
124
|
+
For code that already holds a dashboard snapshot outside a request (startup
|
|
125
|
+
hooks, background loops) and must honour the dashboard the same way the
|
|
126
|
+
request path does.
|
|
127
|
+
"""
|
|
128
|
+
return DashboardOverlay(dashboard_overrides(row)).apply(base)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def bind_dashboard_overrides(row: DashboardSettings) -> contextvars.Token[DashboardOverlay | None]:
|
|
132
|
+
return _OVERLAY.set(DashboardOverlay(dashboard_overrides(row)))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def reset_dashboard_overrides(token: contextvars.Token[DashboardOverlay | None]) -> None:
|
|
136
|
+
_OVERLAY.reset(token)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@contextlib.contextmanager
|
|
140
|
+
def dashboard_overrides_bound(row: DashboardSettings) -> Iterator[None]:
|
|
141
|
+
token = bind_dashboard_overrides(row)
|
|
142
|
+
try:
|
|
143
|
+
yield
|
|
144
|
+
finally:
|
|
145
|
+
reset_dashboard_overrides(token)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Resolver for dashboard-tier settings that inherit an environment or code default.
|
|
2
|
+
|
|
3
|
+
One place combines the three layers (``configuration-tiers``): code default <
|
|
4
|
+
process environment < dashboard column. ``SettingsService`` uses it to build
|
|
5
|
+
the settings API's ``provenance`` map, and hot paths that read a dashboard
|
|
6
|
+
snapshot use it to derive the effective value without re-implementing the
|
|
7
|
+
precedence.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
type SettingSource = Literal["dashboard", "env", "default"]
|
|
16
|
+
type SettingScalar = int | float | str | bool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class InheritableValue[T: SettingScalar]:
|
|
21
|
+
"""Effective value of a dashboard-tier setting and where it came from.
|
|
22
|
+
|
|
23
|
+
``source`` is ``"dashboard"`` when the dashboard column is non-NULL,
|
|
24
|
+
``"env"`` when the column is NULL and the environment value differs from
|
|
25
|
+
the code default, and ``"default"`` otherwise. ``env_value`` is ``None``
|
|
26
|
+
for database-only settings that have no environment fallback.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
value: T
|
|
30
|
+
source: SettingSource
|
|
31
|
+
env_value: T | None
|
|
32
|
+
default: T
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def resolve_inheritable[T: SettingScalar](
|
|
36
|
+
column_value: T | None, env_value: T | None, default: T
|
|
37
|
+
) -> InheritableValue[T]:
|
|
38
|
+
"""Resolve one inheritable setting as code default < environment < dashboard.
|
|
39
|
+
|
|
40
|
+
This is the only place that combines the three layers; call sites must not
|
|
41
|
+
re-implement the precedence (``configuration-tiers``).
|
|
42
|
+
"""
|
|
43
|
+
if column_value is not None:
|
|
44
|
+
return InheritableValue(column_value, "dashboard", env_value, default)
|
|
45
|
+
if env_value is not None and env_value != default:
|
|
46
|
+
return InheritableValue(env_value, "env", env_value, default)
|
|
47
|
+
return InheritableValue(default, "default", env_value, default)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import logging
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
|
10
|
+
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
|
|
13
|
+
from app.core.config.settings import get_settings
|
|
14
|
+
from app.core.crypto import get_or_create_key
|
|
15
|
+
from app.db.models import RuntimeSentinel
|
|
16
|
+
from app.db.sqlite_lock_retry import retry_on_sqlite_lock
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
ENCRYPTION_KEY_FINGERPRINT_SENTINEL = "encryption_key_fingerprint"
|
|
21
|
+
_FINGERPRINT_PREFIX_CHARS = 12
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EncryptionKeyFingerprintMismatchError(RuntimeError):
|
|
25
|
+
"""Raised when this replica's encryption key differs from the shared sentinel."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def compute_encryption_key_fingerprint(key_file: Path | None = None) -> str:
|
|
29
|
+
"""Return ``sha256:<hex>`` over the raw encryption key bytes."""
|
|
30
|
+
key = get_or_create_key(key_file)
|
|
31
|
+
return f"sha256:{hashlib.sha256(key).hexdigest()}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _fingerprint_prefix(fingerprint: str) -> str:
|
|
35
|
+
return fingerprint[: len("sha256:") + _FINGERPRINT_PREFIX_CHARS]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _stamp_if_absent(session: AsyncSession, fingerprint: str) -> None:
|
|
39
|
+
values = {"name": ENCRYPTION_KEY_FINGERPRINT_SENTINEL, "value": fingerprint}
|
|
40
|
+
dialect = session.get_bind().dialect.name
|
|
41
|
+
if dialect == "postgresql":
|
|
42
|
+
stmt = pg_insert(RuntimeSentinel).values(**values).on_conflict_do_nothing(index_elements=[RuntimeSentinel.name])
|
|
43
|
+
await session.execute(stmt)
|
|
44
|
+
elif dialect == "sqlite":
|
|
45
|
+
stmt = (
|
|
46
|
+
sqlite_insert(RuntimeSentinel)
|
|
47
|
+
.values(**values)
|
|
48
|
+
.on_conflict_do_nothing(index_elements=[RuntimeSentinel.name])
|
|
49
|
+
)
|
|
50
|
+
await session.execute(stmt)
|
|
51
|
+
else:
|
|
52
|
+
existing = await session.scalar(
|
|
53
|
+
select(RuntimeSentinel).where(RuntimeSentinel.name == ENCRYPTION_KEY_FINGERPRINT_SENTINEL)
|
|
54
|
+
)
|
|
55
|
+
if existing is None:
|
|
56
|
+
session.add(RuntimeSentinel(**values))
|
|
57
|
+
await session.commit()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def verify_encryption_key_fingerprint(
|
|
61
|
+
session_factory: Callable[[], AsyncSession] | None = None,
|
|
62
|
+
*,
|
|
63
|
+
key_file: Path | None = None,
|
|
64
|
+
mode: str | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Stamp or verify the encryption-key fingerprint sentinel in the shared database.
|
|
67
|
+
|
|
68
|
+
The first replica to boot atomically stamps ``sha256(key)`` into
|
|
69
|
+
``runtime_sentinels`` (insert-if-absent); every replica then compares its
|
|
70
|
+
local fingerprint against the stored value. A mismatch means the replicas
|
|
71
|
+
do not share the same encryption key material, which fails
|
|
72
|
+
replica-dependently at use time (dashboard cookie 401s, undecryptable
|
|
73
|
+
proxy passwords, ``bridge_forward_invalid`` HMAC rejections), so the
|
|
74
|
+
default mode refuses startup with a remediation message.
|
|
75
|
+
"""
|
|
76
|
+
settings = get_settings()
|
|
77
|
+
effective_mode = mode if mode is not None else settings.encryption_key_fingerprint_mode
|
|
78
|
+
if effective_mode == "off":
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if session_factory is None:
|
|
82
|
+
from app.db.session import SessionLocal
|
|
83
|
+
|
|
84
|
+
make_session: Callable[[], AsyncSession] = SessionLocal
|
|
85
|
+
else:
|
|
86
|
+
make_session = session_factory
|
|
87
|
+
|
|
88
|
+
local_fingerprint = compute_encryption_key_fingerprint(key_file)
|
|
89
|
+
|
|
90
|
+
async def stamp_and_read() -> str | None:
|
|
91
|
+
async with make_session() as session:
|
|
92
|
+
await _stamp_if_absent(session, local_fingerprint)
|
|
93
|
+
return await session.scalar(
|
|
94
|
+
select(RuntimeSentinel.value).where(RuntimeSentinel.name == ENCRYPTION_KEY_FINGERPRINT_SENTINEL)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# A lock failure that outlives the budget still propagates and fails
|
|
98
|
+
# startup, exactly as it did before the retry was factored out.
|
|
99
|
+
stored = await retry_on_sqlite_lock(stamp_and_read, what="encryption-key fingerprint stamp")
|
|
100
|
+
|
|
101
|
+
if stored is None or stored == local_fingerprint:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
message = (
|
|
105
|
+
"Encryption key mismatch across replicas: this replica's key fingerprint "
|
|
106
|
+
f"{_fingerprint_prefix(local_fingerprint)}... does not match the fingerprint "
|
|
107
|
+
f"{_fingerprint_prefix(stored)}... stamped in the shared database. Every replica must "
|
|
108
|
+
"mount the same encryption.key file. If you rotated the key intentionally, delete the "
|
|
109
|
+
f"'{ENCRYPTION_KEY_FINGERPRINT_SENTINEL}' row from runtime_sentinels, or set "
|
|
110
|
+
"CODEX_LB_ENCRYPTION_KEY_FINGERPRINT_MODE=warn to bypass this check."
|
|
111
|
+
)
|
|
112
|
+
if effective_mode == "warn":
|
|
113
|
+
logger.error(message)
|
|
114
|
+
return
|
|
115
|
+
raise EncryptionKeyFingerprintMismatchError(message)
|