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,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TypeAlias
|
|
5
|
+
|
|
6
|
+
from app.core.openai.requests import ResponsesCompactRequest, ResponsesRequest
|
|
7
|
+
from app.core.types import JsonObject, JsonValue
|
|
8
|
+
from app.core.utils.json_guards import is_json_list, is_json_mapping
|
|
9
|
+
from app.modules.api_keys.service import (
|
|
10
|
+
API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET,
|
|
11
|
+
ApiKeyRequestUsageBudget,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
ApiKeyUsageEstimableRequest: TypeAlias = ResponsesRequest | ResponsesCompactRequest
|
|
15
|
+
|
|
16
|
+
# Same serialization parameters the budget historically used with ``json.dumps``.
|
|
17
|
+
# ``sort_keys`` and ``default`` cannot change the serialized length, but they
|
|
18
|
+
# are kept so the streamed byte count stays exactly what the one-shot dump
|
|
19
|
+
# produced.
|
|
20
|
+
_BUDGET_ENCODER = json.JSONEncoder(ensure_ascii=False, separators=(",", ":"), sort_keys=True, default=str)
|
|
21
|
+
|
|
22
|
+
_OPAQUE_INPUT_ITEM_TYPES = frozenset({"input_file", "input_image"})
|
|
23
|
+
_INPUT_BUDGET_EXCLUDED_FIELDS = frozenset(
|
|
24
|
+
{
|
|
25
|
+
"model",
|
|
26
|
+
"service_tier",
|
|
27
|
+
"stream",
|
|
28
|
+
"store",
|
|
29
|
+
"max_output_tokens",
|
|
30
|
+
"max_completion_tokens",
|
|
31
|
+
"max_tokens",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def estimate_api_key_request_usage(
|
|
37
|
+
payload: ApiKeyUsageEstimableRequest,
|
|
38
|
+
*,
|
|
39
|
+
upstream_payload: JsonObject | None = None,
|
|
40
|
+
) -> ApiKeyRequestUsageBudget:
|
|
41
|
+
"""Return a bounded local usage budget for API-key reservation admission.
|
|
42
|
+
|
|
43
|
+
``None`` means the proxy cannot size that side of the request locally, so
|
|
44
|
+
API-key enforcement should use its conservative default for that dimension.
|
|
45
|
+
|
|
46
|
+
``upstream_payload`` lets callers that already hold ``payload.to_payload()``
|
|
47
|
+
share it instead of paying for another full dump. It must be the pristine
|
|
48
|
+
``to_payload()`` result for ``payload`` (``to_payload`` is deterministic, so
|
|
49
|
+
the budget is identical either way).
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
if upstream_payload is None:
|
|
53
|
+
upstream_payload = payload.to_payload()
|
|
54
|
+
return ApiKeyRequestUsageBudget(
|
|
55
|
+
input_tokens=_estimate_request_input_tokens(payload, upstream_payload),
|
|
56
|
+
output_tokens=None,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _estimate_request_input_tokens(payload: ApiKeyUsageEstimableRequest, upstream_payload: JsonObject) -> int | None:
|
|
61
|
+
if _has_opaque_upstream_context(upstream_payload):
|
|
62
|
+
return None
|
|
63
|
+
if _contains_opaque_input_reference(payload.input):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
data = _input_budget_payload(upstream_payload)
|
|
67
|
+
# The budget is ``min(serialized_utf8_length, CAP)``. A JSON string literal
|
|
68
|
+
# is never shorter than its Python length (escapes only add bytes), so a
|
|
69
|
+
# long ``instructions`` field alone already proves the cap is reached; this
|
|
70
|
+
# is the common Codex CLI shape and avoids serializing multi-hundred-KB
|
|
71
|
+
# payloads to learn an 8 KiB answer.
|
|
72
|
+
instructions = data.get("instructions")
|
|
73
|
+
if isinstance(instructions, str) and len(instructions) >= API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET:
|
|
74
|
+
return API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET
|
|
75
|
+
# Otherwise stream the serialization and stop as soon as the cap is proven.
|
|
76
|
+
# Only the first ~CAP bytes are ever produced, so a lone surrogate deeper in
|
|
77
|
+
# the payload no longer raises ``UnicodeEncodeError`` here (the full dump
|
|
78
|
+
# used to fail for a surrogate anywhere in the payload).
|
|
79
|
+
serialized_bytes = 0
|
|
80
|
+
for chunk in _BUDGET_ENCODER.iterencode(data):
|
|
81
|
+
# A chunk's UTF-8 length is at least its character count, so a chunk
|
|
82
|
+
# that alone covers the remaining budget (one large string literal)
|
|
83
|
+
# proves the cap without encoding it.
|
|
84
|
+
if len(chunk) >= API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET - serialized_bytes:
|
|
85
|
+
return API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET
|
|
86
|
+
serialized_bytes += len(chunk.encode("utf-8"))
|
|
87
|
+
if serialized_bytes >= API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET:
|
|
88
|
+
return API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET
|
|
89
|
+
return serialized_bytes
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _input_budget_payload(payload: JsonObject) -> dict[str, JsonValue]:
|
|
93
|
+
data = dict(payload.items())
|
|
94
|
+
for field in _INPUT_BUDGET_EXCLUDED_FIELDS:
|
|
95
|
+
data.pop(field, None)
|
|
96
|
+
return data
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _has_opaque_upstream_context(payload: JsonObject) -> bool:
|
|
100
|
+
return payload.get("previous_response_id") is not None or payload.get("conversation") is not None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _contains_opaque_input_reference(value: JsonValue) -> bool:
|
|
104
|
+
if is_json_mapping(value):
|
|
105
|
+
item_type = value.get("type")
|
|
106
|
+
if isinstance(item_type, str) and item_type in _OPAQUE_INPUT_ITEM_TYPES:
|
|
107
|
+
return True
|
|
108
|
+
if "file_id" in value:
|
|
109
|
+
return True
|
|
110
|
+
return any(_contains_opaque_input_reference(child) for child in value.values())
|
|
111
|
+
if is_json_list(value):
|
|
112
|
+
return any(_contains_opaque_input_reference(item) for item in value)
|
|
113
|
+
return False
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Cluster-wide account concurrency cap partitioning.
|
|
2
|
+
|
|
3
|
+
Configured per-account concurrency caps are cluster-wide targets. Each replica
|
|
4
|
+
derives its own deterministic share locally from the same sorted active
|
|
5
|
+
bridge-ring member list, so there is no cross-replica mutable shared state and
|
|
6
|
+
no per-request database I/O: every replica computes `floor(cap / R)` plus one
|
|
7
|
+
extra slot when its rank falls below `cap mod R`.
|
|
8
|
+
|
|
9
|
+
Membership changes are adopted with hysteresis keyed on whether this
|
|
10
|
+
replica's share could actually grow. A rank's share is monotone non-increasing
|
|
11
|
+
in both the replica count and the rank, but neither direction alone decides
|
|
12
|
+
growth: a count decrease can be outweighed by a rank increase (more members
|
|
13
|
+
sort ahead and consume the remainder), and a rank decrease can be outweighed by
|
|
14
|
+
a large enough count increase. Rather than reason about count/rank directions,
|
|
15
|
+
the decision compares the *prospective* share against the *current* share for
|
|
16
|
+
each configured cap directly — using the same effective caps (dashboard
|
|
17
|
+
overrides over startup defaults) and the same ``partition_cap`` share formula
|
|
18
|
+
the admission path enforces — and defers only when some configured cap's share
|
|
19
|
+
would strictly grow. Any change whose every configured-cap share holds or
|
|
20
|
+
shrinks is safe toward upstream and adopted on the next refresh; a
|
|
21
|
+
share-growing change is adopted only after that exact pending partition (count
|
|
22
|
+
and rank) has been observed continuously for a configured stability window — a
|
|
23
|
+
different pending partition restarts the window, and a failed membership read
|
|
24
|
+
also restarts it since an observation gap must not count as continuous — so
|
|
25
|
+
neither a missed heartbeat, a rolling replacement, nor a read outage can
|
|
26
|
+
transiently inflate a survivor's share.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import logging
|
|
32
|
+
import time
|
|
33
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
|
|
36
|
+
from app.core.config.settings import get_settings
|
|
37
|
+
from app.core.metrics.prometheus import PROMETHEUS_AVAILABLE, cap_partition_replicas
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
DEFAULT_SCALE_DOWN_SECONDS = 60.0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def partition_cap(cap: int, replica_count: int, rank: int) -> int:
|
|
45
|
+
"""Return this replica's share of a cluster-wide account cap.
|
|
46
|
+
|
|
47
|
+
``cap <= 0`` stays unlimited on every replica, a single replica keeps the
|
|
48
|
+
full cap, and every share is floored at one slot so an account never
|
|
49
|
+
becomes unroutable on a replica (when ``cap < replica_count`` the
|
|
50
|
+
aggregate may therefore reach ``replica_count``).
|
|
51
|
+
"""
|
|
52
|
+
if cap <= 0:
|
|
53
|
+
return 0
|
|
54
|
+
if replica_count <= 1:
|
|
55
|
+
return cap
|
|
56
|
+
base, remainder = divmod(cap, replica_count)
|
|
57
|
+
return max(1, base + (1 if rank < remainder else 0))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
61
|
+
class CapPartition:
|
|
62
|
+
"""Adopted partitioning inputs: live replica count and this replica's rank."""
|
|
63
|
+
|
|
64
|
+
replica_count: int = 1
|
|
65
|
+
rank: int = 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CapPartitionHolder:
|
|
69
|
+
"""Tracks the adopted partition with share-growth-aware hysteresis."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, *, clock: Callable[[], float] = time.monotonic) -> None:
|
|
72
|
+
self._clock = clock
|
|
73
|
+
self._adopted = CapPartition()
|
|
74
|
+
self._pending: CapPartition | None = None
|
|
75
|
+
self._pending_since: float | None = None
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def current(self) -> CapPartition:
|
|
79
|
+
return self._adopted
|
|
80
|
+
|
|
81
|
+
def observe_members(
|
|
82
|
+
self,
|
|
83
|
+
active_instance_ids: Sequence[str],
|
|
84
|
+
self_instance_id: str,
|
|
85
|
+
*,
|
|
86
|
+
configured_caps: Sequence[int],
|
|
87
|
+
scale_down_seconds: float,
|
|
88
|
+
) -> bool:
|
|
89
|
+
"""Feed a fresh active-member list; return True when the adopted partition changed.
|
|
90
|
+
|
|
91
|
+
``configured_caps`` are the cluster-wide per-account caps currently in
|
|
92
|
+
effect (response-create and stream limits); the share-growth decision is
|
|
93
|
+
made against those caps only, never against an arbitrary cap.
|
|
94
|
+
|
|
95
|
+
The observing replica is always counted even when its own ring row is
|
|
96
|
+
missing or stale, so startup and self-heartbeat gaps degrade to fewer
|
|
97
|
+
shared slots rather than a crash or an empty ring.
|
|
98
|
+
"""
|
|
99
|
+
members = sorted(set(active_instance_ids) | {self_instance_id})
|
|
100
|
+
observed = CapPartition(replica_count=len(members), rank=members.index(self_instance_id))
|
|
101
|
+
if observed == self._adopted:
|
|
102
|
+
self._clear_pending()
|
|
103
|
+
return False
|
|
104
|
+
if not self._could_grow_share(observed, configured_caps):
|
|
105
|
+
# Every cap share shrinks or stays put, which is safe toward
|
|
106
|
+
# upstream — adopt now.
|
|
107
|
+
self._adopted = observed
|
|
108
|
+
self._clear_pending()
|
|
109
|
+
return True
|
|
110
|
+
now = self._clock()
|
|
111
|
+
if observed != self._pending:
|
|
112
|
+
# A new or changed share-growing target restarts the stability
|
|
113
|
+
# window: only a partition held continuously may be adopted.
|
|
114
|
+
self._pending = observed
|
|
115
|
+
self._pending_since = now
|
|
116
|
+
return False
|
|
117
|
+
if self._pending_since is not None and now - self._pending_since >= scale_down_seconds:
|
|
118
|
+
self._adopted = observed
|
|
119
|
+
self._clear_pending()
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def _could_grow_share(self, observed: CapPartition, configured_caps: Sequence[int]) -> bool:
|
|
124
|
+
"""Whether adopting ``observed`` grows this replica's share of any configured cap.
|
|
125
|
+
|
|
126
|
+
The share is monotone non-increasing in both the replica count and the
|
|
127
|
+
rank, but neither direction alone decides growth: a count decrease can
|
|
128
|
+
be outweighed by a rank increase, and a rank decrease by a large enough
|
|
129
|
+
count increase. Reasoning about count/rank directions therefore either
|
|
130
|
+
over-defers genuine shrinks (a count decrease that a rank increase turns
|
|
131
|
+
into a smaller configured share) or under-defers genuine growth, so the
|
|
132
|
+
decision compares the prospective share against the current share for
|
|
133
|
+
each configured cap using the same ``partition_cap`` formula the
|
|
134
|
+
admission path enforces. Adopting can only be unsafe toward the
|
|
135
|
+
cluster-wide cap when some configured cap's share strictly grows; every
|
|
136
|
+
other change (each configured cap's share holds or shrinks) is safe and
|
|
137
|
+
adopted immediately.
|
|
138
|
+
"""
|
|
139
|
+
adopted = self._adopted
|
|
140
|
+
return any(
|
|
141
|
+
partition_cap(cap, observed.replica_count, observed.rank)
|
|
142
|
+
> partition_cap(cap, adopted.replica_count, adopted.rank)
|
|
143
|
+
for cap in configured_caps
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def note_failed_read(self) -> None:
|
|
147
|
+
"""Restart the stability window after a failed membership read.
|
|
148
|
+
|
|
149
|
+
A share-growing partition is adopted only after being observed
|
|
150
|
+
*continuously* for the stability window. A failed read is a gap in
|
|
151
|
+
observations, so any pending share-increase is dropped and its window
|
|
152
|
+
must restart from the next successful read; otherwise a read outage
|
|
153
|
+
spanning the window would let the first post-outage observation adopt a
|
|
154
|
+
larger survivor share without continuous confirmation, weakening the
|
|
155
|
+
missed-heartbeat guard. The already-adopted partition is left intact.
|
|
156
|
+
"""
|
|
157
|
+
self._clear_pending()
|
|
158
|
+
|
|
159
|
+
def _clear_pending(self) -> None:
|
|
160
|
+
self._pending = None
|
|
161
|
+
self._pending_since = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
_holder = CapPartitionHolder()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_cap_partition() -> CapPartition:
|
|
168
|
+
"""Return the partition currently used for account cap enforcement."""
|
|
169
|
+
return _holder.current
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def configured_account_concurrency_caps(
|
|
173
|
+
dashboard_settings: object | None,
|
|
174
|
+
*,
|
|
175
|
+
startup_settings: object | None = None,
|
|
176
|
+
) -> tuple[int, int]:
|
|
177
|
+
"""Return the cluster-wide per-account caps currently in effect.
|
|
178
|
+
|
|
179
|
+
Dashboard-configured overrides (when present on ``dashboard_settings``) win
|
|
180
|
+
over the startup defaults, exactly as ``effective_account_concurrency_caps``
|
|
181
|
+
derives them before partitioning. The share-growth hysteresis must gate on
|
|
182
|
+
these *same* effective caps rather than the startup defaults: when a
|
|
183
|
+
dashboard cap is higher (or lower) than the startup value, a membership
|
|
184
|
+
change that does not grow the startup-default share can still grow the
|
|
185
|
+
effective share (or vice versa), so gating on the wrong caps would adopt or
|
|
186
|
+
defer incorrectly. ``startup_settings`` supplies the startup-default source
|
|
187
|
+
when a caller already holds it (so both the admission and hysteresis paths
|
|
188
|
+
read the same settings object); it defaults to ``get_settings()``. Returns
|
|
189
|
+
``(response_create_limit, stream_limit)``.
|
|
190
|
+
"""
|
|
191
|
+
if startup_settings is None:
|
|
192
|
+
startup_settings = get_settings()
|
|
193
|
+
response_override = getattr(dashboard_settings, "proxy_account_response_create_limit", None)
|
|
194
|
+
stream_override = getattr(dashboard_settings, "proxy_account_stream_limit", None)
|
|
195
|
+
startup_response = getattr(startup_settings, "proxy_account_response_create_limit", 4)
|
|
196
|
+
startup_stream = getattr(startup_settings, "proxy_account_stream_limit", 8)
|
|
197
|
+
response_cap = max(0, int(startup_response if response_override is None else response_override))
|
|
198
|
+
stream_cap = max(0, int(startup_stream if stream_override is None else stream_override))
|
|
199
|
+
return (response_cap, stream_cap)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def _current_dashboard_settings() -> object | None:
|
|
203
|
+
"""Best-effort fetch of the dashboard-configured caps for the hysteresis gate.
|
|
204
|
+
|
|
205
|
+
Reuses the process settings cache so the common case adds no per-refresh
|
|
206
|
+
database round trip, and falls back to the startup defaults (``None``) when
|
|
207
|
+
the read fails so a settings outage never blocks a membership refresh.
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
from app.core.config.settings_cache import get_settings_cache
|
|
211
|
+
|
|
212
|
+
return await get_settings_cache().get()
|
|
213
|
+
except Exception:
|
|
214
|
+
logger.debug("Cap partition dashboard-settings lookup failed; using startup caps", exc_info=True)
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def observe_ring_members(
|
|
219
|
+
active_instance_ids: Sequence[str],
|
|
220
|
+
self_instance_id: str,
|
|
221
|
+
*,
|
|
222
|
+
dashboard_settings: object | None = None,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Refresh the process-wide partition from an active bridge-ring member list.
|
|
225
|
+
|
|
226
|
+
``dashboard_settings`` carries the dashboard-configured per-account cap
|
|
227
|
+
overrides so the hysteresis gates on the same effective caps the admission
|
|
228
|
+
path partitions; when ``None`` the startup defaults apply.
|
|
229
|
+
"""
|
|
230
|
+
settings = get_settings()
|
|
231
|
+
scale_down_seconds = float(
|
|
232
|
+
getattr(settings, "proxy_account_cap_partition_scale_down_seconds", DEFAULT_SCALE_DOWN_SECONDS)
|
|
233
|
+
)
|
|
234
|
+
previous = _holder.current
|
|
235
|
+
changed = _holder.observe_members(
|
|
236
|
+
active_instance_ids,
|
|
237
|
+
self_instance_id,
|
|
238
|
+
configured_caps=configured_account_concurrency_caps(dashboard_settings, startup_settings=settings),
|
|
239
|
+
scale_down_seconds=scale_down_seconds,
|
|
240
|
+
)
|
|
241
|
+
current = _holder.current
|
|
242
|
+
_record_cap_partition_replicas(current.replica_count)
|
|
243
|
+
if changed:
|
|
244
|
+
logger.info(
|
|
245
|
+
"Account cap partition rebalanced old_count=%s new_count=%s rank=%s",
|
|
246
|
+
previous.replica_count,
|
|
247
|
+
current.replica_count,
|
|
248
|
+
current.rank,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
async def refresh_cap_partition(
|
|
253
|
+
list_active_members: Callable[[], Awaitable[Sequence[str]]],
|
|
254
|
+
self_instance_id: str,
|
|
255
|
+
) -> None:
|
|
256
|
+
"""Refresh the partition from active bridge-ring membership.
|
|
257
|
+
|
|
258
|
+
A failed membership read retains the last-known adopted partition instead
|
|
259
|
+
of falling open to the full configured caps, but restarts any pending
|
|
260
|
+
share-increase window so the observation gap does not count toward the
|
|
261
|
+
continuous stability window.
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
members = await list_active_members()
|
|
265
|
+
except Exception:
|
|
266
|
+
logger.warning("Cap partition refresh failed; retaining last-known partition", exc_info=True)
|
|
267
|
+
_holder.note_failed_read()
|
|
268
|
+
return
|
|
269
|
+
dashboard_settings = await _current_dashboard_settings()
|
|
270
|
+
observe_ring_members(members, self_instance_id, dashboard_settings=dashboard_settings)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def reset_cap_partition_for_tests() -> None:
|
|
274
|
+
global _holder
|
|
275
|
+
_holder = CapPartitionHolder()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _record_cap_partition_replicas(count: int) -> None:
|
|
279
|
+
if PROMETHEUS_AVAILABLE and cap_partition_replicas is not None:
|
|
280
|
+
cap_partition_replicas.set(count)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
|
|
7
|
+
_MARKER_DOMAIN = "capability-lineage/v1"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class CapabilityLineageAlias:
|
|
12
|
+
kind: str
|
|
13
|
+
value: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def normalize_capability_lineage_aliases(
|
|
17
|
+
aliases: Collection[CapabilityLineageAlias],
|
|
18
|
+
) -> tuple[CapabilityLineageAlias, ...]:
|
|
19
|
+
normalized: dict[tuple[str, str], CapabilityLineageAlias] = {}
|
|
20
|
+
for alias in aliases:
|
|
21
|
+
kind = alias.kind.strip()
|
|
22
|
+
value = alias.value.strip()
|
|
23
|
+
if kind and value:
|
|
24
|
+
normalized[(kind, value)] = CapabilityLineageAlias(kind=kind, value=value)
|
|
25
|
+
return tuple(normalized.values())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def capability_lineage_marker_hash(
|
|
29
|
+
*,
|
|
30
|
+
capability: str,
|
|
31
|
+
api_key_scope: str,
|
|
32
|
+
alias: CapabilityLineageAlias,
|
|
33
|
+
) -> str:
|
|
34
|
+
normalized_capability = capability.strip()
|
|
35
|
+
normalized_scope = api_key_scope.strip()
|
|
36
|
+
normalized_aliases = normalize_capability_lineage_aliases((alias,))
|
|
37
|
+
if not normalized_capability or not normalized_scope or not normalized_aliases:
|
|
38
|
+
raise ValueError("capability, API-key scope, and lineage alias must be non-empty")
|
|
39
|
+
normalized_alias = normalized_aliases[0]
|
|
40
|
+
payload = "\0".join(
|
|
41
|
+
(
|
|
42
|
+
_MARKER_DOMAIN,
|
|
43
|
+
normalized_capability,
|
|
44
|
+
normalized_scope,
|
|
45
|
+
normalized_alias.kind,
|
|
46
|
+
normalized_alias.value,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
return sha256(payload.encode("utf-8")).hexdigest()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def capability_lineage_marker_hashes(
|
|
53
|
+
*,
|
|
54
|
+
capability: str,
|
|
55
|
+
api_key_scope: str,
|
|
56
|
+
aliases: Collection[CapabilityLineageAlias],
|
|
57
|
+
) -> tuple[str, ...]:
|
|
58
|
+
return tuple(
|
|
59
|
+
capability_lineage_marker_hash(
|
|
60
|
+
capability=capability,
|
|
61
|
+
api_key_scope=api_key_scope,
|
|
62
|
+
alias=alias,
|
|
63
|
+
)
|
|
64
|
+
for alias in normalize_capability_lineage_aliases(aliases)
|
|
65
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import func, select
|
|
6
|
+
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
|
7
|
+
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
from sqlalchemy.sql import Insert
|
|
10
|
+
|
|
11
|
+
from app.db.models import CapabilityLineageMarker
|
|
12
|
+
from app.db.session import sqlite_writer_section
|
|
13
|
+
from app.modules.proxy.capability_lineage import (
|
|
14
|
+
CapabilityLineageAlias,
|
|
15
|
+
capability_lineage_marker_hashes,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CapabilityLineageRepository:
|
|
20
|
+
def __init__(self, session: AsyncSession) -> None:
|
|
21
|
+
self._session = session
|
|
22
|
+
|
|
23
|
+
async def is_required(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
capability: str,
|
|
27
|
+
api_key_scope: str,
|
|
28
|
+
aliases: Collection[CapabilityLineageAlias],
|
|
29
|
+
) -> bool:
|
|
30
|
+
marker_hashes = capability_lineage_marker_hashes(
|
|
31
|
+
capability=capability,
|
|
32
|
+
api_key_scope=api_key_scope,
|
|
33
|
+
aliases=aliases,
|
|
34
|
+
)
|
|
35
|
+
if not marker_hashes:
|
|
36
|
+
return False
|
|
37
|
+
statement = (
|
|
38
|
+
select(CapabilityLineageMarker.marker_hash)
|
|
39
|
+
.where(CapabilityLineageMarker.marker_hash.in_(marker_hashes))
|
|
40
|
+
.limit(1)
|
|
41
|
+
)
|
|
42
|
+
required = (await self._session.execute(statement)).scalar_one_or_none() is not None
|
|
43
|
+
# End SQLite's read snapshot before a caller upgrades this dedicated
|
|
44
|
+
# repository session into a writer while another connection commits.
|
|
45
|
+
await self._session.commit()
|
|
46
|
+
return required
|
|
47
|
+
|
|
48
|
+
async def require(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
capability: str,
|
|
52
|
+
api_key_scope: str,
|
|
53
|
+
aliases: Collection[CapabilityLineageAlias],
|
|
54
|
+
) -> tuple[str, ...]:
|
|
55
|
+
marker_hashes = capability_lineage_marker_hashes(
|
|
56
|
+
capability=capability,
|
|
57
|
+
api_key_scope=api_key_scope,
|
|
58
|
+
aliases=aliases,
|
|
59
|
+
)
|
|
60
|
+
if not marker_hashes:
|
|
61
|
+
return ()
|
|
62
|
+
async with sqlite_writer_section():
|
|
63
|
+
# Overlapping alias sets must lock rows in one global order on
|
|
64
|
+
# PostgreSQL so concurrent reconnects cannot deadlock A->B/B->A.
|
|
65
|
+
for marker_hash in sorted(marker_hashes):
|
|
66
|
+
await self._session.execute(self._upsert_statement(marker_hash))
|
|
67
|
+
await self._session.commit()
|
|
68
|
+
return marker_hashes
|
|
69
|
+
|
|
70
|
+
def _upsert_statement(self, marker_hash: str) -> Insert:
|
|
71
|
+
dialect = self._session.get_bind().dialect.name
|
|
72
|
+
if dialect == "postgresql":
|
|
73
|
+
insert_fn = pg_insert
|
|
74
|
+
elif dialect == "sqlite":
|
|
75
|
+
insert_fn = sqlite_insert
|
|
76
|
+
else:
|
|
77
|
+
raise RuntimeError(f"Capability lineage persistence unsupported for dialect={dialect!r}")
|
|
78
|
+
statement = insert_fn(CapabilityLineageMarker).values(marker_hash=marker_hash)
|
|
79
|
+
return statement.on_conflict_do_update(
|
|
80
|
+
index_elements=[CapabilityLineageMarker.marker_hash],
|
|
81
|
+
set_={"last_seen_at": func.now()},
|
|
82
|
+
)
|