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,749 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
8
|
+
from typing import Any, NoReturn, Protocol, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
import aiohttp
|
|
11
|
+
|
|
12
|
+
from app.core.auth.refresh import RefreshError
|
|
13
|
+
from app.core.balancer import ResetPreferenceWindow, RoutingStrategy
|
|
14
|
+
from app.core.clients.files import FileProxyError, pop_files_timeout_overrides, push_files_timeout_overrides
|
|
15
|
+
from app.core.clients.files import create_file as core_create_file
|
|
16
|
+
from app.core.clients.files import finalize_file as core_finalize_file
|
|
17
|
+
from app.core.clients.proxy import ProxyResponseError, UpstreamProxyRouteTrace, filter_inbound_headers
|
|
18
|
+
from app.core.config.settings import get_settings
|
|
19
|
+
from app.core.config.settings_cache import get_settings_cache
|
|
20
|
+
from app.core.errors import openai_error
|
|
21
|
+
from app.core.openai.requests import (
|
|
22
|
+
ResponsesCompactRequest,
|
|
23
|
+
ResponsesRequest,
|
|
24
|
+
extract_input_file_ids,
|
|
25
|
+
extract_input_image_file_references,
|
|
26
|
+
)
|
|
27
|
+
from app.core.types import JsonValue
|
|
28
|
+
from app.core.upstream_proxy import ResolvedUpstreamRoute, UpstreamProxyRouteError
|
|
29
|
+
from app.core.utils.request_id import ensure_request_id, get_request_id
|
|
30
|
+
from app.db.models import Account
|
|
31
|
+
from app.modules.api_keys.service import ApiKeyData
|
|
32
|
+
from app.modules.proxy._service.support import (
|
|
33
|
+
_request_log_client_fields,
|
|
34
|
+
_RequestLogFailureMetadata,
|
|
35
|
+
)
|
|
36
|
+
from app.modules.proxy.continuity import resolve_required_account_id
|
|
37
|
+
from app.modules.proxy.file_pin_repository import (
|
|
38
|
+
FileAccountPinOwnershipConflict,
|
|
39
|
+
FileAccountPinRepository,
|
|
40
|
+
)
|
|
41
|
+
from app.modules.proxy.helpers import _header_account_id, _normalize_error_code, _parse_openai_error
|
|
42
|
+
from app.modules.proxy.load_balancer import AccountSelection
|
|
43
|
+
from app.modules.proxy.selection_errors import selection_failure_response
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger("app.modules.proxy.service")
|
|
46
|
+
T = TypeVar("T")
|
|
47
|
+
_ResponsesPayloadT = ResponsesRequest | ResponsesCompactRequest
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _FileOpsServiceProtocol(Protocol):
|
|
51
|
+
_encryptor: Any
|
|
52
|
+
_file_pin_session_factory: Any
|
|
53
|
+
_load_balancer: Any
|
|
54
|
+
_FILE_ACCOUNT_PIN_TTL_SECONDS: int
|
|
55
|
+
|
|
56
|
+
async def _select_account_with_budget_compatible(self, deadline: float, **kwargs: object) -> AccountSelection: ...
|
|
57
|
+
async def _select_account_with_budget(self, deadline: float, **kwargs: Any) -> AccountSelection: ...
|
|
58
|
+
async def _ensure_previsible_unary_fresh_with_failover(self, account: Account, **kwargs: Any) -> Account: ...
|
|
59
|
+
async def _retry_previsible_unary_call_failover(
|
|
60
|
+
self, exc: ProxyResponseError, account: Account, **kwargs: Any
|
|
61
|
+
) -> tuple[Account, dict[str, JsonValue]] | None: ...
|
|
62
|
+
async def _ensure_fresh_with_budget_or_auth_error(self, account: Account, *, timeout_seconds: float) -> Account: ...
|
|
63
|
+
async def _handle_proxy_error(self, account: Account, exc: ProxyResponseError) -> None: ...
|
|
64
|
+
async def _write_request_log(self, **kwargs: Any) -> None: ...
|
|
65
|
+
async def _resolve_upstream_route_for_account(
|
|
66
|
+
self, account: Account, *, operation: str
|
|
67
|
+
) -> ResolvedUpstreamRoute | None: ...
|
|
68
|
+
async def _proxy_files_call(self, **kwargs: Any) -> tuple[dict[str, JsonValue], str | None]: ...
|
|
69
|
+
async def _pin_file_account(self, file_id: str, account_id: str) -> None: ...
|
|
70
|
+
async def _resolve_file_account(self, file_id: str) -> str | None: ...
|
|
71
|
+
async def _resolve_file_account_for_responses(
|
|
72
|
+
self,
|
|
73
|
+
payload: ResponsesRequest | ResponsesCompactRequest,
|
|
74
|
+
headers: Mapping[str, str],
|
|
75
|
+
) -> str | None: ...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _service_core_create_file() -> Callable[..., Awaitable[dict[str, JsonValue]]]:
|
|
79
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
80
|
+
if service_module is not None:
|
|
81
|
+
return cast(
|
|
82
|
+
Callable[..., Awaitable[dict[str, JsonValue]]],
|
|
83
|
+
getattr(service_module, "core_create_file", core_create_file),
|
|
84
|
+
)
|
|
85
|
+
return core_create_file
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _service_core_finalize_file() -> Callable[..., Awaitable[dict[str, JsonValue]]]:
|
|
89
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
90
|
+
if service_module is not None:
|
|
91
|
+
return cast(
|
|
92
|
+
Callable[..., Awaitable[dict[str, JsonValue]]],
|
|
93
|
+
getattr(service_module, "core_finalize_file", core_finalize_file),
|
|
94
|
+
)
|
|
95
|
+
return core_finalize_file
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _service_global_or(name: str, fallback: T) -> T:
|
|
99
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
100
|
+
if service_module is None:
|
|
101
|
+
return fallback
|
|
102
|
+
return cast(T, getattr(service_module, name, fallback))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _service_get_settings() -> Any:
|
|
106
|
+
return _service_global_or("get_settings", get_settings)()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _service_get_settings_cache() -> Any:
|
|
110
|
+
return _service_global_or("get_settings_cache", get_settings_cache)()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _service_time() -> Any:
|
|
114
|
+
return _service_global_or("time", time)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _service_global(name: str) -> Any:
|
|
118
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
119
|
+
if service_module is None:
|
|
120
|
+
raise RuntimeError("app.modules.proxy.service is not loaded")
|
|
121
|
+
return getattr(service_module, name)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _remaining_budget_seconds(deadline: float) -> float:
|
|
125
|
+
return cast(Callable[[float], float], _service_global("_remaining_budget_seconds"))(deadline)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _raise_proxy_budget_exhausted() -> NoReturn:
|
|
129
|
+
cast(Callable[[], NoReturn], _service_global("_raise_proxy_budget_exhausted"))()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _raise_proxy_unavailable(message: str) -> NoReturn:
|
|
133
|
+
cast(Callable[[str], NoReturn], _service_global("_raise_proxy_unavailable"))(message)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _request_log_failure_metadata(exc: ProxyResponseError) -> _RequestLogFailureMetadata:
|
|
137
|
+
return cast(
|
|
138
|
+
Callable[[ProxyResponseError], _RequestLogFailureMetadata], _service_global("_request_log_failure_metadata")
|
|
139
|
+
)(exc)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _proxy_response_failed_account(exc: ProxyResponseError, fallback: Account) -> Account:
|
|
143
|
+
return cast(Callable[[ProxyResponseError, Account], Account], _service_global("_proxy_response_failed_account"))(
|
|
144
|
+
exc, fallback
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _refresh_error_failed_account(exc: RefreshError, fallback: Account) -> Account:
|
|
149
|
+
return cast(Callable[[RefreshError, Account], Account], _service_global("_refresh_error_failed_account"))(
|
|
150
|
+
exc, fallback
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _prefer_earlier_reset_window(settings: Any) -> ResetPreferenceWindow:
|
|
155
|
+
return cast(Callable[[Any], ResetPreferenceWindow], _service_global("_prefer_earlier_reset_window"))(settings)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _routing_strategy(settings: Any) -> RoutingStrategy:
|
|
159
|
+
return cast(Callable[[Any], RoutingStrategy], _service_global("_routing_strategy"))(settings)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
_FAILED_ACCOUNT_ATTR = "_codex_lb_failed_account"
|
|
163
|
+
_REQUEST_TRANSPORT_HTTP = "http"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class _FileOwnerPostSuccessError(RuntimeError):
|
|
167
|
+
def __init__(self, proxy_error: ProxyResponseError) -> None:
|
|
168
|
+
super().__init__("File owner persistence failed after a successful upstream call")
|
|
169
|
+
self.proxy_error = proxy_error
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _file_owner_unavailable_error() -> ProxyResponseError:
|
|
173
|
+
return ProxyResponseError(
|
|
174
|
+
502,
|
|
175
|
+
openai_error(
|
|
176
|
+
"file_owner_unavailable",
|
|
177
|
+
"Input file owner metadata is unavailable; upload the file again and retry.",
|
|
178
|
+
error_type="server_error",
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class _FileOpsMixin:
|
|
184
|
+
# File-account pin TTL: long enough to cover a slow client-side
|
|
185
|
+
# PUT of a 512 MiB upload (the upstream limit) plus the finalize
|
|
186
|
+
# poll loop and a follow-up ``/responses`` that references the
|
|
187
|
+
# file_id, while still bounding how long stale pins remain in
|
|
188
|
+
# shared storage. 30 minutes covers a 512 MiB
|
|
189
|
+
# upload at ~280 KiB/s -- well below typical broadband uplink.
|
|
190
|
+
# The database clock defines both expiry and opportunistic cleanup.
|
|
191
|
+
_FILE_ACCOUNT_PIN_TTL_SECONDS: int = 30 * 60
|
|
192
|
+
|
|
193
|
+
async def _pin_file_account(
|
|
194
|
+
self,
|
|
195
|
+
file_id: str,
|
|
196
|
+
account_id: str,
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Remember that ``file_id`` was registered through ``account_id``.
|
|
199
|
+
|
|
200
|
+
Used so a subsequent ``finalize_file`` can be routed to the same
|
|
201
|
+
account that created the file, including when another replica
|
|
202
|
+
handles the follow-up request.
|
|
203
|
+
"""
|
|
204
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
205
|
+
if not file_id or not account_id:
|
|
206
|
+
return
|
|
207
|
+
try:
|
|
208
|
+
async with proxy._file_pin_session_factory() as session:
|
|
209
|
+
await FileAccountPinRepository(session).claim(
|
|
210
|
+
file_id,
|
|
211
|
+
account_id,
|
|
212
|
+
ttl_seconds=proxy._FILE_ACCOUNT_PIN_TTL_SECONDS,
|
|
213
|
+
)
|
|
214
|
+
except FileAccountPinOwnershipConflict as exc:
|
|
215
|
+
raise ProxyResponseError(
|
|
216
|
+
502,
|
|
217
|
+
openai_error(
|
|
218
|
+
"continuity_owner_conflict",
|
|
219
|
+
"File ownership conflicts with an existing live upload.",
|
|
220
|
+
error_type="server_error",
|
|
221
|
+
),
|
|
222
|
+
) from exc
|
|
223
|
+
except Exception as exc:
|
|
224
|
+
raise _file_owner_unavailable_error() from exc
|
|
225
|
+
|
|
226
|
+
async def _resolve_file_account(self, file_id: str) -> str | None:
|
|
227
|
+
"""Return the pinned account_id for ``file_id`` if still live."""
|
|
228
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
229
|
+
if not file_id:
|
|
230
|
+
return None
|
|
231
|
+
try:
|
|
232
|
+
async with proxy._file_pin_session_factory() as session:
|
|
233
|
+
return await FileAccountPinRepository(session).get_live_account_id(file_id)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
raise _file_owner_unavailable_error() from exc
|
|
236
|
+
|
|
237
|
+
async def _resolve_file_account_for_responses(
|
|
238
|
+
self,
|
|
239
|
+
payload: ResponsesRequest | ResponsesCompactRequest,
|
|
240
|
+
headers: Mapping[str, str],
|
|
241
|
+
) -> str | None:
|
|
242
|
+
"""Resolve a ``preferred_account_id`` from durable ``input_file.file_id`` pins."""
|
|
243
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
244
|
+
del headers
|
|
245
|
+
|
|
246
|
+
input_value = payload.input
|
|
247
|
+
if isinstance(payload, ResponsesCompactRequest):
|
|
248
|
+
input_value = payload.to_payload().get("input")
|
|
249
|
+
file_ids = extract_input_file_ids(input_value)
|
|
250
|
+
if not file_ids:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
async with proxy._file_pin_session_factory() as session:
|
|
255
|
+
account_ids_by_file_id = await FileAccountPinRepository(session).get_live_account_ids(file_ids)
|
|
256
|
+
except Exception as exc:
|
|
257
|
+
raise _file_owner_unavailable_error() from exc
|
|
258
|
+
resolved_account_ids = [account_ids_by_file_id.get(file_id) for file_id in file_ids]
|
|
259
|
+
|
|
260
|
+
pinned_account_ids = [account_id for account_id in resolved_account_ids if account_id is not None]
|
|
261
|
+
if not pinned_account_ids:
|
|
262
|
+
return None
|
|
263
|
+
if len(pinned_account_ids) != len(resolved_account_ids):
|
|
264
|
+
raise ProxyResponseError(
|
|
265
|
+
502,
|
|
266
|
+
openai_error(
|
|
267
|
+
"file_owner_unavailable",
|
|
268
|
+
"Input file owner metadata is unavailable; upload the file again and retry.",
|
|
269
|
+
error_type="server_error",
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
owner_account_ids = set(pinned_account_ids)
|
|
273
|
+
if len(owner_account_ids) != 1:
|
|
274
|
+
raise ProxyResponseError(
|
|
275
|
+
502,
|
|
276
|
+
openai_error(
|
|
277
|
+
"continuity_owner_conflict",
|
|
278
|
+
"Input files resolve to conflicting upstream accounts; retry with files from one account.",
|
|
279
|
+
error_type="server_error",
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
return next(iter(owner_account_ids))
|
|
283
|
+
|
|
284
|
+
async def _resolve_forwarded_file_account_for_responses(
|
|
285
|
+
self,
|
|
286
|
+
payload: ResponsesRequest | ResponsesCompactRequest,
|
|
287
|
+
headers: Mapping[str, str],
|
|
288
|
+
*,
|
|
289
|
+
forwarded_file_owner_account_id: str | None,
|
|
290
|
+
require_forwarded_file_owner: bool = False,
|
|
291
|
+
) -> str | None:
|
|
292
|
+
"""Revalidate signed bridge ownership against the shared database."""
|
|
293
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
294
|
+
durable_owner_account_id = await proxy._resolve_file_account_for_responses(payload, headers)
|
|
295
|
+
if (
|
|
296
|
+
require_forwarded_file_owner
|
|
297
|
+
and durable_owner_account_id is not None
|
|
298
|
+
and forwarded_file_owner_account_id is None
|
|
299
|
+
):
|
|
300
|
+
raise _file_owner_unavailable_error()
|
|
301
|
+
if forwarded_file_owner_account_id is not None and durable_owner_account_id is None:
|
|
302
|
+
raise _file_owner_unavailable_error()
|
|
303
|
+
return resolve_required_account_id(
|
|
304
|
+
("signed forwarding context", forwarded_file_owner_account_id),
|
|
305
|
+
("durable file pin", durable_owner_account_id),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _raise_for_unsupported_input_image_references(self, payload: _ResponsesPayloadT) -> None:
|
|
309
|
+
references = extract_input_image_file_references(payload.input)
|
|
310
|
+
if not references:
|
|
311
|
+
return
|
|
312
|
+
raise ProxyResponseError(
|
|
313
|
+
400,
|
|
314
|
+
openai_error(
|
|
315
|
+
"unsupported_input_image_format",
|
|
316
|
+
(
|
|
317
|
+
"input_image references via file_id or sediment:// URIs are not supported on "
|
|
318
|
+
"/v1/responses; the upstream API only accepts inline data: URLs. Send the "
|
|
319
|
+
"image inline (codex-cli style) or use the upload protocol exclusively for "
|
|
320
|
+
"MCP tool arguments."
|
|
321
|
+
),
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
async def create_file(
|
|
326
|
+
self,
|
|
327
|
+
payload: Mapping[str, JsonValue],
|
|
328
|
+
headers: Mapping[str, str],
|
|
329
|
+
*,
|
|
330
|
+
api_key: ApiKeyData | None = None,
|
|
331
|
+
) -> dict[str, JsonValue]:
|
|
332
|
+
"""Forward an inbound `POST /backend-api/files` registration to upstream.
|
|
333
|
+
|
|
334
|
+
The body is whatever the caller sent (already validated as
|
|
335
|
+
``FileCreateRequest`` at the API edge). Returns the upstream
|
|
336
|
+
``{file_id, upload_url, ...}`` JSON verbatim. Mirrors the
|
|
337
|
+
account-selection / refresh / 401-retry pattern from ``transcribe``.
|
|
338
|
+
|
|
339
|
+
On success we record a ``file_id -> account_id`` pin so a
|
|
340
|
+
subsequent ``finalize_file`` for the same ``file_id`` is routed
|
|
341
|
+
to the same account; the upstream contract is account-scoped
|
|
342
|
+
(chatgpt-account-id) so a finalize on a different account would
|
|
343
|
+
fail with not-found / unauthorized.
|
|
344
|
+
"""
|
|
345
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
346
|
+
|
|
347
|
+
async def persist_file_owner(result: dict[str, JsonValue], account_id: str) -> None:
|
|
348
|
+
file_id = result.get("file_id")
|
|
349
|
+
if isinstance(file_id, str) and file_id:
|
|
350
|
+
await proxy._pin_file_account(file_id, account_id)
|
|
351
|
+
|
|
352
|
+
result, _account_id = await proxy._proxy_files_call(
|
|
353
|
+
log_model="files-create",
|
|
354
|
+
kind="files-create",
|
|
355
|
+
api_key=api_key,
|
|
356
|
+
headers=headers,
|
|
357
|
+
invoke=lambda access_token, upstream_account_id, filtered_headers, route, route_trace: (
|
|
358
|
+
_service_core_create_file()(
|
|
359
|
+
payload=payload,
|
|
360
|
+
headers=filtered_headers,
|
|
361
|
+
access_token=access_token,
|
|
362
|
+
account_id=upstream_account_id,
|
|
363
|
+
route=route,
|
|
364
|
+
allow_direct_egress=route is None,
|
|
365
|
+
route_trace=route_trace,
|
|
366
|
+
)
|
|
367
|
+
),
|
|
368
|
+
on_success=persist_file_owner,
|
|
369
|
+
)
|
|
370
|
+
return result
|
|
371
|
+
|
|
372
|
+
async def finalize_file(
|
|
373
|
+
self,
|
|
374
|
+
file_id: str,
|
|
375
|
+
headers: Mapping[str, str],
|
|
376
|
+
*,
|
|
377
|
+
api_key: ApiKeyData | None = None,
|
|
378
|
+
) -> dict[str, JsonValue]:
|
|
379
|
+
"""Forward an inbound `POST /backend-api/files/{file_id}/uploaded` finalize call.
|
|
380
|
+
|
|
381
|
+
The upstream client (Codex CLI) polls this endpoint while
|
|
382
|
+
``status == "retry"``; ``_service_core_finalize_file()`` mirrors that loop
|
|
383
|
+
server-side with a 30 s budget. Returns the upstream JSON
|
|
384
|
+
verbatim.
|
|
385
|
+
|
|
386
|
+
Routes to the account that handled the matching ``create_file``
|
|
387
|
+
(via the durable pin table) so the upstream finalize call
|
|
388
|
+
carries the same ``chatgpt-account-id`` that registered the
|
|
389
|
+
file. Falls back to a fresh load-balancer selection when no
|
|
390
|
+
pin is found (unknown ``file_id`` or an expired pin).
|
|
391
|
+
"""
|
|
392
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
393
|
+
|
|
394
|
+
async def resolve_file_owner() -> str | None:
|
|
395
|
+
return await proxy._resolve_file_account(file_id)
|
|
396
|
+
|
|
397
|
+
async def persist_file_owner(result: dict[str, JsonValue], account_id: str) -> None:
|
|
398
|
+
if result.get("status") == "success":
|
|
399
|
+
await proxy._pin_file_account(file_id, account_id)
|
|
400
|
+
|
|
401
|
+
result, _account_id = await proxy._proxy_files_call(
|
|
402
|
+
log_model="files-finalize",
|
|
403
|
+
kind="files-finalize",
|
|
404
|
+
api_key=api_key,
|
|
405
|
+
headers=headers,
|
|
406
|
+
resolve_preferred_account_id=resolve_file_owner,
|
|
407
|
+
invoke=lambda access_token, upstream_account_id, filtered_headers, route, route_trace: (
|
|
408
|
+
_service_core_finalize_file()(
|
|
409
|
+
file_id=file_id,
|
|
410
|
+
headers=filtered_headers,
|
|
411
|
+
access_token=access_token,
|
|
412
|
+
account_id=upstream_account_id,
|
|
413
|
+
route=route,
|
|
414
|
+
allow_direct_egress=route is None,
|
|
415
|
+
route_trace=route_trace,
|
|
416
|
+
)
|
|
417
|
+
),
|
|
418
|
+
on_success=persist_file_owner,
|
|
419
|
+
)
|
|
420
|
+
return result
|
|
421
|
+
|
|
422
|
+
async def _proxy_files_call(
|
|
423
|
+
self,
|
|
424
|
+
*,
|
|
425
|
+
log_model: str,
|
|
426
|
+
kind: str,
|
|
427
|
+
api_key: ApiKeyData | None,
|
|
428
|
+
headers: Mapping[str, str],
|
|
429
|
+
invoke: Callable[
|
|
430
|
+
[str, str | None, Mapping[str, str], ResolvedUpstreamRoute | None, UpstreamProxyRouteTrace],
|
|
431
|
+
Awaitable[dict[str, JsonValue]],
|
|
432
|
+
],
|
|
433
|
+
preferred_account_id: str | None = None,
|
|
434
|
+
resolve_preferred_account_id: Callable[[], Awaitable[str | None]] | None = None,
|
|
435
|
+
on_success: Callable[[dict[str, JsonValue], str], Awaitable[None]] | None = None,
|
|
436
|
+
) -> tuple[dict[str, JsonValue], str | None]:
|
|
437
|
+
"""Shared account-selection / refresh / 401-retry plumbing for `/files` calls.
|
|
438
|
+
|
|
439
|
+
Mirrors the structure of ``transcribe``: pick an account with budget,
|
|
440
|
+
ensure freshness, invoke upstream, on 401 force-refresh and retry once,
|
|
441
|
+
translate ``FileProxyError`` -> ``ProxyResponseError``, and always
|
|
442
|
+
write a request-log entry on the way out. When
|
|
443
|
+
``preferred_account_id`` is provided or resolved (e.g. from the file_id
|
|
444
|
+
pin for ``finalize_file``), the call is strict to that account and
|
|
445
|
+
fails closed when the owner account is unavailable. ``on_success`` runs
|
|
446
|
+
before the request is logged or returned so durable owner persistence
|
|
447
|
+
remains part of the route's success contract.
|
|
448
|
+
"""
|
|
449
|
+
proxy = cast(_FileOpsServiceProtocol, self)
|
|
450
|
+
filtered = filter_inbound_headers(headers)
|
|
451
|
+
useragent, useragent_group, conversation_id = _request_log_client_fields(headers)
|
|
452
|
+
request_id = get_request_id() or ensure_request_id(None)
|
|
453
|
+
start = _service_time().monotonic()
|
|
454
|
+
base_settings = _service_get_settings()
|
|
455
|
+
deadline = start + base_settings.transcription_request_budget_seconds
|
|
456
|
+
account_id_value: str | None = None
|
|
457
|
+
log_status = "error"
|
|
458
|
+
log_error_code: str | None = None
|
|
459
|
+
log_error_message: str | None = None
|
|
460
|
+
failure_metadata = _RequestLogFailureMetadata()
|
|
461
|
+
route_mode: str | None = None
|
|
462
|
+
route_pool_id: str | None = None
|
|
463
|
+
route_endpoint_id: str | None = None
|
|
464
|
+
route_fallback_used: bool | None = None
|
|
465
|
+
route_fail_closed_reason: str | None = None
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
if resolve_preferred_account_id is not None:
|
|
469
|
+
preferred_account_id = await resolve_preferred_account_id()
|
|
470
|
+
settings = await _service_get_settings_cache().get()
|
|
471
|
+
prefer_earlier_reset = settings.prefer_earlier_reset_accounts
|
|
472
|
+
routing_strategy = _routing_strategy(settings)
|
|
473
|
+
|
|
474
|
+
async def _persist_success(result: dict[str, JsonValue], account_id: str) -> None:
|
|
475
|
+
if on_success is None:
|
|
476
|
+
return
|
|
477
|
+
try:
|
|
478
|
+
await on_success(result, account_id)
|
|
479
|
+
except ProxyResponseError as exc:
|
|
480
|
+
raise _FileOwnerPostSuccessError(exc) from exc
|
|
481
|
+
except Exception as exc:
|
|
482
|
+
raise _FileOwnerPostSuccessError(_file_owner_unavailable_error()) from exc
|
|
483
|
+
|
|
484
|
+
selection = await proxy._select_account_with_budget_compatible(
|
|
485
|
+
deadline,
|
|
486
|
+
request_id=request_id,
|
|
487
|
+
kind=kind,
|
|
488
|
+
api_key=api_key,
|
|
489
|
+
prefer_earlier_reset_accounts=prefer_earlier_reset,
|
|
490
|
+
routing_strategy=routing_strategy,
|
|
491
|
+
model=None,
|
|
492
|
+
preferred_account_id=preferred_account_id,
|
|
493
|
+
fallback_on_preferred_account_unavailable=preferred_account_id is None,
|
|
494
|
+
)
|
|
495
|
+
account = selection.account
|
|
496
|
+
if not account:
|
|
497
|
+
log_error_code = selection.error_code or "no_accounts"
|
|
498
|
+
log_error_message = selection.error_message or "No active accounts available"
|
|
499
|
+
status_code, error_payload = selection_failure_response(selection)
|
|
500
|
+
raise ProxyResponseError(status_code, error_payload)
|
|
501
|
+
account_id_value = account.id
|
|
502
|
+
|
|
503
|
+
async def _call(target: Account) -> dict[str, JsonValue]:
|
|
504
|
+
nonlocal route_mode, route_pool_id, route_endpoint_id, route_fallback_used
|
|
505
|
+
access_token = proxy._encryptor.decrypt(target.access_token_encrypted)
|
|
506
|
+
account_id = _header_account_id(target.chatgpt_account_id)
|
|
507
|
+
route = await proxy._resolve_upstream_route_for_account(target, operation=kind)
|
|
508
|
+
route_trace = UpstreamProxyRouteTrace()
|
|
509
|
+
if route is not None:
|
|
510
|
+
route_mode = route.mode
|
|
511
|
+
route_pool_id = route.pool_id
|
|
512
|
+
route_endpoint_id = route.endpoint_id
|
|
513
|
+
route_fallback_used = False
|
|
514
|
+
remaining_budget = _remaining_budget_seconds(deadline)
|
|
515
|
+
if remaining_budget <= 0:
|
|
516
|
+
logger.warning(
|
|
517
|
+
"%s request budget exhausted before upstream call request_id=%s account_id=%s",
|
|
518
|
+
kind,
|
|
519
|
+
request_id,
|
|
520
|
+
target.id,
|
|
521
|
+
)
|
|
522
|
+
_raise_proxy_budget_exhausted()
|
|
523
|
+
# Propagate the per-request budget so file create/finalize
|
|
524
|
+
# calls inherit the same effective timeout as the rest of
|
|
525
|
+
# the request, instead of letting them block on the
|
|
526
|
+
# module-default 60 s timeout regardless of how much
|
|
527
|
+
# budget is left.
|
|
528
|
+
timeout_tokens = push_files_timeout_overrides(
|
|
529
|
+
connect_timeout_seconds=remaining_budget,
|
|
530
|
+
total_timeout_seconds=remaining_budget,
|
|
531
|
+
)
|
|
532
|
+
try:
|
|
533
|
+
return await invoke(access_token, account_id, filtered, route, route_trace)
|
|
534
|
+
except FileProxyError as files_exc:
|
|
535
|
+
raise ProxyResponseError(
|
|
536
|
+
files_exc.status_code,
|
|
537
|
+
files_exc.payload,
|
|
538
|
+
failure_phase=files_exc.failure_phase,
|
|
539
|
+
retryable_same_contract=files_exc.retryable_same_contract is True,
|
|
540
|
+
failure_detail="transport_error" if files_exc.retryable_same_contract is not None else None,
|
|
541
|
+
) from files_exc
|
|
542
|
+
finally:
|
|
543
|
+
if route_trace.mode is not None:
|
|
544
|
+
route_mode = route_trace.mode
|
|
545
|
+
route_pool_id = route_trace.pool_id
|
|
546
|
+
route_endpoint_id = route_trace.endpoint_id
|
|
547
|
+
route_fallback_used = route_trace.fallback_used
|
|
548
|
+
pop_files_timeout_overrides(timeout_tokens)
|
|
549
|
+
|
|
550
|
+
async def _select_files_failover(excluded_account_ids: set[str]) -> AccountSelection:
|
|
551
|
+
return await proxy._select_account_with_budget(
|
|
552
|
+
deadline,
|
|
553
|
+
request_id=request_id,
|
|
554
|
+
kind=kind,
|
|
555
|
+
api_key=api_key,
|
|
556
|
+
prefer_earlier_reset_accounts=prefer_earlier_reset,
|
|
557
|
+
routing_strategy=routing_strategy,
|
|
558
|
+
model=None,
|
|
559
|
+
preferred_account_id=preferred_account_id,
|
|
560
|
+
exclude_account_ids=excluded_account_ids,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
account = await proxy._ensure_previsible_unary_fresh_with_failover(
|
|
565
|
+
account,
|
|
566
|
+
deadline=deadline,
|
|
567
|
+
request_id=request_id,
|
|
568
|
+
kind=kind,
|
|
569
|
+
select_next_account=_select_files_failover,
|
|
570
|
+
strict_account_id=preferred_account_id,
|
|
571
|
+
)
|
|
572
|
+
account_id_value = account.id
|
|
573
|
+
result = await _call(account)
|
|
574
|
+
await proxy._load_balancer.record_success(account)
|
|
575
|
+
await _persist_success(result, account.id)
|
|
576
|
+
log_status = "success"
|
|
577
|
+
return result, account_id_value
|
|
578
|
+
except RefreshError as refresh_exc:
|
|
579
|
+
if refresh_exc.is_permanent:
|
|
580
|
+
failed_account = _refresh_error_failed_account(refresh_exc, account)
|
|
581
|
+
account_id_value = failed_account.id
|
|
582
|
+
await proxy._load_balancer.mark_permanent_failure(failed_account, refresh_exc.code)
|
|
583
|
+
raise ProxyResponseError(
|
|
584
|
+
401,
|
|
585
|
+
openai_error(
|
|
586
|
+
"invalid_api_key",
|
|
587
|
+
refresh_exc.message,
|
|
588
|
+
error_type="invalid_request_error",
|
|
589
|
+
),
|
|
590
|
+
) from refresh_exc
|
|
591
|
+
except ProxyResponseError as exc:
|
|
592
|
+
if exc.status_code != 401:
|
|
593
|
+
failover = await proxy._retry_previsible_unary_call_failover(
|
|
594
|
+
exc,
|
|
595
|
+
account,
|
|
596
|
+
deadline=deadline,
|
|
597
|
+
select_next_account=_select_files_failover,
|
|
598
|
+
call_next=_call,
|
|
599
|
+
strict_account_id=preferred_account_id,
|
|
600
|
+
)
|
|
601
|
+
if failover is not None:
|
|
602
|
+
account, result = failover
|
|
603
|
+
account_id_value = account.id
|
|
604
|
+
await _persist_success(result, account.id)
|
|
605
|
+
log_status = "success"
|
|
606
|
+
return result, account_id_value
|
|
607
|
+
failed_account = _proxy_response_failed_account(exc, account)
|
|
608
|
+
account_id_value = failed_account.id
|
|
609
|
+
await proxy._handle_proxy_error(failed_account, exc)
|
|
610
|
+
raise
|
|
611
|
+
try:
|
|
612
|
+
remaining_budget = _remaining_budget_seconds(deadline)
|
|
613
|
+
if remaining_budget <= 0:
|
|
614
|
+
logger.warning(
|
|
615
|
+
"%s request budget exhausted before forced refresh retry request_id=%s account_id=%s",
|
|
616
|
+
kind,
|
|
617
|
+
request_id,
|
|
618
|
+
account.id,
|
|
619
|
+
)
|
|
620
|
+
_raise_proxy_budget_exhausted()
|
|
621
|
+
try:
|
|
622
|
+
account = await proxy._ensure_previsible_unary_fresh_with_failover(
|
|
623
|
+
account,
|
|
624
|
+
deadline=deadline,
|
|
625
|
+
request_id=request_id,
|
|
626
|
+
kind=kind,
|
|
627
|
+
select_next_account=_select_files_failover,
|
|
628
|
+
strict_account_id=preferred_account_id,
|
|
629
|
+
force=True,
|
|
630
|
+
)
|
|
631
|
+
except ProxyResponseError as refresh_failover_exc:
|
|
632
|
+
failed_account = _proxy_response_failed_account(refresh_failover_exc, account)
|
|
633
|
+
account_id_value = failed_account.id
|
|
634
|
+
await proxy._handle_proxy_error(failed_account, refresh_failover_exc)
|
|
635
|
+
raise
|
|
636
|
+
account_id_value = account.id
|
|
637
|
+
except RefreshError as refresh_exc:
|
|
638
|
+
if refresh_exc.is_permanent:
|
|
639
|
+
failed_account = _refresh_error_failed_account(refresh_exc, account)
|
|
640
|
+
account_id_value = failed_account.id
|
|
641
|
+
await proxy._load_balancer.mark_permanent_failure(failed_account, refresh_exc.code)
|
|
642
|
+
raise exc
|
|
643
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as timeout_exc:
|
|
644
|
+
logger.warning(
|
|
645
|
+
"%s forced refresh/connect failed request_id=%s account_id=%s",
|
|
646
|
+
kind,
|
|
647
|
+
request_id,
|
|
648
|
+
account.id,
|
|
649
|
+
exc_info=True,
|
|
650
|
+
)
|
|
651
|
+
_raise_proxy_unavailable(str(timeout_exc) or "Request to upstream timed out")
|
|
652
|
+
try:
|
|
653
|
+
result = await _call(account)
|
|
654
|
+
# The forced-refresh retry can swap to a refreshed
|
|
655
|
+
# account row -- re-pin to that account id so the
|
|
656
|
+
# caller's pin is consistent with the upstream call.
|
|
657
|
+
account_id_value = account.id
|
|
658
|
+
await proxy._load_balancer.record_success(account)
|
|
659
|
+
await _persist_success(result, account.id)
|
|
660
|
+
log_status = "success"
|
|
661
|
+
return result, account_id_value
|
|
662
|
+
except ProxyResponseError as retry_exc:
|
|
663
|
+
await proxy._handle_proxy_error(account, retry_exc)
|
|
664
|
+
if retry_exc.status_code == 401:
|
|
665
|
+
selection = await proxy._select_account_with_budget(
|
|
666
|
+
deadline,
|
|
667
|
+
request_id=request_id,
|
|
668
|
+
kind=kind,
|
|
669
|
+
api_key=api_key,
|
|
670
|
+
prefer_earlier_reset_accounts=prefer_earlier_reset,
|
|
671
|
+
routing_strategy=routing_strategy,
|
|
672
|
+
model=None,
|
|
673
|
+
preferred_account_id=preferred_account_id,
|
|
674
|
+
fallback_on_preferred_account_unavailable=preferred_account_id is None,
|
|
675
|
+
exclude_account_ids={account.id},
|
|
676
|
+
)
|
|
677
|
+
if selection.account is not None:
|
|
678
|
+
account = selection.account
|
|
679
|
+
account_id_value = account.id
|
|
680
|
+
account = await proxy._ensure_fresh_with_budget_or_auth_error(
|
|
681
|
+
account,
|
|
682
|
+
timeout_seconds=_remaining_budget_seconds(deadline),
|
|
683
|
+
)
|
|
684
|
+
try:
|
|
685
|
+
result = await _call(account)
|
|
686
|
+
await proxy._load_balancer.record_success(account)
|
|
687
|
+
await _persist_success(result, account.id)
|
|
688
|
+
log_status = "success"
|
|
689
|
+
return result, account_id_value
|
|
690
|
+
except ProxyResponseError as failover_exc:
|
|
691
|
+
await proxy._handle_proxy_error(account, failover_exc)
|
|
692
|
+
raise
|
|
693
|
+
raise
|
|
694
|
+
except _FileOwnerPostSuccessError as exc:
|
|
695
|
+
proxy_error = exc.proxy_error
|
|
696
|
+
failure_metadata = _request_log_failure_metadata(proxy_error)
|
|
697
|
+
error = _parse_openai_error(proxy_error.payload)
|
|
698
|
+
log_error_code = _normalize_error_code(
|
|
699
|
+
error.code if error else None,
|
|
700
|
+
error.type if error else None,
|
|
701
|
+
)
|
|
702
|
+
log_error_message = error.message if error else None
|
|
703
|
+
raise proxy_error from exc
|
|
704
|
+
except ProxyResponseError as exc:
|
|
705
|
+
failed_account = getattr(exc, _FAILED_ACCOUNT_ATTR, None)
|
|
706
|
+
if isinstance(failed_account, Account):
|
|
707
|
+
account_id_value = failed_account.id
|
|
708
|
+
failure_metadata = _request_log_failure_metadata(exc)
|
|
709
|
+
error = _parse_openai_error(exc.payload)
|
|
710
|
+
log_error_code = log_error_code or _normalize_error_code(
|
|
711
|
+
error.code if error else None,
|
|
712
|
+
error.type if error else None,
|
|
713
|
+
)
|
|
714
|
+
log_error_message = log_error_message or (error.message if error else None)
|
|
715
|
+
raise
|
|
716
|
+
except UpstreamProxyRouteError as exc:
|
|
717
|
+
route_fail_closed_reason = exc.reason
|
|
718
|
+
log_error_code = "upstream_proxy_unavailable"
|
|
719
|
+
log_error_message = exc.reason
|
|
720
|
+
raise ProxyResponseError(
|
|
721
|
+
502,
|
|
722
|
+
openai_error("upstream_proxy_unavailable", f"Upstream proxy route unavailable: {exc.reason}"),
|
|
723
|
+
) from exc
|
|
724
|
+
finally:
|
|
725
|
+
await proxy._write_request_log(
|
|
726
|
+
account_id=account_id_value,
|
|
727
|
+
api_key=api_key,
|
|
728
|
+
request_id=request_id,
|
|
729
|
+
model=log_model,
|
|
730
|
+
latency_ms=int((_service_time().monotonic() - start) * 1000),
|
|
731
|
+
status=log_status,
|
|
732
|
+
error_code=log_error_code,
|
|
733
|
+
error_message=log_error_message,
|
|
734
|
+
transport=_REQUEST_TRANSPORT_HTTP,
|
|
735
|
+
failure_phase=failure_metadata.failure_phase,
|
|
736
|
+
failure_detail=failure_metadata.failure_detail,
|
|
737
|
+
failure_exception_type=failure_metadata.failure_exception_type,
|
|
738
|
+
upstream_status_code=failure_metadata.upstream_status_code,
|
|
739
|
+
upstream_error_code=failure_metadata.upstream_error_code,
|
|
740
|
+
bridge_stage=failure_metadata.bridge_stage,
|
|
741
|
+
upstream_proxy_route_mode=route_mode,
|
|
742
|
+
upstream_proxy_pool_id=route_pool_id,
|
|
743
|
+
upstream_proxy_endpoint_id=route_endpoint_id,
|
|
744
|
+
upstream_proxy_fallback_used=route_fallback_used if route_endpoint_id else None,
|
|
745
|
+
upstream_proxy_fail_closed_reason=route_fail_closed_reason,
|
|
746
|
+
useragent=useragent,
|
|
747
|
+
useragent_group=useragent_group,
|
|
748
|
+
conversation_id=conversation_id,
|
|
749
|
+
)
|