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,122 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
6
|
+
|
|
7
|
+
<title>Sign into Codex LB</title>
|
|
8
|
+
<link rel="icon"
|
|
9
|
+
href="data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"%3E%3Cpath stroke="%23000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/%3E%3C/svg%3E"
|
|
10
|
+
type="image/svg+xml">
|
|
11
|
+
<style>
|
|
12
|
+
.container {
|
|
13
|
+
margin: auto;
|
|
14
|
+
height: 100%;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
position: relative;
|
|
19
|
+
background: white;
|
|
20
|
+
|
|
21
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.inner-container {
|
|
25
|
+
width: 400px;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
justify-content: flex-start;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 20px;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.content {
|
|
34
|
+
align-self: stretch;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
justify-content: flex-start;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 20px;
|
|
39
|
+
display: flex;
|
|
40
|
+
margin-top: 15vh;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.title {
|
|
44
|
+
text-align: center;
|
|
45
|
+
color: var(--text-primary, #0D0D0D);
|
|
46
|
+
font-size: 32px;
|
|
47
|
+
font-weight: 400;
|
|
48
|
+
line-height: 40px;
|
|
49
|
+
word-wrap: break-word;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.setup-description {
|
|
53
|
+
align-self: stretch;
|
|
54
|
+
color: var(--text-secondary, #5D5D5D);
|
|
55
|
+
font-size: 14px;
|
|
56
|
+
font-weight: 400;
|
|
57
|
+
line-height: 20px;
|
|
58
|
+
word-wrap: break-word;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.logo {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
width: 4rem;
|
|
66
|
+
height: 4rem;
|
|
67
|
+
border-radius: 16px;
|
|
68
|
+
border: .5px solid rgba(0, 0, 0, 0.1);
|
|
69
|
+
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 16px 0px;
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
background-color: rgb(255, 255, 255);
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
|
|
76
|
+
<body>
|
|
77
|
+
<div class="container">
|
|
78
|
+
<div class="inner-container">
|
|
79
|
+
<div class="content">
|
|
80
|
+
<div class="logo">
|
|
81
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32">
|
|
82
|
+
<path stroke="#000" stroke-linecap="round" stroke-width="2.484"
|
|
83
|
+
d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z">
|
|
84
|
+
</path>
|
|
85
|
+
</svg>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="title">Signed in to Codex LB</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="close-box" style="display: flex;">
|
|
90
|
+
<div class="setup-description" id="close-message">
|
|
91
|
+
Closing in <span id="close-countdown">3</span>s...
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
<script>
|
|
97
|
+
(() => {
|
|
98
|
+
const countdown = document.getElementById("close-countdown");
|
|
99
|
+
const message = document.getElementById("close-message");
|
|
100
|
+
let remaining = 3;
|
|
101
|
+
const tick = () => {
|
|
102
|
+
if (remaining <= 0) {
|
|
103
|
+
if (message) {
|
|
104
|
+
message.textContent = "Closing...";
|
|
105
|
+
}
|
|
106
|
+
window.close();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (countdown) {
|
|
110
|
+
countdown.textContent = String(remaining);
|
|
111
|
+
} else if (message) {
|
|
112
|
+
message.textContent = `Closing in ${remaining}s...`;
|
|
113
|
+
}
|
|
114
|
+
remaining -= 1;
|
|
115
|
+
window.setTimeout(tick, 1000);
|
|
116
|
+
};
|
|
117
|
+
tick();
|
|
118
|
+
})();
|
|
119
|
+
</script>
|
|
120
|
+
</body>
|
|
121
|
+
|
|
122
|
+
</html>
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Private implementation units for proxy account selection."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Replica-local recent error rate per account, feeding selection weights.
|
|
2
|
+
|
|
3
|
+
The generic ``error_count`` is a latch: it is zeroed by the next success, so
|
|
4
|
+
an account that fails one request in three never looks unhealthy to the
|
|
5
|
+
weighted strategies even though a third of the traffic routed there pays the
|
|
6
|
+
failure (and, for overload rejections, the 30-90 s wait before failover).
|
|
7
|
+
|
|
8
|
+
This module keeps a small minute-bucket window of upstream outcomes per
|
|
9
|
+
account -- successes and the account-attributable transient failures that
|
|
10
|
+
reach ``LoadBalancer.record_errors`` (rate limits, quota, permanent failures
|
|
11
|
+
and account-neutral rejections are handled by their own paths and never
|
|
12
|
+
counted here) -- and derives a draw-weight multiplier
|
|
13
|
+
``max(floor, 1 - error_rate)`` once the window holds enough samples. Weighted
|
|
14
|
+
strategies multiply each candidate's weight by it, so a flaky account keeps
|
|
15
|
+
receiving *some* traffic (the floor) and recovers its share as soon as its
|
|
16
|
+
window clears. Deterministic strategies are unaffected.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
|
|
23
|
+
from app.modules.proxy._load_balancer.types import RuntimeState
|
|
24
|
+
|
|
25
|
+
_BUCKET_SECONDS = 60
|
|
26
|
+
# Ten minutes of outcomes: long enough to smooth a burst, short enough that a
|
|
27
|
+
# recovered account regains its full share within one window.
|
|
28
|
+
ERROR_RATE_WINDOW_SECONDS = 600.0
|
|
29
|
+
# Below this many outcomes the rate is noise; the multiplier stays neutral.
|
|
30
|
+
ERROR_RATE_MIN_SAMPLES = 10
|
|
31
|
+
# A fully failing account keeps 5% of its weight so the window keeps sampling
|
|
32
|
+
# it and the discount lifts as soon as it recovers.
|
|
33
|
+
ERROR_RATE_WEIGHT_FLOOR = 0.05
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ErrorRateWeightingPolicy:
|
|
38
|
+
"""Operator knob: the dashboard setting ``proxy_account_error_rate_weighting_enabled``
|
|
39
|
+
(environment fallback ``CODEX_LB_PROXY_ACCOUNT_ERROR_RATE_WEIGHTING_ENABLED``),
|
|
40
|
+
resolved by the caller into ``RoutingTunables`` and passed in -- this module
|
|
41
|
+
never reads settings itself (C2-2 routing/overload)."""
|
|
42
|
+
|
|
43
|
+
enabled: bool = True
|
|
44
|
+
window_seconds: float = ERROR_RATE_WINDOW_SECONDS
|
|
45
|
+
min_samples: int = ERROR_RATE_MIN_SAMPLES
|
|
46
|
+
weight_floor: float = ERROR_RATE_WEIGHT_FLOOR
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _bucket(now: float) -> int:
|
|
50
|
+
return int(now // _BUCKET_SECONDS)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _prune_locked(runtime: RuntimeState, now: float, window_seconds: float) -> dict[int, list[int]]:
|
|
54
|
+
buckets = runtime.outcome_buckets
|
|
55
|
+
if buckets is None:
|
|
56
|
+
buckets = runtime.outcome_buckets = {}
|
|
57
|
+
return buckets
|
|
58
|
+
oldest_kept = _bucket(now - window_seconds)
|
|
59
|
+
stale = [key for key in buckets if key < oldest_kept]
|
|
60
|
+
for key in stale:
|
|
61
|
+
del buckets[key]
|
|
62
|
+
return buckets
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def record_outcome_locked(
|
|
66
|
+
runtime: RuntimeState,
|
|
67
|
+
now: float,
|
|
68
|
+
*,
|
|
69
|
+
success: bool,
|
|
70
|
+
count: int = 1,
|
|
71
|
+
window_seconds: float | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Record ``count`` outcomes at ``now``. Caller holds the per-account lock."""
|
|
74
|
+
if count < 1:
|
|
75
|
+
return
|
|
76
|
+
window = window_seconds if window_seconds is not None else ERROR_RATE_WINDOW_SECONDS
|
|
77
|
+
buckets = _prune_locked(runtime, now, window)
|
|
78
|
+
entry = buckets.setdefault(_bucket(now), [0, 0])
|
|
79
|
+
entry[0 if success else 1] += count
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def recent_outcomes(runtime: RuntimeState | None, now: float, *, window_seconds: float) -> tuple[int, int]:
|
|
83
|
+
"""Return ``(successes, failures)`` observed inside the window (read-only)."""
|
|
84
|
+
if runtime is None or not runtime.outcome_buckets:
|
|
85
|
+
return 0, 0
|
|
86
|
+
oldest_kept = _bucket(now - window_seconds)
|
|
87
|
+
successes = failures = 0
|
|
88
|
+
for key, (ok, failed) in runtime.outcome_buckets.items():
|
|
89
|
+
if key >= oldest_kept:
|
|
90
|
+
successes += ok
|
|
91
|
+
failures += failed
|
|
92
|
+
return successes, failures
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def error_rate_weight_multiplier(
|
|
96
|
+
runtime: RuntimeState | None,
|
|
97
|
+
now: float,
|
|
98
|
+
*,
|
|
99
|
+
policy: ErrorRateWeightingPolicy,
|
|
100
|
+
) -> float:
|
|
101
|
+
"""Draw-weight multiplier in ``[floor, 1.0]`` for the account's recent error rate.
|
|
102
|
+
|
|
103
|
+
Neutral (``1.0``) while disabled or while the window holds fewer than
|
|
104
|
+
``min_samples`` outcomes, so a quiet or brand-new account is never
|
|
105
|
+
penalized on thin evidence.
|
|
106
|
+
"""
|
|
107
|
+
effective = policy
|
|
108
|
+
if not effective.enabled:
|
|
109
|
+
return 1.0
|
|
110
|
+
successes, failures = recent_outcomes(runtime, now, window_seconds=effective.window_seconds)
|
|
111
|
+
samples = successes + failures
|
|
112
|
+
if samples < max(1, effective.min_samples):
|
|
113
|
+
return 1.0
|
|
114
|
+
error_rate = failures / samples
|
|
115
|
+
floor = min(max(effective.weight_floor, 0.0), 1.0)
|
|
116
|
+
return max(floor, 1.0 - error_rate)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Combine the fleet-relative latency signals into one draw-weight multiplier.
|
|
2
|
+
|
|
3
|
+
Two replica-local signals describe how slow an account is relative to its
|
|
4
|
+
siblings: first-token latency per account (``ttft_cohort.py``) and output
|
|
5
|
+
throughput per (account, model) (``throughput_cohort.py``). Each yields a
|
|
6
|
+
multiplier in ``[floor, 1.0]``. This module applies their **minimum** to the
|
|
7
|
+
candidate's ``selection_weight_multiplier`` -- never their product: the two
|
|
8
|
+
signals measure the same defect (a slower serving path) from two angles, so an
|
|
9
|
+
account slow on both would otherwise be punished twice for one cause and drop
|
|
10
|
+
below the floor either signal promises.
|
|
11
|
+
|
|
12
|
+
Applied from ``load_balancer._build_states`` for the model the selection is
|
|
13
|
+
for; the weighted strategies multiply the candidate's weight by it alongside
|
|
14
|
+
the error-rate multiplier. Sticky owners, deterministic probes and
|
|
15
|
+
deterministic strategies never consult ``selection_weight_multiplier``.
|
|
16
|
+
|
|
17
|
+
Transition log: each signal has its own gate on the runtime, matching its
|
|
18
|
+
scope. The first-token multiplier is model-agnostic and gated once per account
|
|
19
|
+
(``RuntimeState.ttft_weight``), so one TTFT transition is one line no matter
|
|
20
|
+
how many models are requested; the throughput multiplier is gated per
|
|
21
|
+
(account, model) (``RuntimeState.tps_weights``), so alternating requests for a
|
|
22
|
+
model where the account is slow and one where it is not do not flap the log.
|
|
23
|
+
Only non-neutral throughput weights are stored: the map is keyed by the
|
|
24
|
+
requested model string, and dropping neutral entries keeps it bounded by the
|
|
25
|
+
models the account is actually discounted on (which requires in-window
|
|
26
|
+
evidence on that model) rather than by every model string a client sends.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import logging
|
|
32
|
+
from collections.abc import Iterable, Mapping
|
|
33
|
+
|
|
34
|
+
from app.core.balancer import AccountState
|
|
35
|
+
from app.modules.proxy._load_balancer.throughput_cohort import fleet_tps_estimates, tps_weight_multiplier
|
|
36
|
+
from app.modules.proxy._load_balancer.ttft_cohort import fleet_ttft_estimates, ttft_weight_multiplier
|
|
37
|
+
from app.modules.proxy._load_balancer.types import RuntimeState
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
# Transition log gate: a multiplier move smaller than this is not logged (a move of exactly this much is).
|
|
42
|
+
_LOG_DELTA = 0.1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def combine_cohort_multipliers(ttft_multiplier: float, tps_multiplier: float) -> tuple[float, str]:
|
|
46
|
+
"""``(multiplier, signal)``: the smaller of the two multipliers and which signal set it.
|
|
47
|
+
|
|
48
|
+
``signal`` is ``ttft``, ``tps``, ``both`` (equal and below neutral) or
|
|
49
|
+
``none`` (neutral). The multipliers are never compounded.
|
|
50
|
+
"""
|
|
51
|
+
if ttft_multiplier >= 1.0 and tps_multiplier >= 1.0:
|
|
52
|
+
return 1.0, "none"
|
|
53
|
+
if ttft_multiplier < tps_multiplier:
|
|
54
|
+
return ttft_multiplier, "ttft"
|
|
55
|
+
if tps_multiplier < ttft_multiplier:
|
|
56
|
+
return tps_multiplier, "tps"
|
|
57
|
+
return ttft_multiplier, "both"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def last_tps_weight(runtime: RuntimeState, model: str | None) -> float:
|
|
61
|
+
"""The throughput multiplier last applied to ``runtime`` for ``model`` (``1.0`` when neutral or unknown)."""
|
|
62
|
+
if runtime.tps_weights is None or not model:
|
|
63
|
+
return 1.0
|
|
64
|
+
return runtime.tps_weights.get(model, 1.0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def last_cohort_weight(runtime: RuntimeState, model: str | None) -> float:
|
|
68
|
+
"""The combined multiplier the last builds applied to ``runtime`` for ``model`` (``1.0`` before any build)."""
|
|
69
|
+
return min(runtime.ttft_weight, last_tps_weight(runtime, model))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _transition(previous: float, current: float) -> bool:
|
|
73
|
+
return (previous < 1.0) != (current < 1.0) or abs(current - previous) >= _LOG_DELTA
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _store_tps_weight(runtime: RuntimeState, model: str | None, multiplier: float) -> None:
|
|
77
|
+
if not model:
|
|
78
|
+
return # a build without a model is always neutral on throughput
|
|
79
|
+
if multiplier >= 1.0:
|
|
80
|
+
if runtime.tps_weights is not None:
|
|
81
|
+
runtime.tps_weights.pop(model, None)
|
|
82
|
+
if not runtime.tps_weights:
|
|
83
|
+
runtime.tps_weights = None
|
|
84
|
+
return
|
|
85
|
+
if runtime.tps_weights is None:
|
|
86
|
+
runtime.tps_weights = {}
|
|
87
|
+
runtime.tps_weights[model] = multiplier
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def apply_latency_cohort_weights(
|
|
91
|
+
states: Iterable[AccountState],
|
|
92
|
+
runtime_by_account_id: Mapping[str, RuntimeState],
|
|
93
|
+
*,
|
|
94
|
+
now: float,
|
|
95
|
+
model: str | None = None,
|
|
96
|
+
log_transitions: bool = True,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Multiply each state's ``selection_weight_multiplier`` by its latency cohort weight for ``model``.
|
|
99
|
+
|
|
100
|
+
Both fleet references are computed over the whole runtime map, not only the
|
|
101
|
+
states being built, so a selection narrowed to a subset (API-key scoping,
|
|
102
|
+
model catalog, continuity owner) is still weighed against the full pool.
|
|
103
|
+
The throughput signal is neutral when ``model`` is ``None`` or when fewer
|
|
104
|
+
than the minimum number of accounts hold evidence for that model; the
|
|
105
|
+
first-token signal is model-agnostic. ``log_transitions=False`` is for
|
|
106
|
+
builds on a detached runtime snapshot (observe-only admission): the
|
|
107
|
+
multiplier is identical but the weight written there is discarded, so
|
|
108
|
+
logging from it would repeat the transition on the next live build.
|
|
109
|
+
"""
|
|
110
|
+
ttft_estimates, fleet_ttft_ms = fleet_ttft_estimates(runtime_by_account_id, now)
|
|
111
|
+
tps_estimates, fleet_tps = fleet_tps_estimates(runtime_by_account_id, model, now)
|
|
112
|
+
for state in states:
|
|
113
|
+
runtime = runtime_by_account_id.get(state.account_id)
|
|
114
|
+
if runtime is None:
|
|
115
|
+
continue
|
|
116
|
+
account_ttft_ms = ttft_estimates.get(state.account_id)
|
|
117
|
+
account_tps = tps_estimates.get(state.account_id)
|
|
118
|
+
ttft_multiplier = (
|
|
119
|
+
1.0
|
|
120
|
+
if fleet_ttft_ms is None or account_ttft_ms is None
|
|
121
|
+
else ttft_weight_multiplier(account_ttft_ms, fleet_ttft_ms)
|
|
122
|
+
)
|
|
123
|
+
tps_multiplier = (
|
|
124
|
+
1.0 if fleet_tps is None or account_tps is None else tps_weight_multiplier(account_tps, fleet_tps)
|
|
125
|
+
)
|
|
126
|
+
multiplier, signal = combine_cohort_multipliers(ttft_multiplier, tps_multiplier)
|
|
127
|
+
if multiplier != 1.0:
|
|
128
|
+
state.selection_weight_multiplier *= multiplier
|
|
129
|
+
ttft_changed = _transition(runtime.ttft_weight, ttft_multiplier)
|
|
130
|
+
tps_changed = _transition(last_tps_weight(runtime, model), tps_multiplier)
|
|
131
|
+
if log_transitions and (ttft_changed or tps_changed):
|
|
132
|
+
# Account identifiers are deliberately omitted: ``_build_states``
|
|
133
|
+
# carries no redaction flag, so this line must stay identifier-free.
|
|
134
|
+
logger.info(
|
|
135
|
+
"latency_cohort_weight_change multiplier=%.2f signal=%s model=%s changed=%s ttft_multiplier=%.2f "
|
|
136
|
+
"tps_multiplier=%.2f ttft_ms=%s fleet_ttft_ms=%s tps=%s fleet_tps=%s ttft_samples=%d "
|
|
137
|
+
"tps_samples=%d accounts_with_ttft_evidence=%d accounts_with_tps_evidence=%d",
|
|
138
|
+
multiplier,
|
|
139
|
+
signal,
|
|
140
|
+
model,
|
|
141
|
+
"both" if ttft_changed and tps_changed else ("ttft" if ttft_changed else "tps"),
|
|
142
|
+
ttft_multiplier,
|
|
143
|
+
tps_multiplier,
|
|
144
|
+
None if account_ttft_ms is None else round(account_ttft_ms),
|
|
145
|
+
None if fleet_ttft_ms is None else round(fleet_ttft_ms),
|
|
146
|
+
None if account_tps is None else round(account_tps, 1),
|
|
147
|
+
None if fleet_tps is None else round(fleet_tps, 1),
|
|
148
|
+
len(runtime.ttft_samples or ()),
|
|
149
|
+
0 if not model else len((runtime.tps_samples or {}).get(model, ())),
|
|
150
|
+
len(ttft_estimates),
|
|
151
|
+
len(tps_estimates),
|
|
152
|
+
)
|
|
153
|
+
runtime.ttft_weight = ttft_multiplier
|
|
154
|
+
_store_tps_weight(runtime, model, tps_multiplier)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from app.core.openai.model_registry import ModelRegistry, canonical_service_tier_value
|
|
7
|
+
from app.core.plan_types import account_plan_matches_allowed, normalize_account_plan_type
|
|
8
|
+
from app.db.models import Account, AdditionalUsageHistory
|
|
9
|
+
from app.modules.proxy.additional_model_limits import get_additional_quota_key_for_model_id
|
|
10
|
+
from app.modules.usage.additional_quota_keys import (
|
|
11
|
+
canonicalize_additional_quota_key,
|
|
12
|
+
get_additional_quota_definition,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_ADDITIONAL_QUOTA_EXEMPT_PLAN_TYPES = frozenset({"free", "plus", "edu"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class CatalogOmissionQuotaAdmission:
|
|
20
|
+
normalized_model: str
|
|
21
|
+
canonical_quota_key: str
|
|
22
|
+
normalized_effective_service_tier: str | None
|
|
23
|
+
|
|
24
|
+
def matches(self, *, requested_model: str, service_tier: str | None) -> bool:
|
|
25
|
+
return (
|
|
26
|
+
self.normalized_model == _normalize_model_id(requested_model)
|
|
27
|
+
and self.canonical_quota_key == _gated_limit_name_for_model(requested_model)
|
|
28
|
+
and self.normalized_effective_service_tier == _effective_model_service_tier(service_tier)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class _ModelAccountFilterResult:
|
|
34
|
+
accounts: list[Account]
|
|
35
|
+
general_model_account_ids: frozenset[str] | None
|
|
36
|
+
# Tier actually applied to the filter, after dropping tiers the model does
|
|
37
|
+
# not advertise. Set only when the tier narrowed the pool, so an empty
|
|
38
|
+
# result can say the tier excluded the accounts rather than the model.
|
|
39
|
+
applied_service_tier: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _filter_accounts_for_model_with_catalog_evidence(
|
|
43
|
+
accounts: list[Account],
|
|
44
|
+
model: str,
|
|
45
|
+
*,
|
|
46
|
+
registry: ModelRegistry,
|
|
47
|
+
service_tier: str | None = None,
|
|
48
|
+
additional_quota_can_override_account_catalog: bool = False,
|
|
49
|
+
) -> _ModelAccountFilterResult:
|
|
50
|
+
account_indexes_cover_selection = True
|
|
51
|
+
get_snapshot = getattr(registry, "get_snapshot", None)
|
|
52
|
+
if callable(get_snapshot):
|
|
53
|
+
snapshot = get_snapshot()
|
|
54
|
+
account_indexes_cover_selection = snapshot is not None and all(
|
|
55
|
+
account.id in snapshot.account_plans for account in accounts
|
|
56
|
+
)
|
|
57
|
+
account_ids_for_model = getattr(registry, "account_ids_for_model", None)
|
|
58
|
+
general_model_account_ids = (
|
|
59
|
+
account_ids_for_model(model) if callable(account_ids_for_model) and account_indexes_cover_selection else None
|
|
60
|
+
)
|
|
61
|
+
if general_model_account_ids is None or additional_quota_can_override_account_catalog:
|
|
62
|
+
model_accounts = accounts
|
|
63
|
+
else:
|
|
64
|
+
model_accounts = [account for account in accounts if account.id in general_model_account_ids]
|
|
65
|
+
|
|
66
|
+
normalized_service_tier = service_tier.strip().lower() if service_tier is not None else None
|
|
67
|
+
effective_service_tier = None if normalized_service_tier in {"auto", "default"} else service_tier
|
|
68
|
+
if effective_service_tier is not None:
|
|
69
|
+
allowed_account_ids = (
|
|
70
|
+
registry.account_ids_for_model_service_tier(model, effective_service_tier)
|
|
71
|
+
if account_indexes_cover_selection
|
|
72
|
+
else None
|
|
73
|
+
)
|
|
74
|
+
if allowed_account_ids is not None:
|
|
75
|
+
if additional_quota_can_override_account_catalog and general_model_account_ids is not None:
|
|
76
|
+
allowed_plans = registry.plan_types_for_model_service_tier(model, effective_service_tier)
|
|
77
|
+
tier_filtered_accounts: list[Account] = []
|
|
78
|
+
for account in accounts:
|
|
79
|
+
if account.id in general_model_account_ids:
|
|
80
|
+
if account.id in allowed_account_ids:
|
|
81
|
+
tier_filtered_accounts.append(account)
|
|
82
|
+
elif allowed_plans is None or account_plan_matches_allowed(account.plan_type, allowed_plans):
|
|
83
|
+
tier_filtered_accounts.append(account)
|
|
84
|
+
model_accounts = tier_filtered_accounts
|
|
85
|
+
else:
|
|
86
|
+
model_accounts = [account for account in model_accounts if account.id in allowed_account_ids]
|
|
87
|
+
return _ModelAccountFilterResult(
|
|
88
|
+
accounts=model_accounts,
|
|
89
|
+
general_model_account_ids=general_model_account_ids,
|
|
90
|
+
applied_service_tier=effective_service_tier,
|
|
91
|
+
)
|
|
92
|
+
allowed_plans = registry.plan_types_for_model_service_tier(model, effective_service_tier)
|
|
93
|
+
else:
|
|
94
|
+
allowed_plans = registry.plan_types_for_model(model)
|
|
95
|
+
if allowed_plans is not None:
|
|
96
|
+
model_accounts = [
|
|
97
|
+
account for account in model_accounts if account_plan_matches_allowed(account.plan_type, allowed_plans)
|
|
98
|
+
]
|
|
99
|
+
return _ModelAccountFilterResult(
|
|
100
|
+
accounts=model_accounts,
|
|
101
|
+
general_model_account_ids=general_model_account_ids,
|
|
102
|
+
applied_service_tier=effective_service_tier,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _filter_accounts_for_model(
|
|
107
|
+
accounts: list[Account],
|
|
108
|
+
model: str,
|
|
109
|
+
*,
|
|
110
|
+
registry: ModelRegistry,
|
|
111
|
+
service_tier: str | None = None,
|
|
112
|
+
) -> list[Account]:
|
|
113
|
+
return _filter_accounts_for_model_with_catalog_evidence(
|
|
114
|
+
accounts,
|
|
115
|
+
model,
|
|
116
|
+
registry=registry,
|
|
117
|
+
service_tier=service_tier,
|
|
118
|
+
).accounts
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _gated_limit_name_for_model(model: str | None) -> str | None:
|
|
122
|
+
return get_additional_quota_key_for_model_id(model)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _normalize_model_id(model: str) -> str:
|
|
126
|
+
return model.strip().lower()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _effective_model_service_tier(service_tier: str | None) -> str | None:
|
|
130
|
+
if service_tier is None:
|
|
131
|
+
return None
|
|
132
|
+
normalized_service_tier = canonical_service_tier_value(service_tier)
|
|
133
|
+
return None if normalized_service_tier in {"", "auto", "default"} else normalized_service_tier
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _catalog_omission_quota_admission(
|
|
137
|
+
*,
|
|
138
|
+
account_id: str,
|
|
139
|
+
model: str | None,
|
|
140
|
+
service_tier: str | None,
|
|
141
|
+
additional_limit_name: str | None,
|
|
142
|
+
quota_admitted_catalog_omission_account_ids: frozenset[str],
|
|
143
|
+
) -> CatalogOmissionQuotaAdmission | None:
|
|
144
|
+
if (
|
|
145
|
+
model is None
|
|
146
|
+
or additional_limit_name is not None
|
|
147
|
+
or account_id not in quota_admitted_catalog_omission_account_ids
|
|
148
|
+
):
|
|
149
|
+
return None
|
|
150
|
+
quota_key = _gated_limit_name_for_model(model)
|
|
151
|
+
if quota_key is None:
|
|
152
|
+
return None
|
|
153
|
+
return CatalogOmissionQuotaAdmission(
|
|
154
|
+
normalized_model=_normalize_model_id(model),
|
|
155
|
+
canonical_quota_key=quota_key,
|
|
156
|
+
normalized_effective_service_tier=_effective_model_service_tier(service_tier),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _mapped_model_has_registry_entry(model: str | None, *, registry: ModelRegistry) -> bool:
|
|
161
|
+
if model is None:
|
|
162
|
+
return False
|
|
163
|
+
plan_types_for_model = getattr(registry, "plan_types_for_model", None)
|
|
164
|
+
if not callable(plan_types_for_model):
|
|
165
|
+
return False
|
|
166
|
+
if plan_types_for_model(model):
|
|
167
|
+
return True
|
|
168
|
+
is_suppressed_model = getattr(registry, "is_suppressed_model", None)
|
|
169
|
+
return callable(is_suppressed_model) and is_suppressed_model(model)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def _latest_additional_by_key(
|
|
173
|
+
additional_usage_repo,
|
|
174
|
+
quota_key: str,
|
|
175
|
+
window: str,
|
|
176
|
+
*,
|
|
177
|
+
account_ids: list[str] | None = None,
|
|
178
|
+
since: datetime | None = None,
|
|
179
|
+
) -> dict[str, AdditionalUsageHistory]:
|
|
180
|
+
resolved_quota_key = canonicalize_additional_quota_key(
|
|
181
|
+
quota_key=quota_key,
|
|
182
|
+
limit_name=quota_key,
|
|
183
|
+
)
|
|
184
|
+
if resolved_quota_key is None:
|
|
185
|
+
return {}
|
|
186
|
+
return await additional_usage_repo.latest_by_quota_key(
|
|
187
|
+
resolved_quota_key,
|
|
188
|
+
window,
|
|
189
|
+
account_ids=account_ids,
|
|
190
|
+
since=since,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _additional_quota_eligibility(
|
|
195
|
+
*,
|
|
196
|
+
account_id: str,
|
|
197
|
+
account_plan_type: str | None,
|
|
198
|
+
quota_key: str | None,
|
|
199
|
+
explicit_limit: bool = False,
|
|
200
|
+
require_fresh_evidence: bool = False,
|
|
201
|
+
latest_primary: dict[str, AdditionalUsageHistory],
|
|
202
|
+
latest_secondary: dict[str, AdditionalUsageHistory],
|
|
203
|
+
fresh_primary: dict[str, AdditionalUsageHistory],
|
|
204
|
+
fresh_secondary: dict[str, AdditionalUsageHistory],
|
|
205
|
+
now: float,
|
|
206
|
+
) -> str:
|
|
207
|
+
latest_primary_entry = latest_primary.get(account_id)
|
|
208
|
+
latest_secondary_entry = latest_secondary.get(account_id)
|
|
209
|
+
primary_entry = fresh_primary.get(account_id)
|
|
210
|
+
secondary_entry = fresh_secondary.get(account_id)
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
not require_fresh_evidence
|
|
214
|
+
and not explicit_limit
|
|
215
|
+
and not _additional_quota_applies_to_plan(quota_key=quota_key, plan_type=account_plan_type)
|
|
216
|
+
):
|
|
217
|
+
return "eligible"
|
|
218
|
+
|
|
219
|
+
if latest_primary_entry is None and latest_secondary_entry is None:
|
|
220
|
+
return "data_unavailable"
|
|
221
|
+
if latest_primary_entry is not None and primary_entry is None:
|
|
222
|
+
return "data_unavailable"
|
|
223
|
+
if latest_secondary_entry is not None and secondary_entry is None:
|
|
224
|
+
return "data_unavailable"
|
|
225
|
+
|
|
226
|
+
if primary_entry is not None and _additional_usage_is_exhausted(primary_entry, now=now):
|
|
227
|
+
return "quota_exhausted"
|
|
228
|
+
if secondary_entry is not None and _additional_usage_is_exhausted(secondary_entry, now=now):
|
|
229
|
+
return "quota_exhausted"
|
|
230
|
+
return "eligible"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _additional_quota_applies_to_plan(*, quota_key: str | None, plan_type: str | None) -> bool:
|
|
234
|
+
definition = get_additional_quota_definition(quota_key)
|
|
235
|
+
if definition is None or definition.applies_to_plans is None:
|
|
236
|
+
return True
|
|
237
|
+
normalized_plan = normalize_account_plan_type(plan_type)
|
|
238
|
+
if normalized_plan is None:
|
|
239
|
+
return True
|
|
240
|
+
if normalized_plan in definition.applies_to_plans:
|
|
241
|
+
return True
|
|
242
|
+
return normalized_plan not in _ADDITIONAL_QUOTA_EXEMPT_PLAN_TYPES
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _additional_usage_is_exhausted(entry: AdditionalUsageHistory, *, now: float) -> bool:
|
|
246
|
+
if entry.used_percent is None:
|
|
247
|
+
return False
|
|
248
|
+
if entry.reset_at is not None and int(entry.reset_at) <= int(now):
|
|
249
|
+
return False
|
|
250
|
+
return float(entry.used_percent) >= 100.0
|