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,331 @@
|
|
|
1
|
+
"""Replay accepted, output-free capacity failures within one response lifecycle.
|
|
2
|
+
|
|
3
|
+
Upstream can accept a ``response.create`` -- ``response.created`` and usually
|
|
4
|
+
``response.in_progress`` reach the client -- and then fail the turn before it
|
|
5
|
+
produces any output, either with a capacity terminal (``server_is_overloaded``,
|
|
6
|
+
``overloaded_error``, ``model_at_capacity``, or the "selected model is at
|
|
7
|
+
capacity" message) or by dropping the transport. Every side effect of a
|
|
8
|
+
Responses turn is reported as an output item or as billed output tokens, so
|
|
9
|
+
such a turn is as replay-safe as a pre-created failure. The helpers here let
|
|
10
|
+
the existing pre-created replay machinery (``replay_downstream_response_id`` +
|
|
11
|
+
prelude suppression + id rewriting) cover that state: the request is re-sent
|
|
12
|
+
once -- on another account when its body is account-neutral, otherwise to the
|
|
13
|
+
account that accepted it (a client-supplied anchor or an account-bound fresh
|
|
14
|
+
body keeps the anchored body on its owner) -- while the client keeps reading
|
|
15
|
+
the single lifecycle it already observed. Quota and rate-limit terminals after
|
|
16
|
+
acceptance deliberately stay fail-closed; see the openspec change
|
|
17
|
+
``retry-accepted-output-free-capacity-failures``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import logging
|
|
24
|
+
from typing import Literal
|
|
25
|
+
|
|
26
|
+
from app.core.types import JsonValue
|
|
27
|
+
from app.modules.proxy._service.support import (
|
|
28
|
+
_REQUEST_TRANSPORT_WEBSOCKET,
|
|
29
|
+
_affinity_may_resolve_hard_owner,
|
|
30
|
+
_HTTPBridgeSession,
|
|
31
|
+
_websocket_request_is_accepted_lifecycle_only,
|
|
32
|
+
_WebSocketRequestState,
|
|
33
|
+
)
|
|
34
|
+
from app.modules.proxy.helpers import is_upstream_model_capacity_error
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("app.modules.proxy.service")
|
|
37
|
+
|
|
38
|
+
_ACCEPTED_CAPACITY_REPLAY_ERROR_CODES = frozenset({"server_is_overloaded", "overloaded_error", "model_at_capacity"})
|
|
39
|
+
# The code handed back is what the account-health write consumes and what the
|
|
40
|
+
# spec requires to be a transparent replay code
|
|
41
|
+
# (``_WEBSOCKET_TRANSPARENT_REPLAY_ERROR_CODES``). ``model_at_capacity`` is not
|
|
42
|
+
# one on either surface, so it is reported as ``server_is_overloaded`` exactly
|
|
43
|
+
# as the pre-created classifier reports the selected-model capacity message;
|
|
44
|
+
# the accepted replay itself no longer branches on the transparent set.
|
|
45
|
+
_ACCEPTED_CAPACITY_REPLAY_REPORTED_CODES = {
|
|
46
|
+
"server_is_overloaded": "server_is_overloaded",
|
|
47
|
+
"overloaded_error": "overloaded_error",
|
|
48
|
+
"model_at_capacity": "server_is_overloaded",
|
|
49
|
+
}
|
|
50
|
+
# Quota and rate-limit codes keep their stronger classification after
|
|
51
|
+
# acceptance even when the message names the selected-model capacity: an
|
|
52
|
+
# accepted turn that hit a quota wall may already be billed.
|
|
53
|
+
_ACCEPTED_REPLAY_FAIL_CLOSED_ERROR_CODES = frozenset(
|
|
54
|
+
{"rate_limit_exceeded", "usage_limit_reached", "insufficient_quota", "usage_not_included", "quota_exceeded"}
|
|
55
|
+
)
|
|
56
|
+
_TERMINAL_EVENT_TYPES = frozenset({"error", "response.failed"})
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _websocket_accepted_replay_candidate(
|
|
60
|
+
request_state: _WebSocketRequestState,
|
|
61
|
+
*,
|
|
62
|
+
has_other_pending_requests: bool,
|
|
63
|
+
) -> bool:
|
|
64
|
+
"""Return whether an accepted request may still be replayed once.
|
|
65
|
+
|
|
66
|
+
Mirrors the fresh-replay rules of the pre-created path: a single pending
|
|
67
|
+
request with its body retained, no replay consumed yet, a send boundary on
|
|
68
|
+
the direct websocket transport, no finite ``sequence_number`` forwarded
|
|
69
|
+
downstream, and either no anchor or a retry-safe fresh payload. The fresh
|
|
70
|
+
payload replaces the anchored body only when the owner-switch prep proves
|
|
71
|
+
the move safe (proxy-injected anchor, account-neutral body); otherwise the
|
|
72
|
+
anchored body is re-sent to the owner that accepted it, which the terminal
|
|
73
|
+
proved produced nothing.
|
|
74
|
+
|
|
75
|
+
The sequence guard is the direct websocket surface's existing contract
|
|
76
|
+
(openspec: "Direct WebSocket replay never mixes numeric response
|
|
77
|
+
sequences"): a fresh upstream generation restarts its counter, so once a
|
|
78
|
+
sequenced prelude frame reached the client the terminal is finalized and
|
|
79
|
+
surfaced unchanged instead of being replayed. The HTTP bridge never
|
|
80
|
+
records a downstream watermark, so its behaviour is unaffected.
|
|
81
|
+
"""
|
|
82
|
+
if has_other_pending_requests:
|
|
83
|
+
return False
|
|
84
|
+
if request_state.last_downstream_sequence_number is not None:
|
|
85
|
+
return False
|
|
86
|
+
if not _websocket_request_is_accepted_lifecycle_only(request_state):
|
|
87
|
+
return False
|
|
88
|
+
if not request_state.request_text or request_state.replay_count != 0:
|
|
89
|
+
return False
|
|
90
|
+
if request_state.transport == _REQUEST_TRANSPORT_WEBSOCKET and request_state.response_create_sent_at is None:
|
|
91
|
+
return False
|
|
92
|
+
if request_state.previous_response_id is not None and not (
|
|
93
|
+
request_state.fresh_upstream_request_is_retry_safe and request_state.fresh_upstream_request_text
|
|
94
|
+
):
|
|
95
|
+
return False
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _http_bridge_accepted_anchored_replay_candidate(request_state: _WebSocketRequestState) -> bool:
|
|
100
|
+
"""Return whether an accepted anchored bridge request may be staged for the pre-created retry.
|
|
101
|
+
|
|
102
|
+
The bridge's transparent-code branch used to stage only unanchored
|
|
103
|
+
requests, so an accepted follow-up whose anchor the proxy injected was
|
|
104
|
+
replayed after the selected-model capacity *message* (the wait branch) but
|
|
105
|
+
forwarded unchanged after a bare ``server_is_overloaded`` /
|
|
106
|
+
``overloaded_error`` code. ``_retry_http_bridge_precreated_request``
|
|
107
|
+
re-sends a proxy-injected anchor through the owner-switch prep (fresh body
|
|
108
|
+
on another account) or, when the fresh body is account-bound, the anchored
|
|
109
|
+
body to its owner, so those requests qualify. A client-supplied anchor is
|
|
110
|
+
replayed only by the transport-failure path (its proof-gated full resend
|
|
111
|
+
is transport-only, see ``request_is_retryable``), so its capacity terminal
|
|
112
|
+
keeps being forwarded rather than staged into a retry that would refuse
|
|
113
|
+
it and rewrite the terminal.
|
|
114
|
+
"""
|
|
115
|
+
return bool(
|
|
116
|
+
request_state.response_id is not None
|
|
117
|
+
and not request_state.awaiting_response_created
|
|
118
|
+
and request_state.previous_response_id is not None
|
|
119
|
+
and request_state.proxy_injected_previous_response_id
|
|
120
|
+
and request_state.fresh_upstream_request_is_retry_safe
|
|
121
|
+
and request_state.fresh_upstream_request_text
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _http_bridge_accepted_capacity_retry_allowed(request_state: _WebSocketRequestState) -> bool:
|
|
126
|
+
"""Return whether the bridge capacity branches may reserve and stage this request.
|
|
127
|
+
|
|
128
|
+
Pre-created requests keep their existing rules. An accepted request
|
|
129
|
+
qualifies unanchored, or anchored only as
|
|
130
|
+
``_http_bridge_accepted_anchored_replay_candidate`` allows (proxy-injected
|
|
131
|
+
anchor with a retry-safe fresh body). The selected-model capacity *message*
|
|
132
|
+
used to reach the wait branch for every accepted request that retained a
|
|
133
|
+
retry-safe fresh body, including one whose anchor the client supplied: the
|
|
134
|
+
request was reserved, staged and penalized, then the pre-created retry
|
|
135
|
+
refused its transport-only full resend and the raw capacity terminal was
|
|
136
|
+
rewritten into a synthetic ``stream_incomplete`` (HTTP 502). Excluding the
|
|
137
|
+
client-supplied anchor before the wait keeps that terminal forwarded
|
|
138
|
+
unchanged, exactly as the bare transparent-code branch already does.
|
|
139
|
+
"""
|
|
140
|
+
accepted = request_state.response_id is not None and not request_state.awaiting_response_created
|
|
141
|
+
if not accepted or request_state.previous_response_id is None:
|
|
142
|
+
return True
|
|
143
|
+
return _http_bridge_accepted_anchored_replay_candidate(request_state)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _http_bridge_accepted_replay_may_exclude_account(
|
|
147
|
+
request_state: _WebSocketRequestState,
|
|
148
|
+
session: _HTTPBridgeSession,
|
|
149
|
+
) -> bool:
|
|
150
|
+
"""Return whether the bridge's fresh-request replay may exclude the account that accepted it.
|
|
151
|
+
|
|
152
|
+
``_retry_http_bridge_precreated_request`` moves an account-neutral fresh
|
|
153
|
+
request off the failing account by excluding it from the reconnect
|
|
154
|
+
selection. The accepted-lifecycle replay of #2127 inherited that
|
|
155
|
+
exclusion on hard session keys (``session_header`` / ``thread_header`` /
|
|
156
|
+
``turn_state_header``, every native Codex bridge session) -- yet the
|
|
157
|
+
reconnect selects with the session's affinity, and when that affinity
|
|
158
|
+
resolves a hard ``CODEX_SESSION`` owner (a turn-state row, or the raw
|
|
159
|
+
compatibility row an old replica persisted for the bare session header)
|
|
160
|
+
selection is narrowed to that owner. Excluding it makes every
|
|
161
|
+
re-selection fail with ``hard_affinity_saturated``, which the reconnect
|
|
162
|
+
loop treats as a transient owner outage and sleeps on until the bridge
|
|
163
|
+
request budget (7200s by default) is spent, long after the client gave up.
|
|
164
|
+
|
|
165
|
+
Mirror of ``_websocket_accepted_replay_may_exclude_account``: an accepted
|
|
166
|
+
replay (``replay_downstream_response_id`` captured) on a hard session key
|
|
167
|
+
whose affinity may resolve a hard owner reconnects without an exclusion,
|
|
168
|
+
so the same owner is re-selected and the request is re-sent to it within
|
|
169
|
+
the single lifecycle the client is reading. Everything else keeps the
|
|
170
|
+
established exclusion: the created-only replay (unchanged from before
|
|
171
|
+
accepted replays existed), soft session keys (never derived alongside a
|
|
172
|
+
hard-capable affinity -- turn-state, thread and bare session headers all
|
|
173
|
+
produce hard keys), and hard keys whose affinity cannot resolve an owner.
|
|
174
|
+
"""
|
|
175
|
+
if request_state.replay_downstream_response_id is None:
|
|
176
|
+
return True
|
|
177
|
+
if session.key.strength != "hard":
|
|
178
|
+
return True
|
|
179
|
+
return not _affinity_may_resolve_hard_owner(session.affinity)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _positive_token_count(value: JsonValue | None) -> bool:
|
|
183
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _terminal_payload_reports_output(payload: dict[str, JsonValue] | None) -> bool:
|
|
187
|
+
"""Return whether a terminal payload proves the model already ran.
|
|
188
|
+
|
|
189
|
+
A non-empty ``output`` list or billed output/reasoning tokens (on the event
|
|
190
|
+
or nested under ``response``) mean the turn produced something the client
|
|
191
|
+
may be charged for; such a terminal is never replayed.
|
|
192
|
+
"""
|
|
193
|
+
if not isinstance(payload, dict):
|
|
194
|
+
return False
|
|
195
|
+
response = payload.get("response")
|
|
196
|
+
scopes: list[dict[str, JsonValue]] = [payload]
|
|
197
|
+
if isinstance(response, dict):
|
|
198
|
+
scopes.append(response)
|
|
199
|
+
for scope in scopes:
|
|
200
|
+
output = scope.get("output")
|
|
201
|
+
if output is not None and (not isinstance(output, list) or output):
|
|
202
|
+
return True
|
|
203
|
+
usage = scope.get("usage")
|
|
204
|
+
if not isinstance(usage, dict):
|
|
205
|
+
continue
|
|
206
|
+
if _positive_token_count(usage.get("output_tokens")):
|
|
207
|
+
return True
|
|
208
|
+
output_token_details = usage.get("output_tokens_details")
|
|
209
|
+
if isinstance(output_token_details, dict) and _positive_token_count(
|
|
210
|
+
output_token_details.get("reasoning_tokens")
|
|
211
|
+
):
|
|
212
|
+
return True
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _websocket_accepted_capacity_retry_error_code(
|
|
217
|
+
request_state: _WebSocketRequestState,
|
|
218
|
+
*,
|
|
219
|
+
event_type: str | None,
|
|
220
|
+
error_code: str | None,
|
|
221
|
+
error_message: str | None,
|
|
222
|
+
payload_response_id: str | None,
|
|
223
|
+
payload: dict[str, JsonValue] | None,
|
|
224
|
+
has_other_pending_requests: bool,
|
|
225
|
+
) -> str | None:
|
|
226
|
+
"""Classify an output-free capacity terminal of an accepted request.
|
|
227
|
+
|
|
228
|
+
Returns the replay error code, or ``None`` when the terminal must surface
|
|
229
|
+
to the client unchanged. Only capacity codes and the selected-model
|
|
230
|
+
capacity message qualify; quota and rate-limit codes are refused before
|
|
231
|
+
the message-only fallback so they can never be reclassified as overload.
|
|
232
|
+
The returned code is always a transparent replay code
|
|
233
|
+
(``model_at_capacity`` is reported as ``server_is_overloaded``).
|
|
234
|
+
"""
|
|
235
|
+
if event_type not in _TERMINAL_EVENT_TYPES:
|
|
236
|
+
return None
|
|
237
|
+
if not _websocket_accepted_replay_candidate(request_state, has_other_pending_requests=has_other_pending_requests):
|
|
238
|
+
return None
|
|
239
|
+
if payload_response_id is not None and payload_response_id != request_state.response_id:
|
|
240
|
+
return None
|
|
241
|
+
if _terminal_payload_reports_output(payload):
|
|
242
|
+
return None
|
|
243
|
+
if error_code in _ACCEPTED_REPLAY_FAIL_CLOSED_ERROR_CODES:
|
|
244
|
+
return None
|
|
245
|
+
if is_upstream_model_capacity_error(error_message) or error_code in _ACCEPTED_CAPACITY_REPLAY_ERROR_CODES:
|
|
246
|
+
return _ACCEPTED_CAPACITY_REPLAY_REPORTED_CODES.get(error_code or "", "server_is_overloaded")
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
async def _claim_websocket_replay_create_gate(
|
|
251
|
+
request_state: _WebSocketRequestState,
|
|
252
|
+
gate: asyncio.Semaphore,
|
|
253
|
+
) -> bool:
|
|
254
|
+
"""Re-claim the session response-create gate for a replay without waiting.
|
|
255
|
+
|
|
256
|
+
``awaiting_response_created=True`` must imply that the request holds the
|
|
257
|
+
gate: the capacity wait sleeps the sole upstream reader and a younger
|
|
258
|
+
``response.create`` admitted meanwhile would be matched to this request's
|
|
259
|
+
identity. An accepted request released the gate at ``response.created``,
|
|
260
|
+
so take it back only if nobody else holds it; a contended gate is never
|
|
261
|
+
awaited from the reader.
|
|
262
|
+
|
|
263
|
+
``response.created`` released the gate together with the shared work
|
|
264
|
+
admission, so taking it back for an accepted request also marks the
|
|
265
|
+
admission for re-acquisition: the replay's ``response.create`` must
|
|
266
|
+
re-enter the configured work limit before it is sent, on the
|
|
267
|
+
transport-close path exactly as on the terminal-error path.
|
|
268
|
+
"""
|
|
269
|
+
if request_state.response_create_gate_acquired:
|
|
270
|
+
return True
|
|
271
|
+
if gate.locked():
|
|
272
|
+
return False
|
|
273
|
+
accepted = request_state.response_id is not None and not request_state.awaiting_response_created
|
|
274
|
+
await gate.acquire()
|
|
275
|
+
request_state.response_create_gate = gate
|
|
276
|
+
request_state.response_create_gate_acquired = True
|
|
277
|
+
if accepted and request_state.response_create_admission is None:
|
|
278
|
+
request_state.response_create_admission_reacquire_required = True
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
async def _stage_websocket_request_state_for_replay(
|
|
283
|
+
request_state: _WebSocketRequestState,
|
|
284
|
+
*,
|
|
285
|
+
create_gate: asyncio.Semaphore | None,
|
|
286
|
+
surface: Literal["http_bridge", "websocket"],
|
|
287
|
+
trigger: str,
|
|
288
|
+
) -> bool:
|
|
289
|
+
"""Reset a request to the pre-created shape before its replay is prepared.
|
|
290
|
+
|
|
291
|
+
For an accepted request this captures the client-visible response id and
|
|
292
|
+
arms the prelude suppression *before* ``response_id`` is cleared (clearing
|
|
293
|
+
first is what leaks a second ``response.created``), marks the shared work
|
|
294
|
+
admission for re-acquisition, and re-claims the session create gate when
|
|
295
|
+
one is supplied. Returns ``False`` without touching the state when the gate
|
|
296
|
+
is held by another request. Pre-created requests pass through unchanged.
|
|
297
|
+
|
|
298
|
+
``terminal_settlement_phase`` is deliberately left alone: the settlement
|
|
299
|
+
claim belongs to the bridge terminal bookkeeping, not to staging. The
|
|
300
|
+
transparent-code branch recorded a ``"claimed"`` marker when it popped the
|
|
301
|
+
request; the capacity-message wait branch keeps the request in pending
|
|
302
|
+
ownership while it waits and records the marker itself when it gives that
|
|
303
|
+
ownership up (``_relinquish_http_bridge_capacity_wait_ownership``). Either
|
|
304
|
+
way the marker is what lets the shielded abort settlement (issue #1594)
|
|
305
|
+
release the API-key reservation when the replay fails and the continuation
|
|
306
|
+
is cancelled or raises before it finalizes; a replay that succeeds keeps
|
|
307
|
+
the request in pending ownership, where the abort helper skips it, and the
|
|
308
|
+
bookkeeping clears the marker on its normal exit.
|
|
309
|
+
"""
|
|
310
|
+
accepted = request_state.response_id is not None and not request_state.awaiting_response_created
|
|
311
|
+
if accepted:
|
|
312
|
+
if create_gate is not None and not await _claim_websocket_replay_create_gate(request_state, create_gate):
|
|
313
|
+
return False
|
|
314
|
+
request_state.replay_downstream_response_id = (
|
|
315
|
+
request_state.replay_downstream_response_id or request_state.response_id
|
|
316
|
+
)
|
|
317
|
+
request_state.suppress_next_created_downstream = True
|
|
318
|
+
request_state.suppress_next_in_progress_downstream = request_state.response_event_count >= 2
|
|
319
|
+
request_state.response_create_admission_reacquire_required = True
|
|
320
|
+
logger.info(
|
|
321
|
+
"Accepted output-free replay staged request_id=%s surface=%s trigger=%s visible_response_id=%s events=%d",
|
|
322
|
+
request_state.request_log_id or request_state.request_id,
|
|
323
|
+
surface,
|
|
324
|
+
trigger,
|
|
325
|
+
request_state.replay_downstream_response_id,
|
|
326
|
+
request_state.response_event_count,
|
|
327
|
+
)
|
|
328
|
+
request_state.awaiting_response_created = True
|
|
329
|
+
request_state.response_id = None
|
|
330
|
+
request_state.response_event_count = 0
|
|
331
|
+
return True
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from app.modules.proxy._service.http_bridge.helpers import _extract_model_class, _log_http_bridge_event
|
|
4
|
+
from app.modules.proxy._service.http_bridge.protocol import _HTTPBridgeServiceProtocol
|
|
5
|
+
from app.modules.proxy._service.support import _HTTPBridgeSession
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _HTTPBridgeAccountSessionsMixin:
|
|
9
|
+
async def close_http_bridge_sessions_for_account(self: _HTTPBridgeServiceProtocol, account_id: str) -> int:
|
|
10
|
+
sessions_to_close: list[_HTTPBridgeSession] = []
|
|
11
|
+
scheduled_session_ids: set[int] = set()
|
|
12
|
+
async with self._http_bridge_lock:
|
|
13
|
+
for key, session in tuple(self._http_bridge_sessions.items()):
|
|
14
|
+
if session.account.id != account_id:
|
|
15
|
+
continue
|
|
16
|
+
detached = self._detach_http_bridge_session_locked(key, expected_session=session)
|
|
17
|
+
if detached is None:
|
|
18
|
+
continue
|
|
19
|
+
_log_http_bridge_event(
|
|
20
|
+
"evict_account_binding_changed",
|
|
21
|
+
key,
|
|
22
|
+
account_id=session.account.id,
|
|
23
|
+
model=session.request_model,
|
|
24
|
+
cache_key_family=key.affinity_kind,
|
|
25
|
+
model_class=_extract_model_class(session.request_model) if session.request_model else None,
|
|
26
|
+
)
|
|
27
|
+
sessions_to_close.append(detached)
|
|
28
|
+
scheduled_session_ids.add(id(detached))
|
|
29
|
+
# Detached predecessors still own authenticated sockets and account
|
|
30
|
+
# leases. Account invalidation must fence them even though a newer
|
|
31
|
+
# generation occupies (or has vacated) their canonical key.
|
|
32
|
+
for session in tuple(self._http_bridge_detached_sessions.values()):
|
|
33
|
+
if session.account.id != account_id or id(session) in scheduled_session_ids:
|
|
34
|
+
continue
|
|
35
|
+
close_task = session.resource_close_task
|
|
36
|
+
if close_task is not None and (
|
|
37
|
+
not close_task.done() or (not close_task.cancelled() and close_task.exception() is None)
|
|
38
|
+
):
|
|
39
|
+
# ``closed`` only rejects admission. A live close task (or a
|
|
40
|
+
# successfully completed one awaiting registry finalization)
|
|
41
|
+
# is the proof that this detached generation is already owned.
|
|
42
|
+
continue
|
|
43
|
+
session.closed = True
|
|
44
|
+
_log_http_bridge_event(
|
|
45
|
+
"evict_account_binding_changed",
|
|
46
|
+
session.key,
|
|
47
|
+
account_id=session.account.id,
|
|
48
|
+
model=session.request_model,
|
|
49
|
+
cache_key_family=session.key.affinity_kind,
|
|
50
|
+
model_class=_extract_model_class(session.request_model) if session.request_model else None,
|
|
51
|
+
)
|
|
52
|
+
sessions_to_close.append(session)
|
|
53
|
+
scheduled_session_ids.add(id(session))
|
|
54
|
+
|
|
55
|
+
for session in sessions_to_close:
|
|
56
|
+
await self._close_http_bridge_session_bounded(session, reason="account_binding_changed")
|
|
57
|
+
return len(sessions_to_close)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from app.core.clients.proxy import ProxyResponseError
|
|
7
|
+
from app.core.resilience.overload import local_overload_error
|
|
8
|
+
from app.modules.proxy._service.http_bridge.helpers import (
|
|
9
|
+
_close_http_bridge_session_bounded,
|
|
10
|
+
_http_bridge_capacity_generation_count,
|
|
11
|
+
_http_bridge_pending_count_nowait,
|
|
12
|
+
_http_bridge_pending_state_is_stale,
|
|
13
|
+
_http_bridge_request_counts_against_queue,
|
|
14
|
+
_log_http_bridge_event,
|
|
15
|
+
_raise_http_bridge_incompatible_admission_handoff,
|
|
16
|
+
_record_http_bridge_unanchored_handoff_recovery,
|
|
17
|
+
http_bridge_activity_snapshot_nowait,
|
|
18
|
+
)
|
|
19
|
+
from app.modules.proxy._service.http_bridge.protocol import _HTTPBridgeServiceProtocol
|
|
20
|
+
from app.modules.proxy._service.support import (
|
|
21
|
+
_http_bridge_session_supports_service_tier,
|
|
22
|
+
_HTTPBridgeSession,
|
|
23
|
+
_HTTPBridgeSessionKey,
|
|
24
|
+
)
|
|
25
|
+
from app.modules.proxy.affinity import _extract_model_class
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _HTTPBridgeActivityMixin:
|
|
29
|
+
_http_bridge_pending_state_is_stale = staticmethod(_http_bridge_pending_state_is_stale)
|
|
30
|
+
|
|
31
|
+
def _recover_http_bridge_incompatible_admission_handoff(
|
|
32
|
+
self: Any,
|
|
33
|
+
key: Any,
|
|
34
|
+
existing: Any,
|
|
35
|
+
force_durable_takeover: bool,
|
|
36
|
+
original_request_unanchored: bool,
|
|
37
|
+
request_model: str | None,
|
|
38
|
+
api_key: Any,
|
|
39
|
+
incoming_turn_state: str | None,
|
|
40
|
+
previous_response_id: str | None,
|
|
41
|
+
preferred_account_id: str | None,
|
|
42
|
+
require_preferred_account: bool,
|
|
43
|
+
request_service_tier: str | None,
|
|
44
|
+
) -> tuple[Any, bool]:
|
|
45
|
+
if original_request_unanchored and existing is not None:
|
|
46
|
+
detached = self._detach_http_bridge_session_locked(key, expected_session=existing)
|
|
47
|
+
if detached is not None:
|
|
48
|
+
force_durable_takeover = True
|
|
49
|
+
_record_http_bridge_unanchored_handoff_recovery(reason="closed_admission_handoff")
|
|
50
|
+
_log_http_bridge_event(
|
|
51
|
+
"unanchored_handoff_recovery",
|
|
52
|
+
key,
|
|
53
|
+
account_id=detached.account.id,
|
|
54
|
+
model=request_model,
|
|
55
|
+
detail="outcome=retired_closed_admission_handoff",
|
|
56
|
+
cache_key_family=key.affinity_kind,
|
|
57
|
+
model_class=_extract_model_class(request_model) if request_model else None,
|
|
58
|
+
owner_check_applied=False,
|
|
59
|
+
)
|
|
60
|
+
self._schedule_http_bridge_session_closes([detached], reason="unanchored_handoff_recovery")
|
|
61
|
+
return None, force_durable_takeover
|
|
62
|
+
|
|
63
|
+
_raise_http_bridge_incompatible_admission_handoff(
|
|
64
|
+
session=existing,
|
|
65
|
+
key=key,
|
|
66
|
+
api_key=api_key,
|
|
67
|
+
incoming_turn_state=incoming_turn_state,
|
|
68
|
+
previous_response_id=previous_response_id,
|
|
69
|
+
preferred_account_id=preferred_account_id,
|
|
70
|
+
require_preferred_account=require_preferred_account,
|
|
71
|
+
request_service_tier=request_service_tier,
|
|
72
|
+
service_tier_supported=_http_bridge_session_supports_service_tier(
|
|
73
|
+
existing,
|
|
74
|
+
request_model=request_model,
|
|
75
|
+
request_service_tier=request_service_tier,
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
raise AssertionError("incompatible admission handoff must raise")
|
|
79
|
+
|
|
80
|
+
async def _close_http_bridge_session_bounded(
|
|
81
|
+
self: Any,
|
|
82
|
+
session: _HTTPBridgeSession,
|
|
83
|
+
*,
|
|
84
|
+
reason: str,
|
|
85
|
+
) -> None:
|
|
86
|
+
await _close_http_bridge_session_bounded(self, session, reason=reason)
|
|
87
|
+
|
|
88
|
+
def _http_bridge_active_capacity_error(
|
|
89
|
+
self: _HTTPBridgeServiceProtocol,
|
|
90
|
+
*,
|
|
91
|
+
key: _HTTPBridgeSessionKey,
|
|
92
|
+
request_model: str | None,
|
|
93
|
+
) -> ProxyResponseError:
|
|
94
|
+
_log_http_bridge_event(
|
|
95
|
+
"capacity_exhausted_active_sessions",
|
|
96
|
+
key,
|
|
97
|
+
account_id=None,
|
|
98
|
+
model=request_model,
|
|
99
|
+
pending_count=_http_bridge_capacity_generation_count(self),
|
|
100
|
+
cache_key_family=key.affinity_kind,
|
|
101
|
+
model_class=_extract_model_class(request_model) if request_model else None,
|
|
102
|
+
)
|
|
103
|
+
return ProxyResponseError(
|
|
104
|
+
429,
|
|
105
|
+
local_overload_error(
|
|
106
|
+
"HTTP responses session bridge has no idle capacity",
|
|
107
|
+
code="capacity_exhausted_active_sessions",
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _http_bridge_forced_close_must_finish_before_create(
|
|
112
|
+
self: _HTTPBridgeServiceProtocol,
|
|
113
|
+
forced_replacement: bool,
|
|
114
|
+
max_sessions: int,
|
|
115
|
+
) -> bool:
|
|
116
|
+
# Detachment retains capacity. A forced replacement at the cap must
|
|
117
|
+
# finish closing its idle predecessor before enforcing the same cap.
|
|
118
|
+
return forced_replacement and _http_bridge_capacity_generation_count(self) >= max_sessions
|
|
119
|
+
|
|
120
|
+
async def _enforce_http_bridge_capacity_after_planned_closes(
|
|
121
|
+
self: _HTTPBridgeServiceProtocol,
|
|
122
|
+
*,
|
|
123
|
+
key: _HTTPBridgeSessionKey,
|
|
124
|
+
inflight_future: asyncio.Future[_HTTPBridgeSession] | None,
|
|
125
|
+
max_sessions: int,
|
|
126
|
+
request_model: str | None,
|
|
127
|
+
) -> None:
|
|
128
|
+
assert inflight_future is not None
|
|
129
|
+
async with self._http_bridge_lock:
|
|
130
|
+
if (
|
|
131
|
+
self._http_bridge_inflight_sessions.get(key) is not inflight_future
|
|
132
|
+
or _http_bridge_capacity_generation_count(self) <= max_sessions
|
|
133
|
+
):
|
|
134
|
+
return
|
|
135
|
+
# Planned evictions are discounted only to reserve this creation
|
|
136
|
+
# slot. A bounded close may return on timeout while the detached
|
|
137
|
+
# socket and leases remain live, so registry ownership wins here.
|
|
138
|
+
_log_http_bridge_event(
|
|
139
|
+
"capacity_exhausted_after_lru_close",
|
|
140
|
+
key,
|
|
141
|
+
account_id=None,
|
|
142
|
+
model=request_model,
|
|
143
|
+
pending_count=_http_bridge_capacity_generation_count(self),
|
|
144
|
+
cache_key_family=key.affinity_kind,
|
|
145
|
+
model_class=_extract_model_class(request_model) if request_model else None,
|
|
146
|
+
)
|
|
147
|
+
capacity_error = ProxyResponseError(
|
|
148
|
+
429,
|
|
149
|
+
local_overload_error(
|
|
150
|
+
"HTTP responses session bridge has no idle capacity",
|
|
151
|
+
code="capacity_exhausted_active_sessions",
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
await self._fail_http_bridge_inflight_session_creation(key, inflight_future, capacity_error)
|
|
155
|
+
raise capacity_error
|
|
156
|
+
|
|
157
|
+
async def _http_bridge_pending_count(
|
|
158
|
+
self: _HTTPBridgeServiceProtocol,
|
|
159
|
+
session: _HTTPBridgeSession,
|
|
160
|
+
) -> int:
|
|
161
|
+
async with session.pending_lock:
|
|
162
|
+
visible_pending_count = sum(
|
|
163
|
+
1
|
|
164
|
+
for request_state in session.pending_requests
|
|
165
|
+
if _http_bridge_request_counts_against_queue(request_state)
|
|
166
|
+
)
|
|
167
|
+
return max(visible_pending_count, session.queued_request_count)
|
|
168
|
+
|
|
169
|
+
def http_bridge_activity_snapshot_nowait(self: _HTTPBridgeServiceProtocol) -> dict[str, int | bool]:
|
|
170
|
+
return http_bridge_activity_snapshot_nowait(self)
|
|
171
|
+
|
|
172
|
+
def _http_bridge_pending_count_nowait(
|
|
173
|
+
self: _HTTPBridgeServiceProtocol,
|
|
174
|
+
session: _HTTPBridgeSession,
|
|
175
|
+
*,
|
|
176
|
+
context: str,
|
|
177
|
+
) -> int | None:
|
|
178
|
+
return _http_bridge_pending_count_nowait(session, context=context)
|