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,745 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Coroutine, Mapping
|
|
7
|
+
from typing import Any, Protocol, cast
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
from anyio.lowlevel import checkpoint_if_cancelled
|
|
11
|
+
|
|
12
|
+
from app.core.clients.proxy import ProxyResponseError
|
|
13
|
+
from app.core.clock import clock_for, scheduler_for
|
|
14
|
+
from app.core.errors import openai_error
|
|
15
|
+
from app.core.exceptions import ProxyAuthError, ProxyRateLimitError
|
|
16
|
+
from app.core.openai.models import CompactResponsePayload
|
|
17
|
+
from app.core.utils.request_id import get_request_id
|
|
18
|
+
from app.core.utils.shared_future import wait_on_shared_future
|
|
19
|
+
from app.db.models import Account
|
|
20
|
+
from app.modules.api_keys.service import (
|
|
21
|
+
API_KEY_USAGE_RESERVATION_DEFAULT_INPUT_TOKENS,
|
|
22
|
+
API_KEY_USAGE_RESERVATION_DEFAULT_OUTPUT_TOKENS,
|
|
23
|
+
API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET,
|
|
24
|
+
ApiKeyData,
|
|
25
|
+
ApiKeyInvalidError,
|
|
26
|
+
ApiKeyRateLimitExceededError,
|
|
27
|
+
ApiKeyRequestUsageBudget,
|
|
28
|
+
ApiKeysService,
|
|
29
|
+
ApiKeyUsageReservationData,
|
|
30
|
+
)
|
|
31
|
+
from app.modules.proxy._service.support import (
|
|
32
|
+
_ApiKeyReservationTouchState,
|
|
33
|
+
_consume_api_key_reservation_heartbeat_result,
|
|
34
|
+
_signal_propagated_responses_service_cleanup_ready,
|
|
35
|
+
_StreamSettlement,
|
|
36
|
+
_WebSocketRequestState,
|
|
37
|
+
)
|
|
38
|
+
from app.modules.proxy.repo_bundle import ProxyRepoFactory
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger("app.modules.proxy.service")
|
|
41
|
+
|
|
42
|
+
_API_KEY_RESERVATION_HEARTBEAT_SECONDS = 300.0
|
|
43
|
+
_STREAM_API_KEY_RELEASE_RETRY_BASE_SECONDS = 0.1
|
|
44
|
+
_STREAM_API_KEY_RELEASE_RETRY_MAX_SECONDS = 5.0
|
|
45
|
+
_STREAM_API_KEY_RELEASE_RETRY_MAX_CONCURRENCY = 4
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _service_api_keys_service() -> type[ApiKeysService]:
|
|
49
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
50
|
+
if service_module is not None:
|
|
51
|
+
return cast(type[ApiKeysService], getattr(service_module, "ApiKeysService", ApiKeysService))
|
|
52
|
+
return ApiKeysService
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _api_key_reservation_heartbeat_seconds() -> float:
|
|
56
|
+
service_module = sys.modules.get("app.modules.proxy.service")
|
|
57
|
+
if service_module is not None:
|
|
58
|
+
value = getattr(
|
|
59
|
+
service_module,
|
|
60
|
+
"_API_KEY_RESERVATION_HEARTBEAT_SECONDS",
|
|
61
|
+
_API_KEY_RESERVATION_HEARTBEAT_SECONDS,
|
|
62
|
+
)
|
|
63
|
+
try:
|
|
64
|
+
return float(value)
|
|
65
|
+
except (TypeError, ValueError):
|
|
66
|
+
return _API_KEY_RESERVATION_HEARTBEAT_SECONDS
|
|
67
|
+
return _API_KEY_RESERVATION_HEARTBEAT_SECONDS
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class _ApiKeyUsageServiceProtocol(Protocol):
|
|
71
|
+
_repo_factory: ProxyRepoFactory
|
|
72
|
+
_background_cleanup_tasks: set[asyncio.Task[None]]
|
|
73
|
+
_stream_api_key_release_retry_semaphore: asyncio.Semaphore
|
|
74
|
+
_load_balancer: Any
|
|
75
|
+
|
|
76
|
+
async def _handle_stream_error(
|
|
77
|
+
self, account: Account, error: Any, code: str, http_status: int | None = None
|
|
78
|
+
) -> Any: ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _normalize_service_tier_value(value: Any) -> str | None:
|
|
82
|
+
if not isinstance(value, str):
|
|
83
|
+
return None
|
|
84
|
+
stripped = value.strip()
|
|
85
|
+
if not stripped:
|
|
86
|
+
return None
|
|
87
|
+
if stripped.lower() == "fast":
|
|
88
|
+
return "priority"
|
|
89
|
+
return stripped
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _service_tier_from_response(
|
|
93
|
+
response: CompactResponsePayload | None,
|
|
94
|
+
) -> str | None:
|
|
95
|
+
if response is None:
|
|
96
|
+
return None
|
|
97
|
+
extra = response.model_extra
|
|
98
|
+
if not isinstance(extra, Mapping):
|
|
99
|
+
return None
|
|
100
|
+
return _normalize_service_tier_value(extra.get("service_tier"))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _estimated_lease_tokens_from_request_usage_budget(budget: ApiKeyRequestUsageBudget | None) -> float:
|
|
104
|
+
if budget is None:
|
|
105
|
+
return 0.0
|
|
106
|
+
input_tokens = _bounded_lease_token_estimate(
|
|
107
|
+
budget.input_tokens,
|
|
108
|
+
default=API_KEY_USAGE_RESERVATION_DEFAULT_INPUT_TOKENS,
|
|
109
|
+
)
|
|
110
|
+
output_tokens = _bounded_lease_token_estimate(
|
|
111
|
+
budget.output_tokens,
|
|
112
|
+
default=API_KEY_USAGE_RESERVATION_DEFAULT_OUTPUT_TOKENS,
|
|
113
|
+
)
|
|
114
|
+
return float(input_tokens + output_tokens)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _bounded_lease_token_estimate(value: int | None, *, default: int) -> int:
|
|
118
|
+
if value is None:
|
|
119
|
+
return default
|
|
120
|
+
return max(0, min(value, API_KEY_USAGE_RESERVATION_MAX_TOKEN_BUDGET))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _ApiKeyUsageMixin:
|
|
124
|
+
async def _reserve_websocket_api_key_usage(
|
|
125
|
+
self,
|
|
126
|
+
api_key: ApiKeyData | None,
|
|
127
|
+
*,
|
|
128
|
+
request_model: str | None,
|
|
129
|
+
request_service_tier: str | None,
|
|
130
|
+
request_usage_budget: ApiKeyRequestUsageBudget | None = None,
|
|
131
|
+
) -> ApiKeyUsageReservationData | None:
|
|
132
|
+
if api_key is None:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
136
|
+
with anyio.CancelScope(shield=True):
|
|
137
|
+
async with proxy._repo_factory() as repos:
|
|
138
|
+
service = _service_api_keys_service()(repos.api_keys)
|
|
139
|
+
try:
|
|
140
|
+
return await service.enforce_limits_for_request(
|
|
141
|
+
api_key.id,
|
|
142
|
+
request_model=request_model,
|
|
143
|
+
request_service_tier=request_service_tier,
|
|
144
|
+
request_usage_budget=request_usage_budget,
|
|
145
|
+
)
|
|
146
|
+
except ApiKeyRateLimitExceededError as exc:
|
|
147
|
+
message = f"{exc}. Usage resets at {exc.reset_at.isoformat()}Z."
|
|
148
|
+
raise ProxyRateLimitError(message) from exc
|
|
149
|
+
except ApiKeyInvalidError as exc:
|
|
150
|
+
raise ProxyAuthError(str(exc)) from exc
|
|
151
|
+
|
|
152
|
+
async def _release_websocket_reservation(
|
|
153
|
+
self,
|
|
154
|
+
reservation: ApiKeyUsageReservationData | None,
|
|
155
|
+
) -> None:
|
|
156
|
+
if reservation is None:
|
|
157
|
+
return
|
|
158
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
159
|
+
with anyio.CancelScope(shield=True):
|
|
160
|
+
async with proxy._repo_factory() as repos:
|
|
161
|
+
service = _service_api_keys_service()(repos.api_keys)
|
|
162
|
+
await service.release_usage_reservation(reservation.reservation_id)
|
|
163
|
+
|
|
164
|
+
async def _release_websocket_request_state_reservation(
|
|
165
|
+
self,
|
|
166
|
+
request_state: _WebSocketRequestState,
|
|
167
|
+
) -> None:
|
|
168
|
+
self._cancel_request_state_api_key_reservation_heartbeat(request_state)
|
|
169
|
+
await self._release_websocket_reservation(request_state.api_key_reservation)
|
|
170
|
+
request_state.api_key_reservation = None
|
|
171
|
+
lifecycle = request_state.deferred_account_backoff_lifecycle
|
|
172
|
+
if lifecycle is not None:
|
|
173
|
+
lifecycle.settlement_confirmed = True
|
|
174
|
+
pending_backoffs = (
|
|
175
|
+
lifecycle.pending_backoffs if lifecycle is not None else request_state.deferred_account_error_backoffs
|
|
176
|
+
)
|
|
177
|
+
try:
|
|
178
|
+
if pending_backoffs:
|
|
179
|
+
await self._drain_deferred_account_error_backoffs(pending_backoffs)
|
|
180
|
+
finally:
|
|
181
|
+
# Backoffs and queued stream-health penalties own independent
|
|
182
|
+
# post-settlement lanes: a failed backoff write must not orphan
|
|
183
|
+
# the deferred health write.
|
|
184
|
+
if request_state.deferred_keyed_stream_health:
|
|
185
|
+
await self._drain_deferred_keyed_stream_health(request_state)
|
|
186
|
+
|
|
187
|
+
async def _drain_deferred_account_error_backoffs(
|
|
188
|
+
self,
|
|
189
|
+
pending_backoffs: dict[str, Account],
|
|
190
|
+
) -> None:
|
|
191
|
+
if not pending_backoffs:
|
|
192
|
+
return
|
|
193
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
194
|
+
while pending_backoffs:
|
|
195
|
+
account_id, account = pending_backoffs.popitem()
|
|
196
|
+
try:
|
|
197
|
+
await proxy._load_balancer.record_error_backoff(account)
|
|
198
|
+
except BaseException:
|
|
199
|
+
pending_backoffs.setdefault(account_id, account)
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
async def _drain_deferred_keyed_stream_health(
|
|
203
|
+
self,
|
|
204
|
+
request_state: _WebSocketRequestState,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Apply health writes deferred until reservation settlement.
|
|
207
|
+
|
|
208
|
+
Each entry is claimed atomically (popped without an intervening
|
|
209
|
+
await), so the idempotent release and terminal-finalize paths can
|
|
210
|
+
drain the same request state concurrently without applying a penalty
|
|
211
|
+
twice or corrupting the queue.
|
|
212
|
+
|
|
213
|
+
Each attempt runs as an owned task awaited through
|
|
214
|
+
``wait_on_shared_future`` (the ``_await_task_deferring_cancellation``
|
|
215
|
+
pattern, inlined because the cancelled-attempt path must re-queue the
|
|
216
|
+
claimed penalty before re-raising). Caller cancellation therefore
|
|
217
|
+
cannot abandon a
|
|
218
|
+
half-applied penalty for a later drain to replay: every claimed entry
|
|
219
|
+
is drained to completion first — no later path owns the queue once
|
|
220
|
+
terminal ownership is relinquished — and the cancellation re-raises
|
|
221
|
+
only after the queue is empty.
|
|
222
|
+
|
|
223
|
+
An attempt that raises is logged and dropped rather than retained or
|
|
224
|
+
re-raised: settlement has already committed, and the
|
|
225
|
+
settlement-ordering contract only requires an unconfirmed settlement
|
|
226
|
+
to leave health unapplied — a failed health write must not abort the
|
|
227
|
+
remaining terminal finalization (request-log write, retirement).
|
|
228
|
+
"""
|
|
229
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
230
|
+
deferred_cancellation: asyncio.CancelledError | None = None
|
|
231
|
+
penalties = request_state.deferred_keyed_stream_health
|
|
232
|
+
# The anyio shield keeps a level-cancelled Starlette scope from
|
|
233
|
+
# re-raising into every ``await``, which would otherwise busy-spin
|
|
234
|
+
# this loop until the owned task completes; the inner wait loop
|
|
235
|
+
# defers edge task cancellation the same way.
|
|
236
|
+
with anyio.CancelScope(shield=True):
|
|
237
|
+
while penalties:
|
|
238
|
+
penalty = penalties.pop(0)
|
|
239
|
+
apply_task = scheduler_for(self).create_task(
|
|
240
|
+
proxy._handle_stream_error(penalty.account, penalty.error, penalty.code)
|
|
241
|
+
)
|
|
242
|
+
while True:
|
|
243
|
+
try:
|
|
244
|
+
# wait_on_shared_future keeps repeated waits off the
|
|
245
|
+
# task's done-callback list (3.14 shield leaks one per
|
|
246
|
+
# cancelled wait).
|
|
247
|
+
await wait_on_shared_future(apply_task)
|
|
248
|
+
break
|
|
249
|
+
except asyncio.CancelledError as exc:
|
|
250
|
+
if apply_task.cancelled():
|
|
251
|
+
penalties.insert(0, penalty)
|
|
252
|
+
raise
|
|
253
|
+
deferred_cancellation = deferred_cancellation or exc
|
|
254
|
+
except Exception:
|
|
255
|
+
logger.warning(
|
|
256
|
+
"Deferred keyed stream-health write failed after settlement; dropping penalty "
|
|
257
|
+
"account_id=%s code=%s request_id=%s",
|
|
258
|
+
penalty.account.id,
|
|
259
|
+
penalty.code,
|
|
260
|
+
get_request_id(),
|
|
261
|
+
exc_info=True,
|
|
262
|
+
)
|
|
263
|
+
break
|
|
264
|
+
if deferred_cancellation is None:
|
|
265
|
+
# The shield also blocks the level cancellation this drain
|
|
266
|
+
# promises to re-raise after the queue empties. Probe without
|
|
267
|
+
# suspending so it surfaces here instead of at an arbitrary
|
|
268
|
+
# later checkpoint.
|
|
269
|
+
try:
|
|
270
|
+
await checkpoint_if_cancelled()
|
|
271
|
+
except asyncio.CancelledError as exc:
|
|
272
|
+
deferred_cancellation = exc
|
|
273
|
+
if deferred_cancellation is not None:
|
|
274
|
+
raise deferred_cancellation
|
|
275
|
+
|
|
276
|
+
async def _maybe_touch_api_key_reservation(
|
|
277
|
+
self,
|
|
278
|
+
*,
|
|
279
|
+
api_key: ApiKeyData | None,
|
|
280
|
+
reservation: ApiKeyUsageReservationData | None,
|
|
281
|
+
last_touch_at: float,
|
|
282
|
+
request_id: str,
|
|
283
|
+
surface: str,
|
|
284
|
+
) -> float:
|
|
285
|
+
if reservation is None:
|
|
286
|
+
return last_touch_at
|
|
287
|
+
|
|
288
|
+
now = clock_for(self).monotonic()
|
|
289
|
+
if now < last_touch_at + _api_key_reservation_heartbeat_seconds():
|
|
290
|
+
return last_touch_at
|
|
291
|
+
|
|
292
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
293
|
+
with anyio.CancelScope(shield=True):
|
|
294
|
+
try:
|
|
295
|
+
async with proxy._repo_factory() as repos:
|
|
296
|
+
service = _service_api_keys_service()(repos.api_keys)
|
|
297
|
+
touched = await service.touch_usage_reservation(reservation.reservation_id)
|
|
298
|
+
if not touched:
|
|
299
|
+
return last_touch_at
|
|
300
|
+
except Exception:
|
|
301
|
+
logger.warning(
|
|
302
|
+
"Failed to touch %s API key reservation key_id=%s request_id=%s",
|
|
303
|
+
surface,
|
|
304
|
+
api_key.id if api_key is not None else None,
|
|
305
|
+
request_id,
|
|
306
|
+
exc_info=True,
|
|
307
|
+
)
|
|
308
|
+
return last_touch_at
|
|
309
|
+
return now
|
|
310
|
+
|
|
311
|
+
async def _run_api_key_reservation_heartbeat(
|
|
312
|
+
self,
|
|
313
|
+
*,
|
|
314
|
+
api_key: ApiKeyData | None,
|
|
315
|
+
reservation: ApiKeyUsageReservationData | None,
|
|
316
|
+
touch_state: _ApiKeyReservationTouchState,
|
|
317
|
+
request_id: str,
|
|
318
|
+
surface: str,
|
|
319
|
+
stop_event: asyncio.Event,
|
|
320
|
+
) -> None:
|
|
321
|
+
scheduler = scheduler_for(self)
|
|
322
|
+
while not stop_event.is_set():
|
|
323
|
+
try:
|
|
324
|
+
await scheduler.wait_for(stop_event.wait(), timeout=_api_key_reservation_heartbeat_seconds())
|
|
325
|
+
return
|
|
326
|
+
except TimeoutError:
|
|
327
|
+
touch_state.last_touch_at = await self._maybe_touch_api_key_reservation(
|
|
328
|
+
api_key=api_key,
|
|
329
|
+
reservation=reservation,
|
|
330
|
+
last_touch_at=touch_state.last_touch_at,
|
|
331
|
+
request_id=request_id,
|
|
332
|
+
surface=surface,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _cancel_api_key_reservation_heartbeat_task(task: asyncio.Task[None]) -> None:
|
|
337
|
+
task.add_done_callback(_consume_api_key_reservation_heartbeat_result)
|
|
338
|
+
task.cancel()
|
|
339
|
+
|
|
340
|
+
def _start_request_state_api_key_reservation_heartbeat(
|
|
341
|
+
self,
|
|
342
|
+
request_state: _WebSocketRequestState,
|
|
343
|
+
*,
|
|
344
|
+
api_key: ApiKeyData | None,
|
|
345
|
+
surface: str,
|
|
346
|
+
) -> None:
|
|
347
|
+
if request_state.api_key_reservation is None:
|
|
348
|
+
return
|
|
349
|
+
if request_state.api_key_reservation_heartbeat_task is not None:
|
|
350
|
+
return
|
|
351
|
+
stop_event = asyncio.Event()
|
|
352
|
+
request_state.api_key_reservation_heartbeat_stop = stop_event
|
|
353
|
+
request_state.api_key_reservation_heartbeat_task = scheduler_for(self).create_task(
|
|
354
|
+
self._run_api_key_reservation_heartbeat(
|
|
355
|
+
api_key=api_key,
|
|
356
|
+
reservation=request_state.api_key_reservation,
|
|
357
|
+
touch_state=_ApiKeyReservationTouchState(
|
|
358
|
+
last_touch_at=request_state.api_key_reservation_last_touch_at,
|
|
359
|
+
),
|
|
360
|
+
request_id=request_state.response_id or request_state.request_log_id or request_state.request_id,
|
|
361
|
+
surface=surface,
|
|
362
|
+
stop_event=stop_event,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def _cancel_request_state_api_key_reservation_heartbeat(
|
|
367
|
+
self,
|
|
368
|
+
request_state: _WebSocketRequestState,
|
|
369
|
+
) -> None:
|
|
370
|
+
task = request_state.api_key_reservation_heartbeat_task
|
|
371
|
+
stop_event = request_state.api_key_reservation_heartbeat_stop
|
|
372
|
+
request_state.api_key_reservation_heartbeat_task = None
|
|
373
|
+
request_state.api_key_reservation_heartbeat_stop = None
|
|
374
|
+
if stop_event is not None:
|
|
375
|
+
stop_event.set()
|
|
376
|
+
if task is not None and not task.done():
|
|
377
|
+
self._cancel_api_key_reservation_heartbeat_task(task)
|
|
378
|
+
|
|
379
|
+
async def _maybe_touch_request_state_api_key_reservation(
|
|
380
|
+
self,
|
|
381
|
+
request_state: _WebSocketRequestState,
|
|
382
|
+
*,
|
|
383
|
+
api_key: ApiKeyData | None,
|
|
384
|
+
surface: str,
|
|
385
|
+
) -> None:
|
|
386
|
+
request_state.api_key_reservation_last_touch_at = await self._maybe_touch_api_key_reservation(
|
|
387
|
+
api_key=api_key,
|
|
388
|
+
reservation=request_state.api_key_reservation,
|
|
389
|
+
last_touch_at=request_state.api_key_reservation_last_touch_at,
|
|
390
|
+
request_id=request_state.response_id or request_state.request_id,
|
|
391
|
+
surface=surface,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
async def _settle_compact_api_key_usage(
|
|
395
|
+
self,
|
|
396
|
+
*,
|
|
397
|
+
api_key: ApiKeyData | None,
|
|
398
|
+
api_key_reservation: ApiKeyUsageReservationData | None,
|
|
399
|
+
response: CompactResponsePayload | None,
|
|
400
|
+
request_service_tier: str | None,
|
|
401
|
+
) -> None:
|
|
402
|
+
if api_key is None or api_key_reservation is None:
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
reservation_id = api_key_reservation.reservation_id
|
|
406
|
+
usage = response.usage if response is not None else None
|
|
407
|
+
input_tokens = usage.input_tokens if usage else None
|
|
408
|
+
output_tokens = usage.output_tokens if usage else None
|
|
409
|
+
cached_input_tokens = usage.input_tokens_details.cached_tokens if usage and usage.input_tokens_details else 0
|
|
410
|
+
model_name = api_key_reservation.model or (getattr(response, "model", None) or "")
|
|
411
|
+
response_service_tier = _service_tier_from_response(response)
|
|
412
|
+
service_tier = (
|
|
413
|
+
response_service_tier
|
|
414
|
+
if isinstance(response_service_tier, str)
|
|
415
|
+
else request_service_tier
|
|
416
|
+
if isinstance(request_service_tier, str)
|
|
417
|
+
else None
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
421
|
+
reservation_released = False
|
|
422
|
+
with anyio.CancelScope(shield=True):
|
|
423
|
+
try:
|
|
424
|
+
async with proxy._repo_factory() as repos:
|
|
425
|
+
api_keys_service = _service_api_keys_service()(repos.api_keys)
|
|
426
|
+
if response is not None and input_tokens is not None and output_tokens is not None:
|
|
427
|
+
await api_keys_service.finalize_usage_reservation(
|
|
428
|
+
reservation_id,
|
|
429
|
+
model=model_name,
|
|
430
|
+
input_tokens=input_tokens,
|
|
431
|
+
output_tokens=output_tokens,
|
|
432
|
+
cached_input_tokens=cached_input_tokens or 0,
|
|
433
|
+
service_tier=service_tier,
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
await api_keys_service.release_usage_reservation(reservation_id)
|
|
437
|
+
reservation_released = True
|
|
438
|
+
except Exception as exc:
|
|
439
|
+
logger.warning(
|
|
440
|
+
"Failed to settle compact API key reservation key_id=%s request_id=%s",
|
|
441
|
+
api_key.id,
|
|
442
|
+
get_request_id(),
|
|
443
|
+
exc_info=True,
|
|
444
|
+
)
|
|
445
|
+
try:
|
|
446
|
+
async with proxy._repo_factory() as repos:
|
|
447
|
+
api_keys_service = _service_api_keys_service()(repos.api_keys)
|
|
448
|
+
await api_keys_service.release_usage_reservation(reservation_id)
|
|
449
|
+
reservation_released = True
|
|
450
|
+
except Exception:
|
|
451
|
+
logger.warning(
|
|
452
|
+
"Failed to release compact API key reservation after settlement failure "
|
|
453
|
+
"key_id=%s request_id=%s",
|
|
454
|
+
api_key.id,
|
|
455
|
+
get_request_id(),
|
|
456
|
+
exc_info=True,
|
|
457
|
+
)
|
|
458
|
+
raise ProxyResponseError(
|
|
459
|
+
502,
|
|
460
|
+
openai_error(
|
|
461
|
+
"usage_settlement_failed",
|
|
462
|
+
"Compact API key usage could not be settled",
|
|
463
|
+
error_type="server_error",
|
|
464
|
+
),
|
|
465
|
+
failure_phase="usage_settlement",
|
|
466
|
+
failure_detail="compact_api_key_usage_persistence_failed",
|
|
467
|
+
failure_exception_type=type(exc).__name__,
|
|
468
|
+
reservation_released=reservation_released,
|
|
469
|
+
) from exc
|
|
470
|
+
finally:
|
|
471
|
+
_signal_propagated_responses_service_cleanup_ready()
|
|
472
|
+
|
|
473
|
+
async def settle_image_api_key_usage(
|
|
474
|
+
self,
|
|
475
|
+
api_key: ApiKeyData | None,
|
|
476
|
+
reservation: ApiKeyUsageReservationData | None,
|
|
477
|
+
*,
|
|
478
|
+
model: str,
|
|
479
|
+
input_tokens: int | None,
|
|
480
|
+
output_tokens: int | None,
|
|
481
|
+
cached_input_tokens: int | None,
|
|
482
|
+
request_id: str,
|
|
483
|
+
) -> bool:
|
|
484
|
+
"""Transfer captured image usage to tracked reservation settlement."""
|
|
485
|
+
has_usage = input_tokens is not None or output_tokens is not None
|
|
486
|
+
settlement = _StreamSettlement(
|
|
487
|
+
status="success" if has_usage else "failed",
|
|
488
|
+
model=model,
|
|
489
|
+
input_tokens=int(input_tokens or 0) if has_usage else None,
|
|
490
|
+
output_tokens=int(output_tokens or 0) if has_usage else None,
|
|
491
|
+
cached_input_tokens=int(cached_input_tokens or 0) if has_usage else None,
|
|
492
|
+
service_tier=None,
|
|
493
|
+
)
|
|
494
|
+
return await self._settle_stream_api_key_usage(
|
|
495
|
+
api_key,
|
|
496
|
+
reservation,
|
|
497
|
+
settlement,
|
|
498
|
+
request_id=request_id,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
async def _settle_stream_api_key_usage(
|
|
502
|
+
self,
|
|
503
|
+
api_key: ApiKeyData | None,
|
|
504
|
+
api_key_reservation: ApiKeyUsageReservationData | None,
|
|
505
|
+
settlement: _StreamSettlement,
|
|
506
|
+
request_id: str,
|
|
507
|
+
*,
|
|
508
|
+
wait_for_settlement: bool = False,
|
|
509
|
+
) -> bool:
|
|
510
|
+
"""Settle stream reservation. Returns True if settled."""
|
|
511
|
+
if api_key is None or api_key_reservation is None:
|
|
512
|
+
return True
|
|
513
|
+
|
|
514
|
+
reservation_id = api_key_reservation.reservation_id
|
|
515
|
+
model_name = api_key_reservation.model or settlement.model or ""
|
|
516
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
517
|
+
scheduler = scheduler_for(self)
|
|
518
|
+
|
|
519
|
+
async def _release_ordering_sensitive_fallback() -> bool:
|
|
520
|
+
fallback_task = scheduler.create_task(
|
|
521
|
+
self._release_unsettled_stream_api_key_usage(
|
|
522
|
+
api_key=api_key,
|
|
523
|
+
api_key_reservation=api_key_reservation,
|
|
524
|
+
request_id=request_id,
|
|
525
|
+
),
|
|
526
|
+
name=f"proxy-stream-api-key-fallback-{request_id}",
|
|
527
|
+
)
|
|
528
|
+
while not fallback_task.done():
|
|
529
|
+
try:
|
|
530
|
+
await wait_on_shared_future(fallback_task)
|
|
531
|
+
except asyncio.CancelledError:
|
|
532
|
+
# Keep waiting for the owned fallback. Cancellation is not
|
|
533
|
+
# a failed release; retry only when the fallback itself is
|
|
534
|
+
# unconfirmed.
|
|
535
|
+
continue
|
|
536
|
+
return fallback_task.result()
|
|
537
|
+
|
|
538
|
+
async def _settle_once() -> bool:
|
|
539
|
+
try:
|
|
540
|
+
async with proxy._repo_factory() as repos:
|
|
541
|
+
api_keys_service = _service_api_keys_service()(repos.api_keys)
|
|
542
|
+
if (
|
|
543
|
+
settlement.status == "success"
|
|
544
|
+
and settlement.input_tokens is not None
|
|
545
|
+
and settlement.output_tokens is not None
|
|
546
|
+
):
|
|
547
|
+
await api_keys_service.finalize_usage_reservation(
|
|
548
|
+
reservation_id,
|
|
549
|
+
model=model_name,
|
|
550
|
+
input_tokens=settlement.input_tokens,
|
|
551
|
+
output_tokens=settlement.output_tokens,
|
|
552
|
+
cached_input_tokens=settlement.cached_input_tokens or 0,
|
|
553
|
+
service_tier=settlement.service_tier,
|
|
554
|
+
)
|
|
555
|
+
else:
|
|
556
|
+
await api_keys_service.release_usage_reservation(reservation_id)
|
|
557
|
+
return True
|
|
558
|
+
except asyncio.CancelledError:
|
|
559
|
+
if wait_for_settlement:
|
|
560
|
+
return await _release_ordering_sensitive_fallback()
|
|
561
|
+
raise
|
|
562
|
+
except Exception:
|
|
563
|
+
logger.warning(
|
|
564
|
+
"Failed to settle stream API key reservation key_id=%s request_id=%s",
|
|
565
|
+
api_key.id,
|
|
566
|
+
request_id,
|
|
567
|
+
exc_info=True,
|
|
568
|
+
)
|
|
569
|
+
if wait_for_settlement:
|
|
570
|
+
return await _release_ordering_sensitive_fallback()
|
|
571
|
+
return False
|
|
572
|
+
|
|
573
|
+
# Detach unconditionally instead of shield-awaiting: for ordinary
|
|
574
|
+
# callers the tracking callback schedules a release when settlement
|
|
575
|
+
# fails or is cancelled; an ordering-sensitive settlement task runs
|
|
576
|
+
# that fallback before the tracked task completes once started, while
|
|
577
|
+
# the tracker still owns cancellation before coroutine startup. The
|
|
578
|
+
# caller's finally-net skips via
|
|
579
|
+
# usage_settlement_transferred, and reservations keep counting toward
|
|
580
|
+
# limits until finalized/released, so a briefly-lagging settlement can
|
|
581
|
+
# only over-restrict, never over-admit. Awaiting the ~5+2N-statement
|
|
582
|
+
# settlement transaction here made every keyed stream's close wait on
|
|
583
|
+
# it. Shutdown drains the task set (drain_persistence_tasks).
|
|
584
|
+
task = scheduler.create_task(_settle_once(), name=f"proxy-stream-api-key-settle-{request_id}")
|
|
585
|
+
settlement.usage_settlement_transferred = True
|
|
586
|
+
self._track_stream_usage_settlement_task(
|
|
587
|
+
task,
|
|
588
|
+
api_key=api_key,
|
|
589
|
+
api_key_reservation=api_key_reservation,
|
|
590
|
+
request_id=request_id,
|
|
591
|
+
# Ordering-sensitive settlement performs one immediate fallback,
|
|
592
|
+
# but the tracker must still own retrying release if both attempts
|
|
593
|
+
# fail after ownership has transferred from the request finalizer.
|
|
594
|
+
release_on_failure=True,
|
|
595
|
+
)
|
|
596
|
+
if wait_for_settlement:
|
|
597
|
+
# Ordering-sensitive callers (websocket account-health paths) must
|
|
598
|
+
# commit the settlement before load-balancer health writes; they
|
|
599
|
+
# opt into waiting while everything else stays detached.
|
|
600
|
+
settlement_committed = False
|
|
601
|
+
with anyio.CancelScope(shield=True):
|
|
602
|
+
while True:
|
|
603
|
+
try:
|
|
604
|
+
settlement_committed = await wait_on_shared_future(task)
|
|
605
|
+
break
|
|
606
|
+
except asyncio.CancelledError:
|
|
607
|
+
# Caller cancellation must not race fallback release
|
|
608
|
+
# against the still-running settlement transaction.
|
|
609
|
+
if task.cancelled():
|
|
610
|
+
break
|
|
611
|
+
except Exception:
|
|
612
|
+
break
|
|
613
|
+
return settlement_committed
|
|
614
|
+
return True
|
|
615
|
+
|
|
616
|
+
def _track_stream_usage_settlement_task(
|
|
617
|
+
self,
|
|
618
|
+
task: asyncio.Task[bool],
|
|
619
|
+
*,
|
|
620
|
+
api_key: ApiKeyData,
|
|
621
|
+
api_key_reservation: ApiKeyUsageReservationData,
|
|
622
|
+
request_id: str,
|
|
623
|
+
release_on_failure: bool = True,
|
|
624
|
+
) -> None:
|
|
625
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
626
|
+
proxy._background_cleanup_tasks.add(cast(asyncio.Task[None], task))
|
|
627
|
+
|
|
628
|
+
async def _release_after_failed_settlement() -> None:
|
|
629
|
+
await self._release_unsettled_stream_api_key_usage(
|
|
630
|
+
api_key=api_key,
|
|
631
|
+
api_key_reservation=api_key_reservation,
|
|
632
|
+
request_id=request_id,
|
|
633
|
+
retry_persistence_failures=True,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
def _settlement_done(done_task: asyncio.Task[bool]) -> None:
|
|
637
|
+
proxy._background_cleanup_tasks.discard(cast(asyncio.Task[None], done_task))
|
|
638
|
+
try:
|
|
639
|
+
settled = done_task.result()
|
|
640
|
+
except asyncio.CancelledError:
|
|
641
|
+
logger.warning(
|
|
642
|
+
"Stream API key settlement task cancelled key_id=%s request_id=%s",
|
|
643
|
+
api_key.id,
|
|
644
|
+
request_id,
|
|
645
|
+
)
|
|
646
|
+
self._schedule_cancel_safe_cleanup(
|
|
647
|
+
_release_after_failed_settlement(),
|
|
648
|
+
action="release_stream_api_key_reservation_after_cancelled_settlement",
|
|
649
|
+
request_id=request_id,
|
|
650
|
+
)
|
|
651
|
+
except Exception as exc:
|
|
652
|
+
logger.warning(
|
|
653
|
+
"Stream API key settlement task failed key_id=%s request_id=%s",
|
|
654
|
+
api_key.id,
|
|
655
|
+
request_id,
|
|
656
|
+
exc_info=(type(exc), exc, exc.__traceback__),
|
|
657
|
+
)
|
|
658
|
+
else:
|
|
659
|
+
if not settled and release_on_failure:
|
|
660
|
+
self._schedule_cancel_safe_cleanup(
|
|
661
|
+
_release_after_failed_settlement(),
|
|
662
|
+
action="release_stream_api_key_reservation_after_failed_settlement",
|
|
663
|
+
request_id=request_id,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
task.add_done_callback(_settlement_done)
|
|
667
|
+
|
|
668
|
+
def _schedule_cancel_safe_cleanup(
|
|
669
|
+
self,
|
|
670
|
+
coro: Coroutine[Any, Any, None],
|
|
671
|
+
*,
|
|
672
|
+
action: str,
|
|
673
|
+
request_id: str,
|
|
674
|
+
) -> None:
|
|
675
|
+
task = scheduler_for(self).create_task(coro, name=f"proxy-{action}-{request_id}")
|
|
676
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
677
|
+
proxy._background_cleanup_tasks.add(task)
|
|
678
|
+
|
|
679
|
+
def _cleanup_done(done_task: asyncio.Task[None]) -> None:
|
|
680
|
+
proxy._background_cleanup_tasks.discard(done_task)
|
|
681
|
+
try:
|
|
682
|
+
done_task.result()
|
|
683
|
+
except asyncio.CancelledError:
|
|
684
|
+
logger.warning("%s cleanup task cancelled request_id=%s", action, request_id)
|
|
685
|
+
except Exception as exc:
|
|
686
|
+
logger.warning(
|
|
687
|
+
"%s cleanup task failed request_id=%s",
|
|
688
|
+
action,
|
|
689
|
+
request_id,
|
|
690
|
+
exc_info=(type(exc), exc, exc.__traceback__),
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
task.add_done_callback(_cleanup_done)
|
|
694
|
+
|
|
695
|
+
async def _release_unsettled_stream_api_key_usage(
|
|
696
|
+
self,
|
|
697
|
+
*,
|
|
698
|
+
api_key: ApiKeyData,
|
|
699
|
+
api_key_reservation: ApiKeyUsageReservationData,
|
|
700
|
+
request_id: str,
|
|
701
|
+
retry_persistence_failures: bool = False,
|
|
702
|
+
) -> bool:
|
|
703
|
+
proxy = cast(_ApiKeyUsageServiceProtocol, self)
|
|
704
|
+
retry_attempt = 1
|
|
705
|
+
retry_delay_seconds = _STREAM_API_KEY_RELEASE_RETRY_BASE_SECONDS
|
|
706
|
+
while True:
|
|
707
|
+
retry_slot_acquired = False
|
|
708
|
+
try:
|
|
709
|
+
if retry_persistence_failures:
|
|
710
|
+
await proxy._stream_api_key_release_retry_semaphore.acquire()
|
|
711
|
+
retry_slot_acquired = True
|
|
712
|
+
with anyio.CancelScope(shield=True):
|
|
713
|
+
async with proxy._repo_factory() as repos:
|
|
714
|
+
api_keys_service = _service_api_keys_service()(repos.api_keys)
|
|
715
|
+
await api_keys_service.release_usage_reservation(
|
|
716
|
+
api_key_reservation.reservation_id,
|
|
717
|
+
)
|
|
718
|
+
return True
|
|
719
|
+
except Exception:
|
|
720
|
+
if not retry_persistence_failures:
|
|
721
|
+
logger.warning(
|
|
722
|
+
"Failed to release stream API key reservation key_id=%s request_id=%s",
|
|
723
|
+
api_key.id,
|
|
724
|
+
request_id,
|
|
725
|
+
exc_info=True,
|
|
726
|
+
)
|
|
727
|
+
return False
|
|
728
|
+
logger.warning(
|
|
729
|
+
"Failed to release stream API key reservation key_id=%s request_id=%s "
|
|
730
|
+
"retry_attempt=%d retry_delay_seconds=%.2f",
|
|
731
|
+
api_key.id,
|
|
732
|
+
request_id,
|
|
733
|
+
retry_attempt,
|
|
734
|
+
retry_delay_seconds,
|
|
735
|
+
exc_info=True,
|
|
736
|
+
)
|
|
737
|
+
finally:
|
|
738
|
+
if retry_slot_acquired:
|
|
739
|
+
proxy._stream_api_key_release_retry_semaphore.release()
|
|
740
|
+
await scheduler_for(self).sleep(retry_delay_seconds)
|
|
741
|
+
retry_attempt += 1
|
|
742
|
+
retry_delay_seconds = min(
|
|
743
|
+
_STREAM_API_KEY_RELEASE_RETRY_MAX_SECONDS,
|
|
744
|
+
retry_delay_seconds * 2,
|
|
745
|
+
)
|