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,253 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Collection, Mapping
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
|
|
8
|
+
from app.core.clients.proxy import CODEX_LB_REQUIRED_CAPABILITY_HEADER, ProxyResponseError
|
|
9
|
+
from app.core.errors import openai_error
|
|
10
|
+
from app.core.types import JsonValue
|
|
11
|
+
from app.core.utils.json_guards import is_json_mapping
|
|
12
|
+
from app.modules.api_keys.service import ApiKeyData
|
|
13
|
+
from app.modules.proxy.capability_lineage import (
|
|
14
|
+
CapabilityLineageAlias,
|
|
15
|
+
normalize_capability_lineage_aliases,
|
|
16
|
+
)
|
|
17
|
+
from app.modules.proxy.repo_bundle import ProxyRepoFactory
|
|
18
|
+
|
|
19
|
+
REQUIRED_CAPABILITY_HEADER = CODEX_LB_REQUIRED_CAPABILITY_HEADER
|
|
20
|
+
CODEX_PARENT_THREAD_ID_HEADER = "x-codex-parent-thread-id"
|
|
21
|
+
CODEX_WINDOW_ID_HEADER = "x-codex-window-id"
|
|
22
|
+
CAPABILITY_SIGNAL_UNTRUSTED_CODE = "capability_signal_untrusted"
|
|
23
|
+
UNSUPPORTED_REQUIRED_CAPABILITY_CODE = "unsupported_required_capability"
|
|
24
|
+
CAPABILITY_LINEAGE_UNAVAILABLE_CODE = "capability_lineage_unavailable"
|
|
25
|
+
CAPABILITY_ROUTING_UNAVAILABLE_CODE = "capability_routing_unavailable"
|
|
26
|
+
CAPABILITY_ROUTING_UNAVAILABLE_MESSAGE = "Required capability routing is unavailable; retry later."
|
|
27
|
+
|
|
28
|
+
_TERMINAL_WINDOW_SLOT_RE = re.compile(r":\d+$")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RoutingCapability(StrEnum):
|
|
32
|
+
TRUSTED_CYBER = "trusted_cyber"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class RoutingIntent:
|
|
37
|
+
required_capabilities: frozenset[RoutingCapability] = frozenset()
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def empty(cls) -> RoutingIntent:
|
|
41
|
+
return cls()
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def requiring(cls, capability: RoutingCapability) -> RoutingIntent:
|
|
45
|
+
return cls(required_capabilities=frozenset({capability}))
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def requires_trusted_cyber(self) -> bool:
|
|
49
|
+
return RoutingCapability.TRUSTED_CYBER in self.required_capabilities
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class CapabilityRoute:
|
|
54
|
+
require_security_work_authorized: bool
|
|
55
|
+
aliases: tuple[CapabilityLineageAlias, ...]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CapabilityRouter:
|
|
59
|
+
def __init__(self, repo_factory: ProxyRepoFactory) -> None:
|
|
60
|
+
self._repo_factory = repo_factory
|
|
61
|
+
|
|
62
|
+
async def route(
|
|
63
|
+
self,
|
|
64
|
+
intent: RoutingIntent,
|
|
65
|
+
*,
|
|
66
|
+
api_key_id: str | None,
|
|
67
|
+
aliases: Collection[CapabilityLineageAlias],
|
|
68
|
+
) -> CapabilityRoute:
|
|
69
|
+
normalized_aliases = normalize_capability_lineage_aliases(aliases)
|
|
70
|
+
explicitly_required = intent.requires_trusted_cyber
|
|
71
|
+
if api_key_id is None or not normalized_aliases:
|
|
72
|
+
return CapabilityRoute(
|
|
73
|
+
require_security_work_authorized=explicitly_required,
|
|
74
|
+
aliases=normalized_aliases,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
async with self._repo_factory() as repositories:
|
|
79
|
+
repository = repositories.capability_lineage
|
|
80
|
+
if repository is None:
|
|
81
|
+
raise RuntimeError("capability lineage repository is unavailable")
|
|
82
|
+
inherited_requirement = False
|
|
83
|
+
if not explicitly_required:
|
|
84
|
+
inherited_requirement = await repository.is_required(
|
|
85
|
+
capability=RoutingCapability.TRUSTED_CYBER.value,
|
|
86
|
+
api_key_scope=api_key_id,
|
|
87
|
+
aliases=normalized_aliases,
|
|
88
|
+
)
|
|
89
|
+
required = explicitly_required or inherited_requirement
|
|
90
|
+
if required:
|
|
91
|
+
marker_hashes = await repository.require(
|
|
92
|
+
capability=RoutingCapability.TRUSTED_CYBER.value,
|
|
93
|
+
api_key_scope=api_key_id,
|
|
94
|
+
aliases=normalized_aliases,
|
|
95
|
+
)
|
|
96
|
+
if not marker_hashes:
|
|
97
|
+
raise RuntimeError("capability lineage marker was not persisted")
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
raise _capability_lineage_unavailable_error() from exc
|
|
100
|
+
|
|
101
|
+
return CapabilityRoute(
|
|
102
|
+
require_security_work_authorized=required,
|
|
103
|
+
aliases=normalized_aliases,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def capability_lineage_aliases(
|
|
108
|
+
headers: Mapping[str, str],
|
|
109
|
+
*,
|
|
110
|
+
session_id: str | None = None,
|
|
111
|
+
turn_state: str | None = None,
|
|
112
|
+
previous_response_ids: Collection[str | None] = (),
|
|
113
|
+
client_metadata: JsonValue | None = None,
|
|
114
|
+
) -> tuple[CapabilityLineageAlias, ...]:
|
|
115
|
+
aliases: list[CapabilityLineageAlias] = []
|
|
116
|
+
_append_alias(aliases, "session_header", session_id)
|
|
117
|
+
_append_alias(aliases, "turn_state", turn_state)
|
|
118
|
+
for previous_response_id in previous_response_ids:
|
|
119
|
+
_append_alias(aliases, "previous_response", previous_response_id)
|
|
120
|
+
|
|
121
|
+
parent_thread_ids = (
|
|
122
|
+
*_header_values(headers, CODEX_PARENT_THREAD_ID_HEADER),
|
|
123
|
+
*_metadata_string_values(client_metadata, CODEX_PARENT_THREAD_ID_HEADER),
|
|
124
|
+
)
|
|
125
|
+
for parent_thread_id in parent_thread_ids:
|
|
126
|
+
_append_alias(aliases, "codex_task", parent_thread_id)
|
|
127
|
+
|
|
128
|
+
window_ids = (
|
|
129
|
+
*_header_values(headers, CODEX_WINDOW_ID_HEADER),
|
|
130
|
+
*_metadata_string_values(client_metadata, CODEX_WINDOW_ID_HEADER),
|
|
131
|
+
)
|
|
132
|
+
for window_id in window_ids:
|
|
133
|
+
_append_alias(aliases, "codex_window", window_id)
|
|
134
|
+
stable_task_id = _TERMINAL_WINDOW_SLOT_RE.sub("", window_id.strip())
|
|
135
|
+
_append_alias(aliases, "codex_task", stable_task_id)
|
|
136
|
+
return normalize_capability_lineage_aliases(aliases)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_routing_intent(
|
|
140
|
+
headers: Mapping[str, str],
|
|
141
|
+
*,
|
|
142
|
+
api_key: ApiKeyData | None,
|
|
143
|
+
client_metadata: JsonValue | None = None,
|
|
144
|
+
header_values: Collection[str] | None = None,
|
|
145
|
+
client_metadata_values: Collection[JsonValue] | None = None,
|
|
146
|
+
) -> RoutingIntent:
|
|
147
|
+
values: tuple[JsonValue, ...] = (
|
|
148
|
+
*(tuple(header_values) if header_values is not None else _header_values(headers, REQUIRED_CAPABILITY_HEADER)),
|
|
149
|
+
*(
|
|
150
|
+
tuple(client_metadata_values)
|
|
151
|
+
if client_metadata_values is not None
|
|
152
|
+
else _metadata_values(client_metadata, REQUIRED_CAPABILITY_HEADER)
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
if not values:
|
|
156
|
+
return RoutingIntent.empty()
|
|
157
|
+
if api_key is None:
|
|
158
|
+
raise ProxyResponseError(
|
|
159
|
+
403,
|
|
160
|
+
openai_error(
|
|
161
|
+
CAPABILITY_SIGNAL_UNTRUSTED_CODE,
|
|
162
|
+
"Required capability signal requires an authenticated proxy API key.",
|
|
163
|
+
error_type="permission_error",
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
if len(values) != 1:
|
|
167
|
+
raise _unsupported_capability_error()
|
|
168
|
+
raw_capability = values[0]
|
|
169
|
+
if not isinstance(raw_capability, str):
|
|
170
|
+
raise _unsupported_capability_error()
|
|
171
|
+
try:
|
|
172
|
+
capability = RoutingCapability(raw_capability)
|
|
173
|
+
except ValueError as exc:
|
|
174
|
+
raise _unsupported_capability_error() from exc
|
|
175
|
+
return RoutingIntent.requiring(capability)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def required_capability_metadata_values(client_metadata: JsonValue | None) -> tuple[JsonValue, ...]:
|
|
179
|
+
"""Required-capability values carried in a request's ``client_metadata``."""
|
|
180
|
+
|
|
181
|
+
return _metadata_values(client_metadata, REQUIRED_CAPABILITY_HEADER)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def reject_capability_signal_outside_response_create(
|
|
185
|
+
*,
|
|
186
|
+
api_key: ApiKeyData | None,
|
|
187
|
+
client_metadata: JsonValue | None,
|
|
188
|
+
client_metadata_values: Collection[JsonValue] | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
intent = parse_routing_intent(
|
|
191
|
+
{},
|
|
192
|
+
api_key=api_key,
|
|
193
|
+
client_metadata=client_metadata,
|
|
194
|
+
client_metadata_values=client_metadata_values,
|
|
195
|
+
)
|
|
196
|
+
if intent.required_capabilities:
|
|
197
|
+
raise _unsupported_capability_error()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def strip_capability_metadata(client_metadata: JsonValue | None) -> JsonValue | None:
|
|
201
|
+
if not is_json_mapping(client_metadata):
|
|
202
|
+
return client_metadata
|
|
203
|
+
normalized_name = REQUIRED_CAPABILITY_HEADER.lower()
|
|
204
|
+
return {
|
|
205
|
+
key: value
|
|
206
|
+
for key, value in client_metadata.items()
|
|
207
|
+
if not isinstance(key, str) or key.lower() != normalized_name
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _header_values(headers: Mapping[str, str], name: str) -> tuple[str, ...]:
|
|
212
|
+
normalized_name = name.lower()
|
|
213
|
+
return tuple(value for header_name, value in headers.items() if header_name.lower() == normalized_name)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _metadata_values(client_metadata: JsonValue | None, name: str) -> tuple[JsonValue, ...]:
|
|
217
|
+
if not is_json_mapping(client_metadata):
|
|
218
|
+
return ()
|
|
219
|
+
normalized_name = name.lower()
|
|
220
|
+
return tuple(
|
|
221
|
+
value for key, value in client_metadata.items() if isinstance(key, str) and key.lower() == normalized_name
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _metadata_string_values(client_metadata: JsonValue | None, name: str) -> tuple[str, ...]:
|
|
226
|
+
return tuple(value for value in _metadata_values(client_metadata, name) if isinstance(value, str))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _append_alias(aliases: list[CapabilityLineageAlias], kind: str, value: str | None) -> None:
|
|
230
|
+
if value is not None:
|
|
231
|
+
aliases.append(CapabilityLineageAlias(kind=kind, value=value))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _unsupported_capability_error() -> ProxyResponseError:
|
|
235
|
+
return ProxyResponseError(
|
|
236
|
+
400,
|
|
237
|
+
openai_error(
|
|
238
|
+
UNSUPPORTED_REQUIRED_CAPABILITY_CODE,
|
|
239
|
+
"Required routing capability is unsupported.",
|
|
240
|
+
error_type="invalid_request_error",
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _capability_lineage_unavailable_error() -> ProxyResponseError:
|
|
246
|
+
return ProxyResponseError(
|
|
247
|
+
503,
|
|
248
|
+
openai_error(
|
|
249
|
+
CAPABILITY_LINEAGE_UNAVAILABLE_CODE,
|
|
250
|
+
"Required capability lineage is unavailable; retry later.",
|
|
251
|
+
error_type="server_error",
|
|
252
|
+
),
|
|
253
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Shared account-owner consistency checks for proxy continuity sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from hashlib import sha256
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from app.core.clients.proxy import ProxyResponseError
|
|
11
|
+
from app.core.errors import openai_error
|
|
12
|
+
|
|
13
|
+
HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KIND = "internal_unanchored_parallel"
|
|
14
|
+
HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KEY_PREFIX = "account-neutral-replay:v1:"
|
|
15
|
+
# These are canonical lanes whose exact aliases may move only after the
|
|
16
|
+
# existing full-resend validator has proved the request account-neutral. A
|
|
17
|
+
# thread lane is hard during ordinary use, just like a session-header lane;
|
|
18
|
+
# omitting it here would accidentally remove safe owner-unavailable recovery
|
|
19
|
+
# merely because Codex now supplies a more precise canonical identity.
|
|
20
|
+
HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_REBINDABLE_KINDS = frozenset(
|
|
21
|
+
{"prompt_cache", "session_header", "thread_header", "turn_state_header"}
|
|
22
|
+
)
|
|
23
|
+
_HTTP_BRIDGE_SESSION_AFFINITY_HEADERS = frozenset(
|
|
24
|
+
{
|
|
25
|
+
"session_id",
|
|
26
|
+
"session-id",
|
|
27
|
+
"thread-id",
|
|
28
|
+
"x-codex-conversation-id",
|
|
29
|
+
"x-codex-session-id",
|
|
30
|
+
"x-codex-turn-state",
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
logger = logging.getLogger("app.modules.proxy.continuity")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def make_http_bridge_account_neutral_replay_key(nonce: str) -> tuple[str, str]:
|
|
37
|
+
if not nonce:
|
|
38
|
+
raise ValueError("account-neutral replay nonce must not be empty")
|
|
39
|
+
return (
|
|
40
|
+
HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KIND,
|
|
41
|
+
f"{HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KEY_PREFIX}{nonce}",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_http_bridge_account_neutral_replay(*, kind: str, key: str) -> bool:
|
|
46
|
+
"""Recognize only server-namespaced durable replay keys."""
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
kind == HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KIND
|
|
50
|
+
and key.startswith(HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KEY_PREFIX)
|
|
51
|
+
and len(key) > len(HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_KEY_PREFIX)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def without_http_bridge_session_affinity_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
56
|
+
"""Drop downstream aliases that must not reach a fresh upstream account."""
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
header_name: header_value
|
|
60
|
+
for header_name, header_value in headers.items()
|
|
61
|
+
if header_name.lower() not in _HTTP_BRIDGE_SESSION_AFFINITY_HEADERS
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _ReconnectPreferredOwner(Protocol):
|
|
66
|
+
preferred_account_id: str | None
|
|
67
|
+
file_required_preferred_account: bool
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_reconnect_preferred_account_id(
|
|
71
|
+
request_state: _ReconnectPreferredOwner,
|
|
72
|
+
session_account_id: str,
|
|
73
|
+
require_preferred_account: bool,
|
|
74
|
+
account_neutral_recovery: bool,
|
|
75
|
+
) -> str | None:
|
|
76
|
+
if request_state.file_required_preferred_account:
|
|
77
|
+
return request_state.preferred_account_id or session_account_id
|
|
78
|
+
if require_preferred_account or account_neutral_recovery:
|
|
79
|
+
return request_state.preferred_account_id
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def resolve_required_account_id(*owners: tuple[str, str | None]) -> str | None:
|
|
84
|
+
"""Return one proven owner or fail closed when hard sources disagree."""
|
|
85
|
+
resolved = [(source, account_id) for source, account_id in owners if account_id is not None]
|
|
86
|
+
if not resolved:
|
|
87
|
+
return None
|
|
88
|
+
owner_account_id = resolved[0][1]
|
|
89
|
+
conflicting_sources = [source for source, account_id in resolved if account_id != owner_account_id]
|
|
90
|
+
if conflicting_sources:
|
|
91
|
+
# Hard sources identify account-scoped upstream state. Choosing either
|
|
92
|
+
# side would silently abandon the other, so conflicts are never ordered
|
|
93
|
+
# by caller precedence or softened into ordinary affinity fallback.
|
|
94
|
+
sources = ", ".join(source for source, _account_id in resolved)
|
|
95
|
+
owner_hashes = ", ".join(
|
|
96
|
+
f"{source}={sha256(account_id.encode()).hexdigest()[:12]}" for source, account_id in resolved
|
|
97
|
+
)
|
|
98
|
+
logger.warning(
|
|
99
|
+
"continuity_owner_conflict sources=%s conflicting_sources=%s owner_hashes=%s",
|
|
100
|
+
sources,
|
|
101
|
+
", ".join(conflicting_sources),
|
|
102
|
+
owner_hashes,
|
|
103
|
+
)
|
|
104
|
+
raise ProxyResponseError(
|
|
105
|
+
502,
|
|
106
|
+
openai_error(
|
|
107
|
+
"continuity_owner_conflict",
|
|
108
|
+
f"Account-owned continuity sources conflict ({sources}); retry the logical turn.",
|
|
109
|
+
error_type="server_error",
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
return owner_account_id
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Trace whether the SSE terminal frame of a Responses stream reached the downstream writer.
|
|
2
|
+
|
|
3
|
+
``request_logs.status`` is settled when the proxy *parses* the upstream
|
|
4
|
+
terminal frame (``response.completed``/``failed``/``incomplete``/``error``),
|
|
5
|
+
not when that frame is *written* to the client. Two paths drop the write
|
|
6
|
+
without leaving a signal: uvicorn's ``send`` returns silently once the cycle is
|
|
7
|
+
marked disconnected (peer FIN/RST racing the terminal write), and an exception
|
|
8
|
+
raised by an ``api.py`` wrapper while formatting the terminal block closes the
|
|
9
|
+
inner generators after settlement. Both produce a full-looking body without a
|
|
10
|
+
terminal frame and a ``success`` row.
|
|
11
|
+
|
|
12
|
+
:class:`DeliveryTracedStreamingResponse` wraps the ASGI ``send`` of a
|
|
13
|
+
``StreamingResponse`` and reports, once per stream, one bounded ``outcome``
|
|
14
|
+
(see :data:`OUTCOMES`) as a Prometheus counter and one structured log line:
|
|
15
|
+
|
|
16
|
+
- ``terminal_written`` — a terminal frame was handed to the server's writer on
|
|
17
|
+
a live connection (a cancel or exception *after* that does not unwrite it);
|
|
18
|
+
- ``terminal_after_disconnect`` — the terminal frame was handed over after the
|
|
19
|
+
connection was already lost, so uvicorn dropped it;
|
|
20
|
+
- ``exception_before_terminal`` — the body raised before any terminal frame
|
|
21
|
+
(re-raised unchanged; no synthetic frame is emitted);
|
|
22
|
+
- ``cancelled_before_terminal`` — the body ended unfinished without a terminal:
|
|
23
|
+
client disconnect (Starlette cancels the body iteration) or server-side task
|
|
24
|
+
cancellation (``exc=CancelledError`` in the log line), including a cancel
|
|
25
|
+
that lands while ``send`` is still waiting on write backpressure for the
|
|
26
|
+
terminal chunk itself — uvicorn drains *before* it writes, so that chunk never
|
|
27
|
+
reached the writer;
|
|
28
|
+
- ``ended_without_terminal`` — the body iterator was exhausted and the final
|
|
29
|
+
``more_body=False`` sent, yet no terminal frame was seen.
|
|
30
|
+
|
|
31
|
+
It never alters the forwarded bytes, never emits a synthetic frame, and does
|
|
32
|
+
not touch request-log settlement. ``terminal_written`` means the *complete*
|
|
33
|
+
terminal block — ``event:`` line through the blank-line terminator — was handed
|
|
34
|
+
to the HTTP server's writer on a live connection; the transport buffer and any
|
|
35
|
+
reverse proxy in front are not observed. A terminal frame split across chunks
|
|
36
|
+
is committed only by the chunk that carries its terminator, so a disconnect or
|
|
37
|
+
cancellation between the ``event:`` line and the end of the block is reported
|
|
38
|
+
as ``terminal_after_disconnect`` / ``cancelled_before_terminal``, never as a
|
|
39
|
+
written terminal (an SSE client discards an unterminated event at EOF).
|
|
40
|
+
``chunks``/``bytes`` count the non-empty body chunks whose ``send`` returned
|
|
41
|
+
(the empty ``more_body=False`` message is not a chunk).
|
|
42
|
+
|
|
43
|
+
The log line's ``request_id`` is the ingress request id
|
|
44
|
+
(``RequestIdMiddleware`` contextvar, valid for the whole body stream because the
|
|
45
|
+
middleware is pure ASGI). The streaming service stores that value in
|
|
46
|
+
``request_logs.archive_request_id``; ``request_logs.request_id`` holds the
|
|
47
|
+
upstream response id once ``response.created`` was seen. Join on
|
|
48
|
+
``archive_request_id``.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from __future__ import annotations
|
|
52
|
+
|
|
53
|
+
import asyncio
|
|
54
|
+
import logging
|
|
55
|
+
import re
|
|
56
|
+
from collections.abc import AsyncIterator, Mapping
|
|
57
|
+
|
|
58
|
+
from fastapi.responses import StreamingResponse
|
|
59
|
+
from starlette.types import Message, Receive, Scope, Send
|
|
60
|
+
|
|
61
|
+
from app.core.http_protocol import HTTP_DISCONNECTED_STATE
|
|
62
|
+
from app.core.metrics.prometheus import PROMETHEUS_AVAILABLE, stream_terminal_delivery_total
|
|
63
|
+
from app.core.utils.request_id import get_request_id
|
|
64
|
+
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
OUTCOME_TERMINAL_WRITTEN = "terminal_written"
|
|
68
|
+
OUTCOME_TERMINAL_AFTER_DISCONNECT = "terminal_after_disconnect"
|
|
69
|
+
OUTCOME_EXCEPTION_BEFORE_TERMINAL = "exception_before_terminal"
|
|
70
|
+
OUTCOME_CANCELLED_BEFORE_TERMINAL = "cancelled_before_terminal"
|
|
71
|
+
OUTCOME_ENDED_WITHOUT_TERMINAL = "ended_without_terminal"
|
|
72
|
+
OUTCOMES = frozenset(
|
|
73
|
+
{
|
|
74
|
+
OUTCOME_TERMINAL_WRITTEN,
|
|
75
|
+
OUTCOME_TERMINAL_AFTER_DISCONNECT,
|
|
76
|
+
OUTCOME_EXCEPTION_BEFORE_TERMINAL,
|
|
77
|
+
OUTCOME_CANCELLED_BEFORE_TERMINAL,
|
|
78
|
+
OUTCOME_ENDED_WITHOUT_TERMINAL,
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Canonical SSE framing: every wrapper in api.py emits ``format_sse_event``
|
|
83
|
+
# blocks or passes the upstream ``event: <type>\ndata: ...\n\n`` line through
|
|
84
|
+
# verbatim, so the terminal event type is always on an ``event:`` line and a
|
|
85
|
+
# block starts with that line. The pattern is only ever *anchored* (``match``)
|
|
86
|
+
# at a line start — never ``search``-ed across a body — so a large non-terminal
|
|
87
|
+
# frame (``response.output_item.done`` with big tool-call arguments) costs one
|
|
88
|
+
# anchored comparison at offset 0 plus one ``bytes.find`` (memmem) for further
|
|
89
|
+
# line starts, not a regex walk over every byte.
|
|
90
|
+
_TERMINAL_LINE_RE = re.compile(rb"event: (response\.completed|response\.failed|response\.incomplete|error)(?=\r|\n)")
|
|
91
|
+
_EVENT_LINE_START = b"\nevent: "
|
|
92
|
+
# Frames are normally one block per ASGI chunk, but nothing pins that, so the
|
|
93
|
+
# last bytes of the previous chunk are re-scanned together with the current one
|
|
94
|
+
# and a marker split across the boundary is still recognised. The longest marker
|
|
95
|
+
# (``\nevent: response.incomplete\n``) is 28 bytes; keep a little more. The
|
|
96
|
+
# same tail carries a block terminator split across chunks (``\r\n\r`` | ``\n``).
|
|
97
|
+
_TAIL_BYTES = 64
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _find_terminal_event(window: bytes) -> tuple[str, int] | None:
|
|
101
|
+
"""Return ``(terminal event type, offset just past the type)`` for an ``event:`` line of ``window``."""
|
|
102
|
+
if window.startswith(b"event: "):
|
|
103
|
+
match = _TERMINAL_LINE_RE.match(window)
|
|
104
|
+
if match is not None:
|
|
105
|
+
return match.group(1).decode("ascii"), match.end()
|
|
106
|
+
position = window.find(_EVENT_LINE_START)
|
|
107
|
+
while position != -1:
|
|
108
|
+
match = _TERMINAL_LINE_RE.match(window, position + 1)
|
|
109
|
+
if match is not None:
|
|
110
|
+
return match.group(1).decode("ascii"), match.end()
|
|
111
|
+
position = window.find(_EVENT_LINE_START, position + 1)
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _find_block_end(window: bytes) -> int:
|
|
116
|
+
"""Return the offset just past the first SSE blank line in ``window``, or ``-1``.
|
|
117
|
+
|
|
118
|
+
A block ends at an empty line: a line terminator immediately followed by
|
|
119
|
+
another one. LF and CRLF framing (and either mixed) are recognised through
|
|
120
|
+
``bytes.find`` on ``\n`` — a terminal ``response.completed`` frame carries
|
|
121
|
+
one very long ``data:`` line, so this loop runs two or three iterations,
|
|
122
|
+
never a regex walk. Bare-CR framing is not emitted or relayed by the proxy
|
|
123
|
+
and is not recognised.
|
|
124
|
+
"""
|
|
125
|
+
position = window.find(b"\n")
|
|
126
|
+
while position != -1:
|
|
127
|
+
following = window[position + 1 : position + 3]
|
|
128
|
+
if following.startswith(b"\n"):
|
|
129
|
+
return position + 2
|
|
130
|
+
if following == b"\r\n":
|
|
131
|
+
return position + 3
|
|
132
|
+
position = window.find(b"\n", position + 1)
|
|
133
|
+
return -1
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DeliveryTracedStreamingResponse(StreamingResponse):
|
|
137
|
+
"""``StreamingResponse`` that reports whether the SSE terminal frame reached the writer.
|
|
138
|
+
|
|
139
|
+
``outcome`` is ``None`` until the response finishes and one of :data:`OUTCOMES`
|
|
140
|
+
afterwards; tests and callers may read it, the metric/log emission happens
|
|
141
|
+
exactly once in ``__call__``'s ``finally``.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
content: AsyncIterator[str] | AsyncIterator[bytes],
|
|
147
|
+
*,
|
|
148
|
+
surface: str,
|
|
149
|
+
media_type: str = "text/event-stream",
|
|
150
|
+
headers: Mapping[str, str] | None = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
super().__init__(content, media_type=media_type, headers=headers)
|
|
153
|
+
self.surface = surface
|
|
154
|
+
self.outcome: str | None = None
|
|
155
|
+
self._chunks = 0
|
|
156
|
+
self._bytes = 0
|
|
157
|
+
self._tail = b""
|
|
158
|
+
# ``_pending_terminal`` names the terminal whose ``event:`` line was
|
|
159
|
+
# handed over while the rest of its block is still outstanding;
|
|
160
|
+
# ``_terminal_type`` is set only once the block terminator was handed over.
|
|
161
|
+
self._pending_terminal: str | None = None
|
|
162
|
+
self._terminal_type: str | None = None
|
|
163
|
+
self._terminal_dropped = False
|
|
164
|
+
self._final_sent = False
|
|
165
|
+
|
|
166
|
+
def _observe_body(self, body: bytes) -> bool:
|
|
167
|
+
"""Account a body chunk handed to the writer; return whether it completed the terminal block."""
|
|
168
|
+
if not body:
|
|
169
|
+
return False
|
|
170
|
+
self._chunks += 1
|
|
171
|
+
self._bytes += len(body)
|
|
172
|
+
if self._terminal_type is not None:
|
|
173
|
+
return False
|
|
174
|
+
window = self._tail + body if self._tail else body
|
|
175
|
+
if self._pending_terminal is None:
|
|
176
|
+
found = _find_terminal_event(window)
|
|
177
|
+
if found is None:
|
|
178
|
+
self._tail = window[-_TAIL_BYTES:]
|
|
179
|
+
return False
|
|
180
|
+
self._pending_terminal, block_start = found
|
|
181
|
+
# Only bytes after the event line may terminate its block: the
|
|
182
|
+
# blank line that *preceded* it belongs to the previous frame.
|
|
183
|
+
window = window[block_start:]
|
|
184
|
+
block_end = _find_block_end(window)
|
|
185
|
+
if block_end == -1:
|
|
186
|
+
self._tail = window[-_TAIL_BYTES:]
|
|
187
|
+
return False
|
|
188
|
+
self._terminal_type = self._pending_terminal
|
|
189
|
+
self._tail = b""
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
193
|
+
state = scope.get("state")
|
|
194
|
+
|
|
195
|
+
async def traced_send(message: Message) -> None:
|
|
196
|
+
await send(message)
|
|
197
|
+
if message["type"] != "http.response.body":
|
|
198
|
+
return
|
|
199
|
+
# Accounted only after ``send`` returned: uvicorn awaits its
|
|
200
|
+
# write-backpressure drain *before* writing, so a cancel that lands
|
|
201
|
+
# inside ``send`` means this chunk never reached the writer.
|
|
202
|
+
if self._observe_body(bytes(message.get("body", b""))):
|
|
203
|
+
# uvicorn's ``send`` returns without writing once the cycle is
|
|
204
|
+
# disconnected; the protocol stamps that state synchronously in
|
|
205
|
+
# ``connection_lost``, before any task can resume, so a stamp
|
|
206
|
+
# visible here means the chunk that completed the terminal
|
|
207
|
+
# block never reached the writer.
|
|
208
|
+
self._terminal_dropped = isinstance(state, dict) and HTTP_DISCONNECTED_STATE in state
|
|
209
|
+
if not message.get("more_body", False):
|
|
210
|
+
self._final_sent = True
|
|
211
|
+
|
|
212
|
+
exc_name: str | None = None
|
|
213
|
+
cancelled = False
|
|
214
|
+
try:
|
|
215
|
+
await super().__call__(scope, receive, traced_send)
|
|
216
|
+
except asyncio.CancelledError:
|
|
217
|
+
cancelled = True
|
|
218
|
+
raise
|
|
219
|
+
except BaseException as exc:
|
|
220
|
+
exc_name = type(exc).__name__
|
|
221
|
+
raise
|
|
222
|
+
finally:
|
|
223
|
+
self._finish(exc_name=exc_name, cancelled=cancelled)
|
|
224
|
+
|
|
225
|
+
def _classify(self, *, exc_name: str | None, cancelled: bool) -> str:
|
|
226
|
+
if self._terminal_type is not None:
|
|
227
|
+
# A cancel or exception after the complete terminal block was
|
|
228
|
+
# handed over does not unwrite it; the exception name stays visible
|
|
229
|
+
# in the log line. A block whose ``event:`` line went out but whose
|
|
230
|
+
# terminator did not (``_pending_terminal`` set, ``_terminal_type``
|
|
231
|
+
# unset) falls through to the no-terminal outcomes below.
|
|
232
|
+
return OUTCOME_TERMINAL_AFTER_DISCONNECT if self._terminal_dropped else OUTCOME_TERMINAL_WRITTEN
|
|
233
|
+
if exc_name is not None:
|
|
234
|
+
return OUTCOME_EXCEPTION_BEFORE_TERMINAL
|
|
235
|
+
if cancelled or not self._final_sent:
|
|
236
|
+
# Starlette's ASGI 2.3 path absorbs the disconnect-driven cancel
|
|
237
|
+
# inside its task group, so a client that left mid-stream returns
|
|
238
|
+
# here normally with the body unfinished (no ``more_body=False``).
|
|
239
|
+
# ``exc=CancelledError`` in the log line marks the other shape: the
|
|
240
|
+
# server task itself was cancelled (uvicorn shutdown drain).
|
|
241
|
+
return OUTCOME_CANCELLED_BEFORE_TERMINAL
|
|
242
|
+
return OUTCOME_ENDED_WITHOUT_TERMINAL
|
|
243
|
+
|
|
244
|
+
def _finish(self, *, exc_name: str | None, cancelled: bool) -> None:
|
|
245
|
+
outcome = self._classify(exc_name=exc_name, cancelled=cancelled)
|
|
246
|
+
self.outcome = outcome
|
|
247
|
+
if PROMETHEUS_AVAILABLE and stream_terminal_delivery_total is not None:
|
|
248
|
+
stream_terminal_delivery_total.labels(surface=self.surface, outcome=outcome).inc()
|
|
249
|
+
if outcome == OUTCOME_TERMINAL_WRITTEN:
|
|
250
|
+
level = logging.DEBUG
|
|
251
|
+
elif outcome == OUTCOME_CANCELLED_BEFORE_TERMINAL:
|
|
252
|
+
level = logging.INFO
|
|
253
|
+
else:
|
|
254
|
+
level = logging.WARNING
|
|
255
|
+
logger.log(
|
|
256
|
+
level,
|
|
257
|
+
"responses_stream_terminal_delivery request_id=%s surface=%s outcome=%s terminal=%s "
|
|
258
|
+
"chunks=%d bytes=%d exc=%s",
|
|
259
|
+
get_request_id(),
|
|
260
|
+
self.surface,
|
|
261
|
+
outcome,
|
|
262
|
+
self._terminal_type,
|
|
263
|
+
self._chunks,
|
|
264
|
+
self._bytes,
|
|
265
|
+
"CancelledError" if cancelled else exc_name,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
__all__ = [
|
|
270
|
+
"OUTCOMES",
|
|
271
|
+
"OUTCOME_CANCELLED_BEFORE_TERMINAL",
|
|
272
|
+
"OUTCOME_ENDED_WITHOUT_TERMINAL",
|
|
273
|
+
"OUTCOME_EXCEPTION_BEFORE_TERMINAL",
|
|
274
|
+
"OUTCOME_TERMINAL_AFTER_DISCONNECT",
|
|
275
|
+
"OUTCOME_TERMINAL_WRITTEN",
|
|
276
|
+
"DeliveryTracedStreamingResponse",
|
|
277
|
+
]
|