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.
Files changed (827) hide show
  1. app/__init__.py +10 -0
  2. app/admin_cli.py +494 -0
  3. app/cli.py +339 -0
  4. app/codex_sessions_retag.py +487 -0
  5. app/core/__init__.py +0 -0
  6. app/core/audit/__init__.py +0 -0
  7. app/core/audit/service.py +216 -0
  8. app/core/audit/types.py +94 -0
  9. app/core/auth/__init__.py +236 -0
  10. app/core/auth/api_key_cache.py +65 -0
  11. app/core/auth/dashboard_access.py +429 -0
  12. app/core/auth/dashboard_mode.py +171 -0
  13. app/core/auth/dashboard_session_ttl.py +74 -0
  14. app/core/auth/dashboard_users_cache.py +120 -0
  15. app/core/auth/dependencies.py +710 -0
  16. app/core/auth/external_identity.py +78 -0
  17. app/core/auth/guardian.py +374 -0
  18. app/core/auth/models.py +49 -0
  19. app/core/auth/providers/__init__.py +129 -0
  20. app/core/auth/providers/oidc.py +838 -0
  21. app/core/auth/providers/registry.py +118 -0
  22. app/core/auth/refresh.py +394 -0
  23. app/core/auth/step_up.py +117 -0
  24. app/core/auth/totp.py +81 -0
  25. app/core/balancer/__init__.py +71 -0
  26. app/core/balancer/logic.py +1612 -0
  27. app/core/balancer/rendezvous_hash.py +21 -0
  28. app/core/balancer/types.py +21 -0
  29. app/core/bootstrap.py +172 -0
  30. app/core/cache/invalidation.py +426 -0
  31. app/core/clients/__init__.py +0 -0
  32. app/core/clients/codex.py +996 -0
  33. app/core/clients/codex_version.py +187 -0
  34. app/core/clients/codex_version_snapshot.py +2 -0
  35. app/core/clients/files.py +534 -0
  36. app/core/clients/headers.py +29 -0
  37. app/core/clients/http.py +511 -0
  38. app/core/clients/model_fetcher.py +276 -0
  39. app/core/clients/native_egress.py +1304 -0
  40. app/core/clients/oauth.py +477 -0
  41. app/core/clients/proxy.py +6089 -0
  42. app/core/clients/proxy_websocket.py +1411 -0
  43. app/core/clients/rate_limit_reset_credits.py +412 -0
  44. app/core/clients/stream_errors.py +12 -0
  45. app/core/clients/thread_cache_identity.py +347 -0
  46. app/core/clients/usage.py +467 -0
  47. app/core/clock.py +170 -0
  48. app/core/config/__init__.py +0 -0
  49. app/core/config/background_jobs.py +73 -0
  50. app/core/config/context_window_overrides.py +166 -0
  51. app/core/config/dashboard_overrides.py +145 -0
  52. app/core/config/inheritable.py +47 -0
  53. app/core/config/key_fingerprint.py +115 -0
  54. app/core/config/settings.py +872 -0
  55. app/core/config/settings_cache.py +100 -0
  56. app/core/config/spool_retention.py +217 -0
  57. app/core/config/tiers.py +174 -0
  58. app/core/conversation_archive.py +568 -0
  59. app/core/crypto.py +37 -0
  60. app/core/draining.py +11 -0
  61. app/core/errors.py +365 -0
  62. app/core/exceptions.py +155 -0
  63. app/core/handlers/__init__.py +3 -0
  64. app/core/handlers/exceptions.py +478 -0
  65. app/core/http_protocol.py +176 -0
  66. app/core/http_protocol_httptools.py +204 -0
  67. app/core/ingress_limits.py +17 -0
  68. app/core/metadata_store.py +18 -0
  69. app/core/metrics/__init__.py +0 -0
  70. app/core/metrics/middleware.py +74 -0
  71. app/core/metrics/prometheus.py +604 -0
  72. app/core/middleware/__init__.py +25 -0
  73. app/core/middleware/api_firewall.py +111 -0
  74. app/core/middleware/app_version.py +37 -0
  75. app/core/middleware/dashboard_auth_proxy.py +55 -0
  76. app/core/middleware/dashboard_csrf.py +177 -0
  77. app/core/middleware/dashboard_gzip.py +41 -0
  78. app/core/middleware/dashboard_overrides.py +59 -0
  79. app/core/middleware/firewall_cache.py +91 -0
  80. app/core/middleware/inflight.py +126 -0
  81. app/core/middleware/multipart_content_encoding.py +129 -0
  82. app/core/middleware/path_rewrite.py +148 -0
  83. app/core/middleware/request_body_limit.py +196 -0
  84. app/core/middleware/request_decompression.py +209 -0
  85. app/core/middleware/request_id.py +72 -0
  86. app/core/middleware/required_capability_http.py +116 -0
  87. app/core/middleware/trusted_proxy_headers.py +36 -0
  88. app/core/multipart.py +237 -0
  89. app/core/multipart_fields.py +91 -0
  90. app/core/openai/__init__.py +0 -0
  91. app/core/openai/chat_requests.py +493 -0
  92. app/core/openai/chat_responses.py +931 -0
  93. app/core/openai/contracts.py +46 -0
  94. app/core/openai/exceptions.py +24 -0
  95. app/core/openai/host_models.py +14 -0
  96. app/core/openai/images.py +413 -0
  97. app/core/openai/message_coercion.py +376 -0
  98. app/core/openai/model_refresh_scheduler.py +423 -0
  99. app/core/openai/model_registry.py +997 -0
  100. app/core/openai/model_registry_store.py +548 -0
  101. app/core/openai/models.py +172 -0
  102. app/core/openai/parsing.py +97 -0
  103. app/core/openai/requests.py +1980 -0
  104. app/core/openai/strict_schema.py +199 -0
  105. app/core/openai/tool_call_safety.py +99 -0
  106. app/core/openai/v1_requests.py +187 -0
  107. app/core/plan_types.py +90 -0
  108. app/core/prestop.py +202 -0
  109. app/core/rate_limiter/__init__.py +0 -0
  110. app/core/rate_limiter/db_rate_limiter.py +215 -0
  111. app/core/request_locality.py +473 -0
  112. app/core/resilience/__init__.py +0 -0
  113. app/core/resilience/backpressure.py +57 -0
  114. app/core/resilience/bulkhead.py +202 -0
  115. app/core/resilience/circuit_breaker.py +150 -0
  116. app/core/resilience/degradation.py +55 -0
  117. app/core/resilience/loop_lag_monitor.py +60 -0
  118. app/core/resilience/memory_monitor.py +137 -0
  119. app/core/resilience/network_recovery.py +256 -0
  120. app/core/resilience/overload.py +98 -0
  121. app/core/resilience/toggles.py +114 -0
  122. app/core/retention/__init__.py +0 -0
  123. app/core/retention/job.py +240 -0
  124. app/core/retention/scheduler.py +69 -0
  125. app/core/runtime_logging.py +601 -0
  126. app/core/scheduling/__init__.py +0 -0
  127. app/core/scheduling/leader_election.py +993 -0
  128. app/core/scheduling/leader_election_handle.py +25 -0
  129. app/core/server.py +140 -0
  130. app/core/shutdown.py +326 -0
  131. app/core/socket_peer.py +22 -0
  132. app/core/startup.py +41 -0
  133. app/core/timeout_invariants.py +347 -0
  134. app/core/tracing/__init__.py +0 -0
  135. app/core/tracing/otel.py +207 -0
  136. app/core/types.py +6 -0
  137. app/core/upstream_proxy/__init__.py +13 -0
  138. app/core/upstream_proxy/cache.py +130 -0
  139. app/core/upstream_proxy/resolver.py +189 -0
  140. app/core/upstream_proxy/types.py +104 -0
  141. app/core/usage/__init__.py +407 -0
  142. app/core/usage/depletion.py +109 -0
  143. app/core/usage/live_hub.py +41 -0
  144. app/core/usage/live_snapshots.py +174 -0
  145. app/core/usage/logs.py +212 -0
  146. app/core/usage/metadata_scheduler.py +118 -0
  147. app/core/usage/models.py +54 -0
  148. app/core/usage/pricing.py +576 -0
  149. app/core/usage/pricing_catalog.py +241 -0
  150. app/core/usage/pricing_snapshot.json +821 -0
  151. app/core/usage/quota.py +133 -0
  152. app/core/usage/refresh_policy.py +21 -0
  153. app/core/usage/refresh_scheduler.py +713 -0
  154. app/core/usage/reset_credits_refresh_scheduler.py +435 -0
  155. app/core/usage/types.py +168 -0
  156. app/core/utils/__init__.py +0 -0
  157. app/core/utils/json_guards.py +18 -0
  158. app/core/utils/locks.py +33 -0
  159. app/core/utils/masking.py +20 -0
  160. app/core/utils/proxy_env.py +104 -0
  161. app/core/utils/request_id.py +65 -0
  162. app/core/utils/retry.py +77 -0
  163. app/core/utils/shared_future.py +172 -0
  164. app/core/utils/sse.py +286 -0
  165. app/core/utils/time.py +24 -0
  166. app/db/__init__.py +0 -0
  167. app/db/account_identity_lock.py +56 -0
  168. app/db/alembic/__init__.py +1 -0
  169. app/db/alembic/env.py +70 -0
  170. app/db/alembic/revision_ids.py +43 -0
  171. app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
  172. app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
  173. app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
  174. app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
  175. app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
  176. app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
  177. app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
  178. app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
  179. app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
  180. app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
  181. app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
  182. app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
  183. app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
  184. app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
  185. app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
  186. app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
  187. app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
  188. app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
  189. app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
  190. app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
  191. app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
  192. app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
  193. app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
  194. app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
  195. app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
  196. app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
  197. app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
  198. app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
  199. app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
  200. app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
  201. app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
  202. app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
  203. app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
  204. app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
  205. app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
  206. app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
  207. app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
  208. app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
  209. app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
  210. app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
  211. app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
  212. app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
  213. app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
  214. app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
  215. app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
  216. app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
  217. app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
  218. app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
  219. app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
  220. app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
  221. app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
  222. app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
  223. app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
  224. app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
  225. app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
  226. app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
  227. app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
  228. app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
  229. app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
  230. app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
  231. app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
  232. app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
  233. app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
  234. app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
  235. app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
  236. app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
  237. app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
  238. app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
  239. app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
  240. app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
  241. app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
  242. app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
  243. app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
  244. app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
  245. app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
  246. app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
  247. app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
  248. app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
  249. app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
  250. app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
  251. app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
  252. app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
  253. app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
  254. app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
  255. app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
  256. app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
  257. app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
  258. app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
  259. app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
  260. app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
  261. app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
  262. app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
  263. app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
  264. app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
  265. app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
  266. app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
  267. app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
  268. app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
  269. app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
  270. app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
  271. app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
  272. app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
  273. app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
  274. app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
  275. app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
  276. app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
  277. app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
  278. app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
  279. app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
  280. app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
  281. app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
  282. app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
  283. app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
  284. app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
  285. app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
  286. app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
  287. app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
  288. app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
  289. app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
  290. app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
  291. app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
  292. app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
  293. app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
  294. app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
  295. app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
  296. app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
  297. app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
  298. app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
  299. app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
  300. app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
  301. app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
  302. app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
  303. app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
  304. app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
  305. app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
  306. app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
  307. app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
  308. app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
  309. app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
  310. app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
  311. app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
  312. app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
  313. app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
  314. app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
  315. app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
  316. app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
  317. app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
  318. app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
  319. app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
  320. app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
  321. app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
  322. app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
  323. app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
  324. app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
  325. app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
  326. app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
  327. app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
  328. app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
  329. app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
  330. app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
  331. app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
  332. app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
  333. app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
  334. app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
  335. app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
  336. app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
  337. app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
  338. app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
  339. app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
  340. app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
  341. app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
  342. app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
  343. app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
  344. app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
  345. app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
  346. app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
  347. app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
  348. app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
  349. app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
  350. app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
  351. app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
  352. app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
  353. app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
  354. app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
  355. app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
  356. app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
  357. app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
  358. app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
  359. app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
  360. app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
  361. app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
  362. app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
  363. app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
  364. app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
  365. app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
  366. app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
  367. app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
  368. app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
  369. app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
  370. app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
  371. app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
  372. app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
  373. app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
  374. app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
  375. app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
  376. app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
  377. app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
  378. app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
  379. app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
  380. app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
  381. app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
  382. app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
  383. app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
  384. app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
  385. app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
  386. app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
  387. app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
  388. app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
  389. app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
  390. app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
  391. app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
  392. app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
  393. app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
  394. app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
  395. app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
  396. app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
  397. app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
  398. app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
  399. app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
  400. app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
  401. app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
  402. app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
  403. app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
  404. app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
  405. app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
  406. app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
  407. app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
  408. app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
  409. app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
  410. app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
  411. app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
  412. app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
  413. app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
  414. app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
  415. app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
  416. app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
  417. app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
  418. app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
  419. app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
  420. app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
  421. app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
  422. app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
  423. app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
  424. app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
  425. app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
  426. app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
  427. app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
  428. app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
  429. app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
  430. app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
  431. app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
  432. app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
  433. app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
  434. app/db/alembic/versions/__init__.py +1 -0
  435. app/db/backup.py +64 -0
  436. app/db/migrate.py +1249 -0
  437. app/db/migration_lock.py +160 -0
  438. app/db/migration_url.py +18 -0
  439. app/db/models.py +3100 -0
  440. app/db/recover.py +244 -0
  441. app/db/session.py +1295 -0
  442. app/db/snapshot.py +46 -0
  443. app/db/sqlite_lock_retry.py +190 -0
  444. app/db/sqlite_utils.py +549 -0
  445. app/dependencies.py +462 -0
  446. app/main.py +1240 -0
  447. app/modules/__init__.py +0 -0
  448. app/modules/accounts/__init__.py +0 -0
  449. app/modules/accounts/api.py +426 -0
  450. app/modules/accounts/auth_manager.py +1439 -0
  451. app/modules/accounts/background_repository.py +160 -0
  452. app/modules/accounts/deletion.py +540 -0
  453. app/modules/accounts/mappers.py +654 -0
  454. app/modules/accounts/refresh_claims.py +352 -0
  455. app/modules/accounts/repository.py +1679 -0
  456. app/modules/accounts/schemas.py +308 -0
  457. app/modules/accounts/service.py +809 -0
  458. app/modules/accounts/usage_rollup.py +486 -0
  459. app/modules/accounts/usage_rollup_scheduler.py +75 -0
  460. app/modules/accounts/usage_time_rollup.py +1037 -0
  461. app/modules/accounts/usage_time_rollup_read.py +522 -0
  462. app/modules/api_keys/__init__.py +1 -0
  463. app/modules/api_keys/api.py +344 -0
  464. app/modules/api_keys/last_used_coalescer.py +216 -0
  465. app/modules/api_keys/limit_windows.py +41 -0
  466. app/modules/api_keys/repository.py +1200 -0
  467. app/modules/api_keys/reset_scheduler.py +85 -0
  468. app/modules/api_keys/schemas.py +135 -0
  469. app/modules/api_keys/service.py +2123 -0
  470. app/modules/audit/__init__.py +0 -0
  471. app/modules/audit/api.py +88 -0
  472. app/modules/audit/repository.py +74 -0
  473. app/modules/audit/schemas.py +33 -0
  474. app/modules/audit/service.py +71 -0
  475. app/modules/auth_providers/__init__.py +0 -0
  476. app/modules/auth_providers/api.py +127 -0
  477. app/modules/auth_providers/config.py +343 -0
  478. app/modules/auth_providers/repository.py +101 -0
  479. app/modules/auth_providers/schemas.py +88 -0
  480. app/modules/auth_providers/seed.py +105 -0
  481. app/modules/auth_providers/service.py +289 -0
  482. app/modules/automations/__init__.py +1 -0
  483. app/modules/automations/api.py +369 -0
  484. app/modules/automations/repository.py +1896 -0
  485. app/modules/automations/scheduler.py +107 -0
  486. app/modules/automations/schemas.py +158 -0
  487. app/modules/automations/service.py +2548 -0
  488. app/modules/cache_isolation_probe/__init__.py +0 -0
  489. app/modules/cache_isolation_probe/api.py +188 -0
  490. app/modules/cache_isolation_probe/prefix.py +144 -0
  491. app/modules/cache_isolation_probe/schemas.py +94 -0
  492. app/modules/cache_isolation_probe/sender.py +229 -0
  493. app/modules/cache_isolation_probe/service.py +525 -0
  494. app/modules/conversation_archive/__init__.py +1 -0
  495. app/modules/conversation_archive/api.py +101 -0
  496. app/modules/conversation_archive/schemas.py +28 -0
  497. app/modules/conversation_archive/service.py +165 -0
  498. app/modules/dashboard/__init__.py +1 -0
  499. app/modules/dashboard/api.py +106 -0
  500. app/modules/dashboard/builders.py +107 -0
  501. app/modules/dashboard/repository.py +304 -0
  502. app/modules/dashboard/schemas.py +148 -0
  503. app/modules/dashboard/service.py +534 -0
  504. app/modules/dashboard/timeframes.py +19 -0
  505. app/modules/dashboard/weekly_pace.py +617 -0
  506. app/modules/dashboard_auth/__init__.py +1 -0
  507. app/modules/dashboard_auth/api.py +1344 -0
  508. app/modules/dashboard_auth/oidc_api.py +753 -0
  509. app/modules/dashboard_auth/oidc_flows.py +330 -0
  510. app/modules/dashboard_auth/repository.py +452 -0
  511. app/modules/dashboard_auth/schemas.py +232 -0
  512. app/modules/dashboard_auth/service.py +1384 -0
  513. app/modules/dashboard_roles/__init__.py +0 -0
  514. app/modules/dashboard_roles/api.py +64 -0
  515. app/modules/dashboard_roles/repository.py +54 -0
  516. app/modules/dashboard_roles/schemas.py +29 -0
  517. app/modules/dashboard_roles/seed.py +80 -0
  518. app/modules/dashboard_roles/service.py +142 -0
  519. app/modules/dashboard_users/__init__.py +0 -0
  520. app/modules/dashboard_users/api.py +278 -0
  521. app/modules/dashboard_users/break_glass.py +169 -0
  522. app/modules/dashboard_users/credentials.py +18 -0
  523. app/modules/dashboard_users/identity_resolver.py +665 -0
  524. app/modules/dashboard_users/repository.py +725 -0
  525. app/modules/dashboard_users/schemas.py +117 -0
  526. app/modules/dashboard_users/service.py +1054 -0
  527. app/modules/firewall/__init__.py +1 -0
  528. app/modules/firewall/api.py +78 -0
  529. app/modules/firewall/repository.py +51 -0
  530. app/modules/firewall/schemas.py +28 -0
  531. app/modules/firewall/service.py +93 -0
  532. app/modules/fleet/__init__.py +1 -0
  533. app/modules/fleet/api.py +177 -0
  534. app/modules/fleet/mappers.py +56 -0
  535. app/modules/fleet/observability.py +372 -0
  536. app/modules/fleet/schemas.py +119 -0
  537. app/modules/health/__init__.py +0 -0
  538. app/modules/health/api.py +246 -0
  539. app/modules/health/schemas.py +27 -0
  540. app/modules/limit_warmup/__init__.py +1 -0
  541. app/modules/limit_warmup/repository.py +159 -0
  542. app/modules/limit_warmup/service.py +1001 -0
  543. app/modules/model_sources/__init__.py +1 -0
  544. app/modules/model_sources/api.py +99 -0
  545. app/modules/model_sources/catalog.py +307 -0
  546. app/modules/model_sources/forwarding.py +1435 -0
  547. app/modules/model_sources/projection.py +55 -0
  548. app/modules/model_sources/repository.py +201 -0
  549. app/modules/model_sources/schemas.py +79 -0
  550. app/modules/model_sources/selection.py +166 -0
  551. app/modules/model_sources/service.py +236 -0
  552. app/modules/oauth/__init__.py +0 -0
  553. app/modules/oauth/api.py +101 -0
  554. app/modules/oauth/repository.py +238 -0
  555. app/modules/oauth/schemas.py +49 -0
  556. app/modules/oauth/service.py +1196 -0
  557. app/modules/oauth/templates/oauth_success.html +122 -0
  558. app/modules/proxy/__init__.py +0 -0
  559. app/modules/proxy/_load_balancer/__init__.py +1 -0
  560. app/modules/proxy/_load_balancer/error_rate.py +116 -0
  561. app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
  562. app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
  563. app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
  564. app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
  565. app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
  566. app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
  567. app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
  568. app/modules/proxy/_load_balancer/tunables.py +97 -0
  569. app/modules/proxy/_load_balancer/types.py +96 -0
  570. app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
  571. app/modules/proxy/_service/__init__.py +6 -0
  572. app/modules/proxy/_service/api_key_usage.py +745 -0
  573. app/modules/proxy/_service/clock_budget.py +38 -0
  574. app/modules/proxy/_service/codex_control.py +611 -0
  575. app/modules/proxy/_service/compact.py +2211 -0
  576. app/modules/proxy/_service/file_ops.py +749 -0
  577. app/modules/proxy/_service/http_bridge/__init__.py +3 -0
  578. app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
  579. app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
  580. app/modules/proxy/_service/http_bridge/activity.py +178 -0
  581. app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
  582. app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
  583. app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
  584. app/modules/proxy/_service/http_bridge/protocol.py +167 -0
  585. app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
  586. app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
  587. app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
  588. app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
  589. app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
  590. app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
  591. app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
  592. app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
  593. app/modules/proxy/_service/observability.py +410 -0
  594. app/modules/proxy/_service/rate_limit.py +361 -0
  595. app/modules/proxy/_service/realtime_live.py +655 -0
  596. app/modules/proxy/_service/refresh.py +154 -0
  597. app/modules/proxy/_service/request_log.py +585 -0
  598. app/modules/proxy/_service/response_create.py +930 -0
  599. app/modules/proxy/_service/streaming/__init__.py +3 -0
  600. app/modules/proxy/_service/streaming/helpers.py +1317 -0
  601. app/modules/proxy/_service/streaming/mixin.py +1100 -0
  602. app/modules/proxy/_service/streaming/protocol.py +32 -0
  603. app/modules/proxy/_service/streaming/retry.py +3762 -0
  604. app/modules/proxy/_service/support.py +2122 -0
  605. app/modules/proxy/_service/transcribe.py +434 -0
  606. app/modules/proxy/_service/warmup.py +483 -0
  607. app/modules/proxy/_service/websocket/__init__.py +3 -0
  608. app/modules/proxy/_service/websocket/helpers.py +2360 -0
  609. app/modules/proxy/_service/websocket/mixin.py +7285 -0
  610. app/modules/proxy/_service/websocket/protocol.py +74 -0
  611. app/modules/proxy/account_cache.py +229 -0
  612. app/modules/proxy/account_eligibility.py +87 -0
  613. app/modules/proxy/additional_model_limits.py +40 -0
  614. app/modules/proxy/affinity.py +830 -0
  615. app/modules/proxy/affinity_observation.py +101 -0
  616. app/modules/proxy/api.py +10254 -0
  617. app/modules/proxy/api_key_usage.py +113 -0
  618. app/modules/proxy/cap_partitioning.py +280 -0
  619. app/modules/proxy/capability_lineage.py +65 -0
  620. app/modules/proxy/capability_lineage_repository.py +82 -0
  621. app/modules/proxy/capability_routing.py +253 -0
  622. app/modules/proxy/continuity.py +112 -0
  623. app/modules/proxy/downstream_delivery.py +277 -0
  624. app/modules/proxy/durable_bridge_coordinator.py +1217 -0
  625. app/modules/proxy/durable_bridge_repository.py +4516 -0
  626. app/modules/proxy/durable_bridge_runtime.py +9 -0
  627. app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
  628. app/modules/proxy/fair_share.py +131 -0
  629. app/modules/proxy/file_pin_repository.py +210 -0
  630. app/modules/proxy/helpers.py +439 -0
  631. app/modules/proxy/http_bridge_event_batcher.py +613 -0
  632. app/modules/proxy/http_bridge_forwarding.py +797 -0
  633. app/modules/proxy/http_continuation.py +76 -0
  634. app/modules/proxy/images_observability.py +62 -0
  635. app/modules/proxy/images_service.py +1054 -0
  636. app/modules/proxy/load_balancer.py +3016 -0
  637. app/modules/proxy/rate_limit_cache.py +46 -0
  638. app/modules/proxy/replay_safety.py +1063 -0
  639. app/modules/proxy/repo_bundle.py +31 -0
  640. app/modules/proxy/request_policy.py +1054 -0
  641. app/modules/proxy/ring_membership.py +251 -0
  642. app/modules/proxy/schemas.py +345 -0
  643. app/modules/proxy/selection_errors.py +41 -0
  644. app/modules/proxy/service.py +2600 -0
  645. app/modules/proxy/source_admission.py +125 -0
  646. app/modules/proxy/source_dispatch.py +999 -0
  647. app/modules/proxy/sticky_repository.py +831 -0
  648. app/modules/proxy/thread_anchors.py +491 -0
  649. app/modules/proxy/tool_call_dedupe.py +802 -0
  650. app/modules/proxy/types.py +54 -0
  651. app/modules/proxy/work_admission.py +121 -0
  652. app/modules/quota_planner/__init__.py +1 -0
  653. app/modules/quota_planner/api.py +276 -0
  654. app/modules/quota_planner/logic.py +743 -0
  655. app/modules/quota_planner/repository.py +722 -0
  656. app/modules/quota_planner/scheduler.py +212 -0
  657. app/modules/quota_planner/schemas.py +102 -0
  658. app/modules/quota_planner/warmup.py +693 -0
  659. app/modules/rate_limit_reset_credits/__init__.py +0 -0
  660. app/modules/rate_limit_reset_credits/api.py +601 -0
  661. app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
  662. app/modules/rate_limit_reset_credits/store.py +114 -0
  663. app/modules/reports/__init__.py +1 -0
  664. app/modules/reports/api.py +134 -0
  665. app/modules/reports/cache.py +74 -0
  666. app/modules/reports/filters.py +27 -0
  667. app/modules/reports/repository.py +525 -0
  668. app/modules/reports/rollup.py +150 -0
  669. app/modules/reports/rollup_read.py +148 -0
  670. app/modules/reports/schemas.py +123 -0
  671. app/modules/reports/service.py +287 -0
  672. app/modules/reports/thread_identity.py +256 -0
  673. app/modules/request_logs/__init__.py +0 -0
  674. app/modules/request_logs/api.py +224 -0
  675. app/modules/request_logs/cost_backfill.py +154 -0
  676. app/modules/request_logs/mappers.py +97 -0
  677. app/modules/request_logs/repository.py +1765 -0
  678. app/modules/request_logs/schemas.py +145 -0
  679. app/modules/request_logs/service.py +348 -0
  680. app/modules/role_mappings/__init__.py +0 -0
  681. app/modules/role_mappings/api.py +206 -0
  682. app/modules/role_mappings/matching.py +55 -0
  683. app/modules/role_mappings/repository.py +106 -0
  684. app/modules/role_mappings/schemas.py +67 -0
  685. app/modules/role_mappings/service.py +334 -0
  686. app/modules/runtime/__init__.py +1 -0
  687. app/modules/runtime/api.py +18 -0
  688. app/modules/runtime/schemas.py +14 -0
  689. app/modules/runtime/service.py +218 -0
  690. app/modules/scim/__init__.py +1 -0
  691. app/modules/scim/api.py +194 -0
  692. app/modules/scim/dependencies.py +112 -0
  693. app/modules/scim/errors.py +77 -0
  694. app/modules/scim/management_api.py +156 -0
  695. app/modules/scim/repository.py +212 -0
  696. app/modules/scim/schemas.py +145 -0
  697. app/modules/scim/service.py +786 -0
  698. app/modules/scim/tokens.py +43 -0
  699. app/modules/settings/__init__.py +1 -0
  700. app/modules/settings/api.py +1677 -0
  701. app/modules/settings/repository.py +606 -0
  702. app/modules/settings/schemas.py +496 -0
  703. app/modules/settings/service.py +769 -0
  704. app/modules/shared/__init__.py +0 -0
  705. app/modules/shared/schemas.py +22 -0
  706. app/modules/sticky_sessions/__init__.py +1 -0
  707. app/modules/sticky_sessions/api.py +118 -0
  708. app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
  709. app/modules/sticky_sessions/schemas.py +78 -0
  710. app/modules/sticky_sessions/service.py +196 -0
  711. app/modules/telemetry/__init__.py +5 -0
  712. app/modules/telemetry/api.py +115 -0
  713. app/modules/telemetry/clients.py +62 -0
  714. app/modules/telemetry/consent.py +115 -0
  715. app/modules/telemetry/scheduler.py +85 -0
  716. app/modules/telemetry/schemas.py +163 -0
  717. app/modules/telemetry/sender.py +221 -0
  718. app/modules/telemetry/snapshot.py +490 -0
  719. app/modules/usage/__init__.py +0 -0
  720. app/modules/usage/additional_quota_keys.py +379 -0
  721. app/modules/usage/api.py +45 -0
  722. app/modules/usage/background_repository.py +147 -0
  723. app/modules/usage/builders.py +559 -0
  724. app/modules/usage/depletion_service.py +277 -0
  725. app/modules/usage/live_ingest.py +356 -0
  726. app/modules/usage/mappers.py +29 -0
  727. app/modules/usage/plan_downgrade_observations.py +488 -0
  728. app/modules/usage/repository.py +1890 -0
  729. app/modules/usage/schemas.py +68 -0
  730. app/modules/usage/service.py +102 -0
  731. app/modules/usage/updater.py +1488 -0
  732. app/static/assets/Trans-Cj-J9mVA.js +1 -0
  733. app/static/assets/access-page-DHzZiXZi.js +1 -0
  734. app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
  735. app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
  736. app/static/assets/account-status-DIsU5sUS.js +1 -0
  737. app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
  738. app/static/assets/accounts-page-Bbai8ift.js +3 -0
  739. app/static/assets/api-Dwa7XxPe.js +1 -0
  740. app/static/assets/api-client-dugX1Pti.js +55 -0
  741. app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
  742. app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
  743. app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
  744. app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
  745. app/static/assets/apis-page-BvXz9c6i.js +2 -0
  746. app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
  747. app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
  748. app/static/assets/automations-page-DQgNAJOR.js +3 -0
  749. app/static/assets/badge-DqMb9es0.js +1 -0
  750. app/static/assets/blur-email-HEvKagG2.js +1 -0
  751. app/static/assets/button-BjVmmiGW.js +1 -0
  752. app/static/assets/checkbox-apsl9vzk.js +1 -0
  753. app/static/assets/chevron-left-1d_pHVa5.js +1 -0
  754. app/static/assets/circle-alert-BS0xd_o1.js +1 -0
  755. app/static/assets/clsx-DB0hHKMi.js +1 -0
  756. app/static/assets/colors-BBy8GEtr.js +1 -0
  757. app/static/assets/copy-button-D7nAvOKR.js +1 -0
  758. app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
  759. app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
  760. app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
  761. app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
  762. app/static/assets/donut-chart-Bxc69cra.js +1 -0
  763. app/static/assets/download-kQteIcsB.js +1 -0
  764. app/static/assets/ellipsis-B_5OgIre.js +1 -0
  765. app/static/assets/empty-state-JcA7tc8m.js +1 -0
  766. app/static/assets/es2015-bMH8YKZT.js +41 -0
  767. app/static/assets/es6-B264ni6W.js +69 -0
  768. app/static/assets/external-link-UVAOgMQ0.js +1 -0
  769. app/static/assets/i18n-BWqZ9Ua2.js +1 -0
  770. app/static/assets/inbox-B_RM2s1J.js +1 -0
  771. app/static/assets/index-DIpd264Z.js +23 -0
  772. app/static/assets/index-DMeLKRJx.css +2 -0
  773. app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
  774. app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
  775. app/static/assets/iso-Bpe5Ihbb.js +1 -0
  776. app/static/assets/label-CGaLmLNf.js +1 -0
  777. app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
  778. app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
  779. app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
  780. app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
  781. app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
  782. app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
  783. app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
  784. app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
  785. app/static/assets/payload-CIYnGZz9.js +1 -0
  786. app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
  787. app/static/assets/report-chart-card-BXDliwcc.js +1 -0
  788. app/static/assets/reports-page-CGCJeMHG.js +3 -0
  789. app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
  790. app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  791. app/static/assets/search-HfhqwERt.js +1 -0
  792. app/static/assets/select-Cl33bz-g.js +1 -0
  793. app/static/assets/settings-page-mZj01HYl.js +2 -0
  794. app/static/assets/shim-CZwyM4Q7.js +1 -0
  795. app/static/assets/skeleton-C_EUc4Rp.js +1 -0
  796. app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
  797. app/static/assets/status-badge-CHHA5X6c.js +1 -0
  798. app/static/assets/switch-CzxgokcM.js +1 -0
  799. app/static/assets/table-DXaNSQ0G.js +1 -0
  800. app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
  801. app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
  802. app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
  803. app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
  804. app/static/assets/trash-D3KyWr1h.js +1 -0
  805. app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
  806. app/static/assets/use-accounts-PWlVGw3z.js +1 -0
  807. app/static/assets/use-auth-CuDlW3iC.js +1 -0
  808. app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
  809. app/static/assets/use-model-sources-Bul24JBu.js +1 -0
  810. app/static/assets/use-models-DUCPUuZg.js +1 -0
  811. app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
  812. app/static/assets/useTranslation--56nHhlH.js +1 -0
  813. app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
  814. app/static/assets/users-uIC3TIlE.js +1 -0
  815. app/static/assets/vendor-query-_bHZuxEx.js +1 -0
  816. app/static/assets/vendor-react-BVL0YZPW.js +9 -0
  817. app/static/favicon.svg +10 -0
  818. app/static/fonts/GeistSans-Variable.woff2 +0 -0
  819. app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  820. app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
  821. app/static/index.html +29 -0
  822. codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
  823. codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
  824. codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
  825. codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
  826. codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
  827. config/additional_quota_registry.json +11 -0
@@ -0,0 +1,2600 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import re
6
+ from collections.abc import Awaitable, Callable, Collection
7
+ from contextlib import asynccontextmanager
8
+ from typing import Any, AsyncIterator, Literal, Mapping, NoReturn, TypeVar, cast
9
+
10
+ import aiohttp
11
+ import anyio
12
+
13
+ from app.core.auth.refresh import (
14
+ RefreshError,
15
+ is_transient_refresh_contention,
16
+ pop_token_refresh_timeout_override,
17
+ push_token_refresh_timeout_override,
18
+ )
19
+ from app.core.balancer import (
20
+ PERMANENT_FAILURE_CODES,
21
+ TRAFFIC_CLASS_FOREGROUND,
22
+ TRAFFIC_CLASS_OPPORTUNISTIC,
23
+ ResetPreferenceWindow,
24
+ RoutingStrategy,
25
+ TrafficClass,
26
+ )
27
+ from app.core.clients.files import create_file as core_create_file # noqa: F401
28
+ from app.core.clients.files import finalize_file as core_finalize_file # noqa: F401
29
+ from app.core.clients.http import lease_http_session as lease_http_session # noqa: F401
30
+ from app.core.clients.proxy import (
31
+ _RESPONSE_CREATE_IMAGE_OMISSION_NOTICE as _RESPONSE_CREATE_IMAGE_OMISSION_NOTICE,
32
+ )
33
+ from app.core.clients.proxy import (
34
+ _RESPONSE_CREATE_TOOL_OUTPUT_OMISSION_NOTICE as _RESPONSE_CREATE_TOOL_OUTPUT_OMISSION_NOTICE,
35
+ )
36
+ from app.core.clients.proxy import CodexControlRequestPrivacyPolicy as CodexControlRequestPrivacyPolicy
37
+ from app.core.clients.proxy import CodexControlResponse as CodexControlResponse
38
+ from app.core.clients.proxy import ( # noqa: F401 # noqa: F401
39
+ ImageFetchSession,
40
+ ProxyResponseError,
41
+ UpstreamProxyRouteTrace,
42
+ _as_image_fetch_session,
43
+ _inline_content_images,
44
+ _inline_input_image_urls,
45
+ _ws_transport_payload_budget_bytes,
46
+ filter_inbound_headers,
47
+ pop_compact_timeout_overrides,
48
+ pop_stream_timeout_overrides,
49
+ pop_transcribe_timeout_overrides,
50
+ push_compact_timeout_overrides,
51
+ push_stream_timeout_overrides,
52
+ push_transcribe_timeout_overrides,
53
+ )
54
+ from app.core.clients.proxy import (
55
+ _is_inline_image_reference as _is_inline_image_reference,
56
+ )
57
+ from app.core.clients.proxy import (
58
+ _response_create_inline_image_notice_item as _response_create_inline_image_notice_item,
59
+ )
60
+ from app.core.clients.proxy import (
61
+ _response_create_inline_image_notice_part as _response_create_inline_image_notice_part,
62
+ )
63
+ from app.core.clients.proxy import (
64
+ _response_create_recent_suffix_start as _response_create_recent_suffix_start,
65
+ )
66
+ from app.core.clients.proxy import (
67
+ _response_create_too_large_error_envelope as _response_create_too_large_error_envelope,
68
+ )
69
+ from app.core.clients.proxy import (
70
+ _should_slim_historical_tool_output as _should_slim_historical_tool_output,
71
+ )
72
+ from app.core.clients.proxy import (
73
+ _slim_historical_response_content as _slim_historical_response_content,
74
+ )
75
+ from app.core.clients.proxy import (
76
+ _slim_historical_response_content_part as _slim_historical_response_content_part,
77
+ )
78
+ from app.core.clients.proxy import codex_control_request as core_codex_control_request # noqa: F401
79
+ from app.core.clients.proxy import compact_responses as core_compact_responses # noqa: F401
80
+ from app.core.clients.proxy import stream_responses as core_stream_responses # noqa: F401
81
+ from app.core.clients.proxy import thread_goal_request as core_thread_goal_request
82
+ from app.core.clients.proxy import transcribe_audio as core_transcribe_audio # noqa: F401
83
+ from app.core.clients.proxy_websocket import UpstreamWebSocket as UpstreamWebSocket
84
+ from app.core.clients.proxy_websocket import (
85
+ connect_live_websocket as connect_live_websocket,
86
+ )
87
+ from app.core.clients.proxy_websocket import (
88
+ connect_responses_websocket as connect_responses_websocket,
89
+ )
90
+ from app.core.clock import REAL_CLOCK, REAL_SCHEDULER, Clock, Scheduler
91
+ from app.core.config.dashboard_overrides import with_dashboard_overrides
92
+ from app.core.config.settings import Settings as _Settings
93
+ from app.core.config.settings import get_settings as get_environment_settings
94
+ from app.core.config.settings_cache import get_settings_cache
95
+ from app.core.crypto import TokenEncryptor
96
+ from app.core.errors import PREVIOUS_RESPONSE_NOT_FOUND_CODE as PREVIOUS_RESPONSE_NOT_FOUND_CODE
97
+ from app.core.errors import (
98
+ PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE as PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE,
99
+ )
100
+ from app.core.errors import (
101
+ OpenAIErrorEnvelope,
102
+ OpenAIErrorParam,
103
+ ResponseFailedEvent,
104
+ coerce_error_param,
105
+ is_previous_response_not_found_error,
106
+ openai_error,
107
+ previous_response_id_from_not_found_message,
108
+ previous_response_stream_incomplete_error,
109
+ response_failed_event,
110
+ synthetic_transport_failure_event,
111
+ )
112
+ from app.core.errors import (
113
+ is_previous_response_not_found_public_shape as _is_previous_response_not_found_public_shape, # noqa: F401
114
+ )
115
+ from app.core.metrics.prometheus import (
116
+ PROMETHEUS_AVAILABLE,
117
+ bridge_same_account_takeover_total,
118
+ )
119
+ from app.core.openai.models import CompactResponsePayload, OpenAIResponsePayload
120
+ from app.core.openai.requests import (
121
+ ResponsesCompactRequest,
122
+ ResponsesRequest,
123
+ )
124
+ from app.core.resilience.network_recovery import (
125
+ ProcessNetworkRecovery as ProcessNetworkRecovery,
126
+ )
127
+ from app.core.resilience.toggles import bind_resilience_toggles
128
+ from app.core.types import JsonValue
129
+ from app.core.upstream_proxy import UpstreamProxyRouteError
130
+ from app.core.upstream_proxy.resolver import (
131
+ resolve_upstream_route as resolve_upstream_route,
132
+ )
133
+ from app.core.utils.request_id import ensure_request_id, get_request_id
134
+ from app.core.utils.sse import CODEX_KEEPALIVE_FRAME as CODEX_KEEPALIVE_FRAME # noqa: F401
135
+ from app.core.utils.sse import format_sse_event, parse_sse_data_json # noqa: F401
136
+ from app.core.utils.time import utcnow as utcnow
137
+ from app.db.models import (
138
+ Account,
139
+ AccountStatus, # noqa: F401
140
+ DashboardSettings,
141
+ StickySessionKind,
142
+ )
143
+ from app.db.session import SessionLocal as SessionLocal
144
+ from app.modules.accounts.auth_manager import AccountsRepositoryPort, AuthManager
145
+ from app.modules.api_keys.service import (
146
+ ApiKeyData,
147
+ ApiKeyUsageReservationData, # noqa: F401
148
+ )
149
+ from app.modules.api_keys.service import (
150
+ ApiKeysService as ApiKeysService,
151
+ )
152
+ from app.modules.proxy._service.api_key_usage import (
153
+ _API_KEY_RESERVATION_HEARTBEAT_SECONDS as _API_KEY_RESERVATION_HEARTBEAT_SECONDS,
154
+ )
155
+ from app.modules.proxy._service.api_key_usage import (
156
+ _STREAM_API_KEY_RELEASE_RETRY_MAX_CONCURRENCY as _STREAM_API_KEY_RELEASE_RETRY_MAX_CONCURRENCY,
157
+ )
158
+ from app.modules.proxy._service.api_key_usage import _ApiKeyUsageMixin
159
+ from app.modules.proxy._service.api_key_usage import (
160
+ _bounded_lease_token_estimate as _bounded_lease_token_estimate,
161
+ )
162
+ from app.modules.proxy._service.api_key_usage import (
163
+ _estimated_lease_tokens_from_request_usage_budget as _estimated_lease_tokens_from_request_usage_budget,
164
+ )
165
+ from app.modules.proxy._service.clock_budget import _ClockBudgetMixin, _remaining_budget_seconds # noqa: F401
166
+ from app.modules.proxy._service.codex_control import _CodexControlMixin
167
+ from app.modules.proxy._service.compact import _CompactMixin
168
+ from app.modules.proxy._service.compact import (
169
+ _service_tier_from_compact_payload as _service_tier_from_compact_payload,
170
+ )
171
+ from app.modules.proxy._service.compact import (
172
+ _sticky_key_for_compact_request as _sticky_key_for_compact_request,
173
+ )
174
+ from app.modules.proxy._service.compact import (
175
+ _sticky_key_from_compact_payload as _sticky_key_from_compact_payload,
176
+ )
177
+ from app.modules.proxy._service.file_ops import _FileOpsMixin
178
+ from app.modules.proxy._service.http_bridge import _HTTPBridgeMixin
179
+ from app.modules.proxy._service.http_bridge.helpers import (
180
+ _TASK_CANCEL_TIMEOUT_SECONDS as _TASK_CANCEL_TIMEOUT_SECONDS,
181
+ )
182
+ from app.modules.proxy._service.http_bridge.helpers import (
183
+ _active_http_bridge_instance_ring as _active_http_bridge_instance_ring,
184
+ )
185
+ from app.modules.proxy._service.http_bridge.helpers import (
186
+ _await_cancelled_task as _await_cancelled_task,
187
+ )
188
+ from app.modules.proxy._service.http_bridge.helpers import (
189
+ _build_http_bridge_prewarm_text as _build_http_bridge_prewarm_text,
190
+ )
191
+ from app.modules.proxy._service.http_bridge.helpers import (
192
+ _durable_bridge_lookup_active_owner as _durable_bridge_lookup_active_owner,
193
+ )
194
+ from app.modules.proxy._service.http_bridge.helpers import (
195
+ _durable_bridge_lookup_allows_local_reuse as _durable_bridge_lookup_allows_local_reuse,
196
+ )
197
+ from app.modules.proxy._service.http_bridge.helpers import (
198
+ _effective_http_bridge_idle_ttl_seconds as _effective_http_bridge_idle_ttl_seconds,
199
+ )
200
+ from app.modules.proxy._service.http_bridge.helpers import (
201
+ _forwarded_http_bridge_session_key as _forwarded_http_bridge_session_key,
202
+ )
203
+ from app.modules.proxy._service.http_bridge.helpers import (
204
+ _has_http_bridge_response_output_marker as _has_http_bridge_response_output_marker,
205
+ )
206
+ from app.modules.proxy._service.http_bridge.helpers import (
207
+ _http_bridge_admission_timeout_seconds,
208
+ _http_bridge_is_explicit_previous_response_rejection, # noqa: F401
209
+ _http_bridge_should_attempt_local_previous_response_recovery, # noqa: F401
210
+ )
211
+ from app.modules.proxy._service.http_bridge.helpers import (
212
+ _http_bridge_allow_durable_takeover as _http_bridge_allow_durable_takeover,
213
+ )
214
+ from app.modules.proxy._service.http_bridge.helpers import (
215
+ _http_bridge_can_local_recover_without_ring as _http_bridge_can_local_recover_without_ring,
216
+ )
217
+ from app.modules.proxy._service.http_bridge.helpers import (
218
+ _http_bridge_can_recover_during_drain as _http_bridge_can_recover_during_drain,
219
+ )
220
+ from app.modules.proxy._service.http_bridge.helpers import (
221
+ _http_bridge_continuity_lost_error_envelope as _http_bridge_continuity_lost_error_envelope,
222
+ )
223
+ from app.modules.proxy._service.http_bridge.helpers import (
224
+ _http_bridge_durable_lease_ttl_seconds as _http_bridge_durable_lease_ttl_seconds,
225
+ )
226
+ from app.modules.proxy._service.http_bridge.helpers import (
227
+ _http_bridge_endpoint_matches_current_instance as _http_bridge_endpoint_matches_current_instance,
228
+ )
229
+ from app.modules.proxy._service.http_bridge.helpers import (
230
+ _http_bridge_eviction_priority as _http_bridge_eviction_priority,
231
+ )
232
+ from app.modules.proxy._service.http_bridge.helpers import (
233
+ _http_bridge_has_durable_recovery_anchor as _http_bridge_has_durable_recovery_anchor,
234
+ )
235
+ from app.modules.proxy._service.http_bridge.helpers import (
236
+ _http_bridge_input_item_type as _http_bridge_input_item_type,
237
+ )
238
+ from app.modules.proxy._service.http_bridge.helpers import (
239
+ _http_bridge_is_context_overflow_error as _http_bridge_is_context_overflow_error,
240
+ )
241
+ from app.modules.proxy._service.http_bridge.helpers import (
242
+ _http_bridge_is_previous_response_owner_unavailable as _http_bridge_is_previous_response_owner_unavailable,
243
+ )
244
+ from app.modules.proxy._service.http_bridge.helpers import (
245
+ _http_bridge_key_strength as _http_bridge_key_strength,
246
+ )
247
+ from app.modules.proxy._service.http_bridge.helpers import (
248
+ _http_bridge_owner_check_required as _http_bridge_owner_check_required,
249
+ )
250
+ from app.modules.proxy._service.http_bridge.helpers import (
251
+ _http_bridge_owner_instance as _http_bridge_owner_instance,
252
+ )
253
+ from app.modules.proxy._service.http_bridge.helpers import (
254
+ _http_bridge_owner_lookup_unavailable_error_envelope as _http_bridge_owner_lookup_unavailable_error_envelope,
255
+ )
256
+ from app.modules.proxy._service.http_bridge.helpers import (
257
+ _http_bridge_payload_looks_like_full_resend as _http_bridge_payload_looks_like_full_resend,
258
+ )
259
+ from app.modules.proxy._service.http_bridge.helpers import (
260
+ _http_bridge_payload_without_previous_response_id as _http_bridge_payload_without_previous_response_id,
261
+ )
262
+ from app.modules.proxy._service.http_bridge.helpers import (
263
+ _http_bridge_precreated_retry_failure_error as _http_bridge_precreated_retry_failure_error,
264
+ )
265
+ from app.modules.proxy._service.http_bridge.helpers import (
266
+ _http_bridge_previous_response_alias_key as _http_bridge_previous_response_alias_key,
267
+ )
268
+ from app.modules.proxy._service.http_bridge.helpers import (
269
+ _http_bridge_previous_response_error_envelope as _http_bridge_previous_response_error_envelope,
270
+ )
271
+ from app.modules.proxy._service.http_bridge.helpers import (
272
+ _http_bridge_prewarm_enabled as _http_bridge_prewarm_enabled,
273
+ )
274
+ from app.modules.proxy._service.http_bridge.helpers import (
275
+ _http_bridge_request_budget_seconds as _http_bridge_request_budget_seconds,
276
+ )
277
+ from app.modules.proxy._service.http_bridge.helpers import (
278
+ _http_bridge_request_counts_against_queue as _http_bridge_request_counts_against_queue,
279
+ )
280
+ from app.modules.proxy._service.http_bridge.helpers import (
281
+ _http_bridge_request_stage as _http_bridge_request_stage,
282
+ )
283
+ from app.modules.proxy._service.http_bridge.helpers import (
284
+ _http_bridge_requires_cluster_registration as _http_bridge_requires_cluster_registration,
285
+ )
286
+ from app.modules.proxy._service.http_bridge.helpers import (
287
+ _http_bridge_runtime_config as _http_bridge_runtime_config,
288
+ )
289
+ from app.modules.proxy._service.http_bridge.helpers import (
290
+ _http_bridge_session_allows_api_key as _http_bridge_session_allows_api_key,
291
+ )
292
+ from app.modules.proxy._service.http_bridge.helpers import (
293
+ _http_bridge_session_has_visible_requests as _http_bridge_session_has_visible_requests,
294
+ )
295
+ from app.modules.proxy._service.http_bridge.helpers import (
296
+ _http_bridge_session_matches_preferred_account as _http_bridge_session_matches_preferred_account,
297
+ )
298
+ from app.modules.proxy._service.http_bridge.helpers import (
299
+ _http_bridge_session_retiring_with_visible_requests as _http_bridge_session_retiring_with_visible_requests,
300
+ )
301
+ from app.modules.proxy._service.http_bridge.helpers import (
302
+ _http_bridge_session_reusable_for_request as _http_bridge_session_reusable_for_request,
303
+ )
304
+ from app.modules.proxy._service.http_bridge.helpers import (
305
+ _http_bridge_should_attempt_local_bootstrap_rebind as _http_bridge_should_attempt_local_bootstrap_rebind,
306
+ )
307
+ from app.modules.proxy._service.http_bridge.helpers import (
308
+ _http_bridge_should_attempt_soft_affinity_reroute as _http_bridge_should_attempt_soft_affinity_reroute,
309
+ )
310
+ from app.modules.proxy._service.http_bridge.helpers import (
311
+ _http_bridge_should_rollover_after_context_overflow as _http_bridge_should_rollover_after_context_overflow,
312
+ )
313
+ from app.modules.proxy._service.http_bridge.helpers import (
314
+ _http_bridge_should_wait_for_registration as _http_bridge_should_wait_for_registration,
315
+ )
316
+ from app.modules.proxy._service.http_bridge.helpers import (
317
+ _http_bridge_startup_wait_timeout_error as _http_bridge_startup_wait_timeout_error,
318
+ )
319
+ from app.modules.proxy._service.http_bridge.helpers import (
320
+ _http_bridge_turn_state_alias_key as _http_bridge_turn_state_alias_key,
321
+ )
322
+ from app.modules.proxy._service.http_bridge.helpers import (
323
+ _HTTPBridgeRuntimeConfig as _HTTPBridgeRuntimeConfig,
324
+ )
325
+ from app.modules.proxy._service.http_bridge.helpers import (
326
+ _is_http_bridge_previous_response_output_item as _is_http_bridge_previous_response_output_item,
327
+ )
328
+ from app.modules.proxy._service.http_bridge.helpers import (
329
+ _is_missing_durable_bridge_table_error as _is_missing_durable_bridge_table_error,
330
+ )
331
+ from app.modules.proxy._service.http_bridge.helpers import (
332
+ _log_http_bridge_event as _log_http_bridge_event,
333
+ )
334
+ from app.modules.proxy._service.http_bridge.helpers import (
335
+ _log_http_bridge_startup_wait_timeout as _log_http_bridge_startup_wait_timeout,
336
+ )
337
+ from app.modules.proxy._service.http_bridge.helpers import (
338
+ _make_http_bridge_session_key as _make_http_bridge_session_key,
339
+ )
340
+ from app.modules.proxy._service.http_bridge.helpers import (
341
+ _normalize_http_bridge_error_event as _normalize_http_bridge_error_event,
342
+ )
343
+ from app.modules.proxy._service.http_bridge.helpers import (
344
+ _normalized_http_bridge_instance_ring as _normalized_http_bridge_instance_ring,
345
+ )
346
+ from app.modules.proxy._service.http_bridge.helpers import (
347
+ _preferred_http_bridge_reconnect_turn_state as _preferred_http_bridge_reconnect_turn_state,
348
+ )
349
+ from app.modules.proxy._service.http_bridge.helpers import (
350
+ _record_bridge_drain_recovery_allowed as _record_bridge_drain_recovery_allowed,
351
+ )
352
+ from app.modules.proxy._service.http_bridge.helpers import (
353
+ _record_bridge_first_turn_timeout as _record_bridge_first_turn_timeout,
354
+ )
355
+ from app.modules.proxy._service.http_bridge.helpers import (
356
+ _record_bridge_reattach as _record_bridge_reattach,
357
+ )
358
+ from app.modules.proxy._service.http_bridge.helpers import (
359
+ _record_http_bridge_stuck_retire as _record_http_bridge_stuck_retire,
360
+ )
361
+ from app.modules.proxy._service.http_bridge.helpers import (
362
+ _trim_http_bridge_previous_response_input_items as _trim_http_bridge_previous_response_input_items,
363
+ )
364
+ from app.modules.proxy._service.http_bridge.retry_circuit import (
365
+ _HTTPBridgeRetryCircuitMixin,
366
+ _initialize_http_bridge_retry_circuit,
367
+ )
368
+ from app.modules.proxy._service.observability import _hash_identifier as _hash_identifier
369
+ from app.modules.proxy._service.observability import _hash_identifier_or_none as _hash_identifier_or_none
370
+ from app.modules.proxy._service.observability import _interesting_header_keys as _interesting_header_keys
371
+ from app.modules.proxy._service.observability import (
372
+ _maybe_log_proxy_request_payload as _maybe_log_proxy_request_payload,
373
+ )
374
+ from app.modules.proxy._service.observability import _maybe_log_proxy_request_shape as _maybe_log_proxy_request_shape
375
+ from app.modules.proxy._service.observability import (
376
+ _maybe_log_proxy_service_tier_trace as _maybe_log_proxy_service_tier_trace,
377
+ )
378
+ from app.modules.proxy._service.observability import (
379
+ _record_continuity_fail_closed as _record_continuity_fail_closed,
380
+ )
381
+ from app.modules.proxy._service.observability import (
382
+ _record_continuity_owner_resolution as _record_continuity_owner_resolution,
383
+ )
384
+ from app.modules.proxy._service.observability import _summarize_input as _summarize_input
385
+ from app.modules.proxy._service.observability import _tools_hash as _tools_hash
386
+ from app.modules.proxy._service.observability import _truncate_identifier as _truncate_identifier
387
+ from app.modules.proxy._service.observability import continuity_fail_closed_total as continuity_fail_closed_total
388
+ from app.modules.proxy._service.observability import (
389
+ continuity_owner_resolution_total as continuity_owner_resolution_total,
390
+ )
391
+ from app.modules.proxy._service.rate_limit import (
392
+ _RateLimitMixin,
393
+ )
394
+ from app.modules.proxy._service.realtime_live import LiveWebSocketConnector, _RealtimeLiveMixin
395
+ from app.modules.proxy._service.refresh import (
396
+ ensure_fresh_with_auth_error,
397
+ )
398
+ from app.modules.proxy._service.refresh import (
399
+ ensure_fresh_with_budget as _recover_fresh_account,
400
+ )
401
+ from app.modules.proxy._service.request_log import (
402
+ _normalize_session_id as _normalize_session_id,
403
+ )
404
+ from app.modules.proxy._service.request_log import (
405
+ _RequestLogMixin,
406
+ )
407
+ from app.modules.proxy._service.response_create import (
408
+ _OVERSIZED_RESPONSE_CREATE_DUMP_DIR as _OVERSIZED_RESPONSE_CREATE_DUMP_DIR,
409
+ )
410
+ from app.modules.proxy._service.response_create import (
411
+ _OVERSIZED_RESPONSE_CREATE_LARGEST_ITEMS as _OVERSIZED_RESPONSE_CREATE_LARGEST_ITEMS,
412
+ )
413
+ from app.modules.proxy._service.response_create import (
414
+ _RESPONSE_CREATE_COMPATIBILITY_METADATA_HEADERS as _RESPONSE_CREATE_COMPATIBILITY_METADATA_HEADERS,
415
+ )
416
+ from app.modules.proxy._service.response_create import (
417
+ _RESPONSE_CREATE_DUMP_MAX_PAIRS as _RESPONSE_CREATE_DUMP_MAX_PAIRS,
418
+ )
419
+ from app.modules.proxy._service.response_create import (
420
+ _RESPONSE_CREATE_HISTORY_OMISSION_NOTICE as _RESPONSE_CREATE_HISTORY_OMISSION_NOTICE,
421
+ )
422
+ from app.modules.proxy._service.response_create import (
423
+ _UPSTREAM_RESPONSE_CREATE_MAX_BYTES as _UPSTREAM_RESPONSE_CREATE_MAX_BYTES,
424
+ )
425
+ from app.modules.proxy._service.response_create import (
426
+ _UPSTREAM_RESPONSE_CREATE_WARN_BYTES as _UPSTREAM_RESPONSE_CREATE_WARN_BYTES,
427
+ )
428
+ from app.modules.proxy._service.response_create import (
429
+ _count_external_image_urls as _count_external_image_urls,
430
+ )
431
+ from app.modules.proxy._service.response_create import (
432
+ _enforce_response_create_size_limit as _enforce_response_create_size_limit,
433
+ )
434
+ from app.modules.proxy._service.response_create import (
435
+ _fingerprint_input_items as _fingerprint_input_items,
436
+ )
437
+ from app.modules.proxy._service.response_create import (
438
+ _function_call_output_call_ids as _function_call_output_call_ids,
439
+ )
440
+ from app.modules.proxy._service.response_create import (
441
+ _inject_missing_interrupted_function_call_outputs as _inject_missing_interrupted_function_call_outputs,
442
+ )
443
+ from app.modules.proxy._service.response_create import (
444
+ _inline_top_level_input_image_urls as _inline_top_level_input_image_urls,
445
+ )
446
+ from app.modules.proxy._service.response_create import (
447
+ _input_image_request_requires_http_upstream, # noqa: F401
448
+ _responses_request_contains_input_image, # noqa: F401
449
+ _responses_request_uses_image_generation, # noqa: F401
450
+ )
451
+ from app.modules.proxy._service.response_create import (
452
+ _input_part_is_image as _input_part_is_image,
453
+ )
454
+ from app.modules.proxy._service.response_create import (
455
+ _json_size_bytes as _json_size_bytes,
456
+ )
457
+ from app.modules.proxy._service.response_create import (
458
+ _json_value_contains_input_image_part as _json_value_contains_input_image_part,
459
+ )
460
+ from app.modules.proxy._service.response_create import (
461
+ _maybe_dump_oversized_response_create_request as _maybe_dump_oversized_response_create_request,
462
+ )
463
+ from app.modules.proxy._service.response_create import (
464
+ _missing_function_call_outputs_for_previous_response as _missing_function_call_outputs_for_previous_response,
465
+ )
466
+ from app.modules.proxy._service.response_create import (
467
+ _oversized_response_create_dump_dir as _oversized_response_create_dump_dir,
468
+ )
469
+ from app.modules.proxy._service.response_create import (
470
+ _response_create_client_metadata as _response_create_client_metadata,
471
+ )
472
+ from app.modules.proxy._service.response_create import (
473
+ _response_create_history_omission_notice_item as _response_create_history_omission_notice_item,
474
+ )
475
+ from app.modules.proxy._service.response_create import (
476
+ _response_create_text as _response_create_text,
477
+ )
478
+ from app.modules.proxy._service.response_create import (
479
+ _response_create_text_with_account_installation_id as _response_create_text_with_account_installation_id,
480
+ )
481
+ from app.modules.proxy._service.response_create import (
482
+ _response_create_text_with_size_guard as _response_create_text_with_size_guard,
483
+ )
484
+ from app.modules.proxy._service.response_create import (
485
+ _response_output_item_done_tool_call as _response_output_item_done_tool_call,
486
+ )
487
+ from app.modules.proxy._service.response_create import (
488
+ _safe_dump_slug as _safe_dump_slug,
489
+ )
490
+ from app.modules.proxy._service.response_create import (
491
+ _should_dump_oversized_response_create as _should_dump_oversized_response_create,
492
+ )
493
+ from app.modules.proxy._service.response_create import (
494
+ _slim_historical_response_input_item as _slim_historical_response_input_item,
495
+ )
496
+ from app.modules.proxy._service.response_create import (
497
+ _slim_response_create_payload_for_upstream as _slim_response_create_payload_for_upstream,
498
+ )
499
+ from app.modules.proxy._service.response_create import (
500
+ _summarize_response_create_input as _summarize_response_create_input,
501
+ )
502
+ from app.modules.proxy._service.response_create import (
503
+ _summarize_response_create_payload as _summarize_response_create_payload,
504
+ )
505
+ from app.modules.proxy._service.response_create import (
506
+ _synthetic_interrupted_function_call_output as _synthetic_interrupted_function_call_output,
507
+ )
508
+ from app.modules.proxy._service.response_create import (
509
+ _write_response_create_dump as _write_response_create_dump,
510
+ )
511
+ from app.modules.proxy._service.streaming import _StreamingMixin
512
+ from app.modules.proxy._service.streaming.helpers import (
513
+ _build_rewritten_stream_response_failed_event as _build_rewritten_stream_response_failed_event,
514
+ )
515
+ from app.modules.proxy._service.streaming.helpers import (
516
+ _build_stream_incomplete_terminal_event_for_request as _build_stream_incomplete_terminal_event_for_request,
517
+ )
518
+ from app.modules.proxy._service.streaming.helpers import (
519
+ _call_stream_with_supported_optional_kwargs as _call_stream_with_supported_optional_kwargs,
520
+ )
521
+ from app.modules.proxy._service.streaming.helpers import _classify_upstream_close as _classify_upstream_close
522
+ from app.modules.proxy._service.streaming.helpers import (
523
+ _is_account_neutral_transport_drop as _is_account_neutral_transport_drop,
524
+ )
525
+ from app.modules.proxy._service.streaming.helpers import _is_background_json_ack as _is_background_json_ack
526
+ from app.modules.proxy._service.streaming.helpers import (
527
+ _push_stream_attempt_timeout_overrides as _push_stream_attempt_timeout_overrides,
528
+ )
529
+ from app.modules.proxy._service.streaming.helpers import (
530
+ _rewrite_previous_response_stream_error as _rewrite_previous_response_stream_error,
531
+ )
532
+ from app.modules.proxy._service.streaming.helpers import (
533
+ _should_infer_upstream_status_from_proxy_error as _should_infer_upstream_status_from_proxy_error,
534
+ )
535
+ from app.modules.proxy._service.streaming.helpers import (
536
+ _should_penalize_stream_error as _should_penalize_stream_error,
537
+ )
538
+ from app.modules.proxy._service.streaming.helpers import (
539
+ _should_retry_stream_error as _should_retry_stream_error,
540
+ )
541
+ from app.modules.proxy._service.streaming.helpers import (
542
+ _should_retry_transient_stream_error as _should_retry_transient_stream_error,
543
+ )
544
+ from app.modules.proxy._service.streaming.helpers import (
545
+ _stream_iterator_after_capacity_admission as _stream_iterator_after_capacity_admission,
546
+ )
547
+ from app.modules.proxy._service.streaming.helpers import (
548
+ _stream_request_budget_seconds as _stream_request_budget_seconds,
549
+ )
550
+ from app.modules.proxy._service.streaming.helpers import (
551
+ _upstream_turn_state_from_socket as _upstream_turn_state_from_socket,
552
+ )
553
+ from app.modules.proxy._service.streaming.retry import (
554
+ _http_bridge_allowed_by_transport_policy as _http_bridge_allowed_by_transport_policy,
555
+ )
556
+ from app.modules.proxy._service.streaming.retry import (
557
+ _http_downstream_request_is_sticky as _http_downstream_request_is_sticky,
558
+ )
559
+ from app.modules.proxy._service.streaming.retry import (
560
+ _resolve_http_downstream_transport as _resolve_http_downstream_transport,
561
+ )
562
+ from app.modules.proxy._service.support import (
563
+ _HARD_HTTP_BRIDGE_AFFINITY_KINDS, # noqa: F401
564
+ _REQUEST_TRANSPORT_WEBSOCKET, # noqa: F401
565
+ _WEBSOCKET_FULL_REPLAY_WAIT_MIN_ITEMS, # noqa: F401
566
+ _WEBSOCKET_FULL_REPLAY_WAIT_POLL_SECONDS, # noqa: F401
567
+ _ApiKeyReservationTouchState, # noqa: F401
568
+ _call_with_supported_optional_kwargs,
569
+ _clear_websocket_request_error_overrides, # noqa: F401
570
+ _DownstreamWebSocketActivity, # noqa: F401
571
+ _event_type_from_payload, # noqa: F401
572
+ _finalize_ttft_reasoning_deltas, # noqa: F401
573
+ _http_error_status_from_payload, # noqa: F401
574
+ _HTTPBridgeSession,
575
+ _HTTPBridgeSessionKey,
576
+ _is_account_neutral_error_code,
577
+ _is_local_account_cap_code, # noqa: F401
578
+ _is_ttft_event, # noqa: F401
579
+ _openai_error_envelope_from_response_failed_payload, # noqa: F401
580
+ _PreparedWebSocketRequest, # noqa: F401
581
+ _record_response_event, # noqa: F401
582
+ _record_websocket_route_metadata, # noqa: F401
583
+ _request_log_client_fields, # noqa: F401
584
+ _request_log_useragent_fields, # noqa: F401
585
+ _RequestLogFailureMetadata,
586
+ _RetryableStreamError, # noqa: F401
587
+ _selection_api_key_fair_share_threshold_pct,
588
+ _stream_settlement_error_payload, # noqa: F401
589
+ _StreamSettlement, # noqa: F401
590
+ _supported_optional_kwargs, # noqa: F401
591
+ _TerminalStreamError, # noqa: F401
592
+ _TransientStreamError, # noqa: F401
593
+ _ttft_event_visible_at, # noqa: F401
594
+ _TTFTReasoningDeltaState, # noqa: F401
595
+ _wait_for_websocket_continuity_gap, # noqa: F401
596
+ _websocket_full_replay_should_wait_for_continuity, # noqa: F401
597
+ _websocket_request_can_replay_before_visible_output, # noqa: F401
598
+ _WebSocketConnectFailureEmitted, # noqa: F401
599
+ _WebSocketContinuityAnchor, # noqa: F401
600
+ _WebSocketContinuityState,
601
+ _WebSocketReceiveTimeout, # noqa: F401
602
+ _WebSocketRequestState,
603
+ _WebSocketUpstreamControl, # noqa: F401
604
+ opportunistic_admission_account_scope,
605
+ )
606
+ from app.modules.proxy._service.support import (
607
+ _HTTPBridgeOwnerForward as _HTTPBridgeOwnerForward,
608
+ )
609
+ from app.modules.proxy._service.support import (
610
+ _raise_proxy_unavailable_for_account as _raise_proxy_unavailable_for_account,
611
+ )
612
+ from app.modules.proxy._service.support import (
613
+ _websocket_route_log_kwargs as _websocket_route_log_kwargs,
614
+ )
615
+ from app.modules.proxy._service.support import (
616
+ failover_after_previsible_refresh_error as failover_after_previsible_refresh_error,
617
+ )
618
+ from app.modules.proxy._service.support import (
619
+ is_claim_contention_unpenalized as is_claim_contention_unpenalized,
620
+ )
621
+ from app.modules.proxy._service.support import (
622
+ raise_proxy_unavailable_for_claim_contention as raise_proxy_unavailable_for_claim_contention,
623
+ )
624
+ from app.modules.proxy._service.transcribe import (
625
+ _TranscribeMixin,
626
+ )
627
+ from app.modules.proxy._service.warmup import (
628
+ WarmupExecutionData as WarmupExecutionData,
629
+ )
630
+ from app.modules.proxy._service.warmup import (
631
+ WarmupFailedAccountData as WarmupFailedAccountData,
632
+ )
633
+ from app.modules.proxy._service.warmup import (
634
+ WarmupSkippedAccountData as WarmupSkippedAccountData,
635
+ )
636
+ from app.modules.proxy._service.warmup import (
637
+ WarmupSubmittedAccountData as WarmupSubmittedAccountData,
638
+ )
639
+ from app.modules.proxy._service.warmup import (
640
+ _is_warmup_usage_eligible as _is_warmup_usage_eligible,
641
+ )
642
+ from app.modules.proxy._service.warmup import (
643
+ _materialize_warmup_account as _materialize_warmup_account,
644
+ )
645
+ from app.modules.proxy._service.warmup import (
646
+ _snapshot_warmup_account as _snapshot_warmup_account,
647
+ )
648
+ from app.modules.proxy._service.warmup import (
649
+ _WarmupAccountSnapshot as _WarmupAccountSnapshot,
650
+ )
651
+ from app.modules.proxy._service.warmup import (
652
+ _WarmupMixin,
653
+ )
654
+ from app.modules.proxy._service.warmup import (
655
+ _WarmupSubmitResult as _WarmupSubmitResult,
656
+ )
657
+ from app.modules.proxy._service.warmup import (
658
+ _WarmupUsageSnapshot as _WarmupUsageSnapshot,
659
+ )
660
+ from app.modules.proxy._service.websocket import (
661
+ _WebSocketMixin,
662
+ )
663
+ from app.modules.proxy._service.websocket.helpers import (
664
+ _app_error_to_websocket_event, # noqa: F401
665
+ _assign_websocket_response_id, # noqa: F401
666
+ _clear_websocket_stale_previous_response_cache, # noqa: F401
667
+ _draining_websocket_request_states, # noqa: F401
668
+ _find_websocket_request_state_by_response_id, # noqa: F401
669
+ _is_websocket_previous_response_output_item, # noqa: F401
670
+ _is_websocket_response_create, # noqa: F401
671
+ _match_websocket_request_state_for_anonymous_event, # noqa: F401
672
+ _match_websocket_request_state_for_precreated_terminal_event, # noqa: F401
673
+ _match_websocket_request_state_for_previous_response_error, # noqa: F401
674
+ _matching_websocket_request_states_for_missing_tool_output_error, # noqa: F401
675
+ _matching_websocket_request_states_for_previous_response_error, # noqa: F401
676
+ _maybe_rewrite_websocket_previous_response_not_found_event, # noqa: F401
677
+ _parse_websocket_payload, # noqa: F401
678
+ _pop_matching_websocket_request_states, # noqa: F401
679
+ _pop_replayable_precreated_websocket_request_state, # noqa: F401
680
+ _pop_terminal_websocket_request_state, # noqa: F401
681
+ _prepare_websocket_request_state_for_account_switch, # noqa: F401
682
+ _prepare_websocket_request_state_for_auth_replay, # noqa: F401
683
+ _prepare_websocket_request_state_for_visible_output_replay, # noqa: F401
684
+ _record_websocket_continuity_completion, # noqa: F401
685
+ _record_websocket_responses_lite_acceptance, # noqa: F401
686
+ _refresh_websocket_request_input_fingerprint_from_text, # noqa: F401
687
+ _release_websocket_response_create_gate, # noqa: F401
688
+ _rewrite_websocket_continuity_corruption_event, # noqa: F401
689
+ _rewrite_websocket_downstream_response_id, # noqa: F401
690
+ _rewrite_websocket_previous_response_owner_unavailable_event, # noqa: F401
691
+ _rewrite_websocket_suppressed_duplicate_tool_call_completion_event, # noqa: F401
692
+ _sanitize_websocket_connect_failure, # noqa: F401
693
+ _sanitize_websocket_previous_response_error, # noqa: F401
694
+ _sanitize_websocket_terminal_error_fields, # noqa: F401
695
+ _serialize_websocket_error_event, # noqa: F401
696
+ _trim_websocket_previous_response_input_items, # noqa: F401
697
+ _upstream_websocket_disconnect_message, # noqa: F401
698
+ _websocket_auth_failure_permanent_code, # noqa: F401
699
+ _websocket_auth_failure_requires_reauth, # noqa: F401
700
+ _websocket_auth_request_can_switch_account, # noqa: F401
701
+ _websocket_client_previous_response_full_resend_is_retry_safe, # noqa: F401
702
+ _websocket_connect_deadline, # noqa: F401
703
+ _websocket_continuity_anchor_for_payload, # noqa: F401
704
+ _websocket_continuity_error_fields, # noqa: F401
705
+ _websocket_continuity_response_ids, # noqa: F401
706
+ _websocket_downstream_response_id, # noqa: F401
707
+ _websocket_event_error_code, # noqa: F401
708
+ _websocket_event_error_message, # noqa: F401
709
+ _websocket_event_error_param, # noqa: F401
710
+ _websocket_event_error_payload, # noqa: F401
711
+ _websocket_event_error_type, # noqa: F401
712
+ _websocket_full_resend_conflicts_with_visible_pending, # noqa: F401
713
+ _websocket_input_item_type, # noqa: F401
714
+ _websocket_owner_pinned_quota_error_code, # noqa: F401
715
+ _websocket_precreated_auth_error_code, # noqa: F401
716
+ _websocket_precreated_retry_error_code, # noqa: F401
717
+ _websocket_receive_timeout_for_pending_requests, # noqa: F401
718
+ _websocket_request_text_is_account_neutral_fresh_replay, # noqa: F401
719
+ _websocket_response_id, # noqa: F401
720
+ _websocket_top_level_error_payload, # noqa: F401
721
+ _wrapped_websocket_error_event, # noqa: F401
722
+ )
723
+ from app.modules.proxy.affinity import (
724
+ _AffinityPolicy,
725
+ _CodexSessionSource,
726
+ _sticky_key_for_thread_goal_request,
727
+ _sticky_key_from_session_header, # noqa: F401
728
+ )
729
+ from app.modules.proxy.affinity import (
730
+ _owner_lookup_session_id_from_headers as _owner_lookup_session_id_from_headers,
731
+ )
732
+ from app.modules.proxy.affinity import (
733
+ _sticky_key_for_responses_request as _sticky_key_for_responses_request,
734
+ )
735
+ from app.modules.proxy.capability_routing import CapabilityRouter
736
+ from app.modules.proxy.durable_bridge_coordinator import (
737
+ DurableBridgeLookup as DurableBridgeLookup,
738
+ )
739
+ from app.modules.proxy.durable_bridge_coordinator import (
740
+ DurableBridgeSessionCoordinator,
741
+ )
742
+ from app.modules.proxy.helpers import (
743
+ _apply_error_metadata,
744
+ _header_account_id,
745
+ _normalize_error_code,
746
+ _parse_openai_error,
747
+ _upstream_error_from_openai,
748
+ )
749
+ from app.modules.proxy.http_bridge_event_batcher import HttpBridgeOperationEventBatcher
750
+ from app.modules.proxy.http_bridge_forwarding import (
751
+ HTTPBridgeForwardContext as HTTPBridgeForwardContext,
752
+ )
753
+ from app.modules.proxy.http_bridge_forwarding import (
754
+ HTTPBridgeOwnerClient,
755
+ )
756
+ from app.modules.proxy.http_bridge_forwarding import (
757
+ OwnerForwardRelayFailure as OwnerForwardRelayFailure,
758
+ )
759
+ from app.modules.proxy.load_balancer import (
760
+ AccountConcurrencyCaps,
761
+ AccountLease,
762
+ AccountLeaseKind,
763
+ AccountSelection,
764
+ LoadBalancer,
765
+ RoutingTunables,
766
+ effective_account_concurrency_caps,
767
+ effective_routing_tunables,
768
+ )
769
+ from app.modules.proxy.repo_bundle import ProxyRepoFactory
770
+ from app.modules.proxy.ring_membership import (
771
+ RingMembershipService,
772
+ )
773
+ from app.modules.proxy.selection_errors import selection_failure_response
774
+ from app.modules.proxy.work_admission import (
775
+ ADMISSION_WAIT_TIMEOUT_SECONDS,
776
+ COMPACT_RESPONSE_CREATE_LIMIT,
777
+ TOKEN_REFRESH_LIMIT,
778
+ UPSTREAM_WEBSOCKET_CONNECT_LIMIT,
779
+ WorkAdmissionController,
780
+ )
781
+
782
+
783
+ def get_settings() -> _Settings:
784
+ """Startup ``Settings`` with the request-bound dashboard overrides applied.
785
+
786
+ Every proxy consumer reads settings through this facade (directly or via
787
+ ``_service_get_settings()``), so the dashboard-managed timeouts (C2-1) take
788
+ effect here without touching each call site; outside a bound request context
789
+ the environment values apply unchanged.
790
+ """
791
+ return with_dashboard_overrides(get_environment_settings())
792
+
793
+
794
+ logger = logging.getLogger(__name__)
795
+
796
+ _ResponsesPayloadT = TypeVar("_ResponsesPayloadT", ResponsesRequest, ResponsesCompactRequest)
797
+ _DOWNSTREAM_WEBSOCKET_IDLE_CLOSE_REASON = "Idle downstream websocket timeout"
798
+ _DOWNSTREAM_WEBSOCKET_RECEIVE_POLL_SECONDS = 1.0
799
+ # Keep the first HTTP bridge liveness frame behind the API layer's startup
800
+ # error probe window. If a keepalive becomes the first yielded chunk, the HTTP
801
+ # status is committed as 200 and startup ProxyResponseError handling is masked.
802
+ _HTTP_BRIDGE_STARTUP_KEEPALIVE_GRACE_SECONDS = 0.5
803
+
804
+
805
+ def _proxy_admission_wait_timeout_seconds() -> float:
806
+ # Module-level indirection so the HTTP bridge helpers and tests share one
807
+ # patch point for the fixed admission wait.
808
+ return ADMISSION_WAIT_TIMEOUT_SECONDS
809
+
810
+
811
+ # Maximum time (seconds) to wait for a prewarm upstream response before
812
+ # giving up and letting the actual request proceed without prewarming.
813
+ # A blocked prewarm holds the response_create_gate semaphore and prevents
814
+ # the real request from being sent, leading to an indefinite :keepalive hang.
815
+ _PREWARM_RESPONSE_TIMEOUT_SECONDS = 2.0
816
+ _HTTP_BRIDGE_BACKGROUND_CLOSE_TIMEOUT_SECONDS = 5.0
817
+ _HTTP_BRIDGE_BACKGROUND_CLEANUP_WARN_THRESHOLD = 100
818
+ # Maximum consecutive keepalive frames sent before terminating the stream.
819
+ # 6 × 10s (default interval) = 60s. Combined with the 0.5s startup-probe
820
+ # window this ensures the client sees a terminal event within ≈70s when the
821
+ # upstream silently stops responding.
822
+ _STREAM_KEEPALIVE_MAX_COUNT = 6
823
+
824
+
825
+ _TEXT_DELTA_EVENT_TYPES = frozenset({"response.output_text.delta", "response.refusal.delta"})
826
+ _TEXT_DONE_CONTENT_PART_TYPES = frozenset({"output_text", "refusal"})
827
+ _REQUEST_TRANSPORT_HTTP = "http"
828
+ _COMPACT_SAME_CONTRACT_RETRY_BUDGET = 1
829
+ _ACCOUNT_RECOVERY_RETRY_CODES = frozenset(
830
+ {
831
+ "rate_limit_exceeded",
832
+ "usage_limit_reached",
833
+ "insufficient_quota",
834
+ "usage_not_included",
835
+ "quota_exceeded",
836
+ *PERMANENT_FAILURE_CODES.keys(),
837
+ }
838
+ )
839
+
840
+ _TRANSIENT_RETRY_CODES = frozenset(
841
+ {
842
+ "overloaded_error",
843
+ "server_error",
844
+ "server_is_overloaded",
845
+ "stream_incomplete",
846
+ "stream_idle_timeout",
847
+ "upstream_request_timeout",
848
+ }
849
+ )
850
+ _UPSTREAM_UNAVAILABLE_TRANSIENT_MESSAGE_MARKERS = (
851
+ "broken pipe",
852
+ "cannot connect",
853
+ "connection aborted",
854
+ "connection closed",
855
+ "connection reset",
856
+ "keepalive ping timeout",
857
+ "no close frame",
858
+ "server disconnected",
859
+ "timed out",
860
+ "timeout",
861
+ "upstream closed",
862
+ )
863
+ _UPSTREAM_UNAVAILABLE_NON_TRANSIENT_MESSAGE_MARKERS = (
864
+ "certificate verify failed",
865
+ "clientconnectorcertificateerror",
866
+ "sslcertverificationerror",
867
+ )
868
+ _UPSTREAM_CLOSE_CODES_SKIP_SAME_ACCOUNT_RETRY = frozenset({1011})
869
+ _MAX_TRANSIENT_SAME_ACCOUNT_RETRIES = 3
870
+ _COMPACT_MAX_ACCOUNT_ATTEMPTS = 2
871
+ _STREAM_MAX_ACCOUNT_ATTEMPTS = 3
872
+ _WEBSOCKET_MAX_ACCOUNT_ATTEMPTS = 3
873
+ _WEBSOCKET_TRANSPARENT_REPLAY_ERROR_CODES = frozenset(
874
+ {
875
+ "rate_limit_exceeded",
876
+ "usage_limit_reached",
877
+ "insufficient_quota",
878
+ "usage_not_included",
879
+ "quota_exceeded",
880
+ "overloaded_error",
881
+ "server_is_overloaded",
882
+ }
883
+ )
884
+ _WEBSOCKET_AUTH_FAILURE_CODES = frozenset({"invalid_api_key", "invalid_authentication", "token_invalidated"})
885
+ _WEBSOCKET_REAUTH_REQUIRED_MESSAGE_MARKERS = (
886
+ "session has ended",
887
+ "session expired",
888
+ "log in again",
889
+ "login again",
890
+ "reauth",
891
+ "re-auth",
892
+ )
893
+ _WEBSOCKET_SESSION_EXPIRED_FAILURE_CODE = "account_session_expired"
894
+ _WEBSOCKET_AUTH_INVALIDATED_FAILURE_CODE = "account_auth_invalidated"
895
+ _SUPPRESSED_DUPLICATE_TOOL_CALL_MESSAGE = (
896
+ "Suppressed duplicate side-effect tool call; upstream response cannot be continued safely."
897
+ )
898
+ _SUPPRESSED_DUPLICATE_TOOL_CALL_ERROR_CODE = "duplicate_tool_call_replay_suppressed"
899
+ _WEBSOCKET_PREVIOUS_RESPONSE_ACCOUNT_CACHE_LIMIT = 4096
900
+ _WEBSOCKET_CONTINUITY_CACHE_LIMIT = 4096
901
+ _SECURITY_WORK_AUTHORIZATION_REQUIRED_CODE = "security_work_authorization_required"
902
+ _NO_SECURITY_WORK_AUTHORIZED_ACCOUNTS_CODE = "no_security_work_authorized_accounts"
903
+ _SECURITY_WORK_AUTHORIZATION_REQUIRED_HINTS = (
904
+ "flagged for possible cybersecurity risk",
905
+ "authorized for security work",
906
+ "chatgpt.com/cyber",
907
+ )
908
+ _SECURITY_WORK_RETRY_MESSAGE = (
909
+ "Upstream flagged this request as possible cybersecurity work. "
910
+ "codex-lb is retrying on an account marked as authorized for security work."
911
+ )
912
+ _SECURITY_WORK_NO_AUTHORIZED_ACCOUNTS_MESSAGE = (
913
+ "Upstream flagged this request as possible cybersecurity work, but no account is marked as authorized for "
914
+ "security work. codex-lb is continuing with normal account selection; the upstream request may still fail until "
915
+ "an account with Trusted Access for Cyber is marked as security-work-authorized."
916
+ )
917
+
918
+
919
+ class ProxyService(
920
+ _ApiKeyUsageMixin,
921
+ _RequestLogMixin,
922
+ _RateLimitMixin,
923
+ _WarmupMixin,
924
+ _FileOpsMixin,
925
+ _TranscribeMixin,
926
+ _CodexControlMixin,
927
+ _RealtimeLiveMixin,
928
+ _CompactMixin,
929
+ _StreamingMixin,
930
+ _WebSocketMixin,
931
+ _HTTPBridgeRetryCircuitMixin,
932
+ _HTTPBridgeMixin,
933
+ _ClockBudgetMixin,
934
+ ):
935
+ def __init__(
936
+ self,
937
+ repo_factory: ProxyRepoFactory,
938
+ *,
939
+ clock: Clock = REAL_CLOCK,
940
+ scheduler: Scheduler = REAL_SCHEDULER,
941
+ live_websocket_connector: LiveWebSocketConnector = connect_live_websocket,
942
+ ) -> None:
943
+ self._repo_factory = repo_factory
944
+ self._clock, self._scheduler = clock, scheduler
945
+ self._encryptor = TokenEncryptor()
946
+ self._load_balancer = LoadBalancer(repo_factory, encryptor=self._encryptor, clock=clock)
947
+ self._capability_router = CapabilityRouter(repo_factory)
948
+ self._live_websocket_connector = live_websocket_connector
949
+ self._ring_membership = RingMembershipService(SessionLocal)
950
+ self._durable_bridge = DurableBridgeSessionCoordinator(SessionLocal)
951
+ self._http_bridge_operation_event_batcher = HttpBridgeOperationEventBatcher.from_settings(self._durable_bridge)
952
+ self._http_bridge_owner_client = HTTPBridgeOwnerClient()
953
+ self._initialize_http_bridge_session_registry()
954
+ _initialize_http_bridge_retry_circuit(self, _clear_websocket_stale_previous_response_cache)
955
+ self._http_bridge_account_timeout_failures, self._http_bridge_account_timeout_lock = {}, asyncio.Lock()
956
+ self._http_bridge_inflight_sessions: dict[_HTTPBridgeSessionKey, asyncio.Future[_HTTPBridgeSession]] = {}
957
+ self._http_bridge_turn_state_index: dict[tuple[str, str | None], _HTTPBridgeSessionKey] = {}
958
+ self._http_bridge_previous_response_index: dict[tuple[str, str | None], _HTTPBridgeSessionKey] = {}
959
+ self._websocket_previous_response_account_index: dict[tuple[str, str | None, str | None], str] = {}
960
+ self._websocket_continuity_index: dict[tuple[str, str | None], _WebSocketContinuityState] = {}
961
+ self._background_cleanup_tasks: set[asyncio.Task[None]] = set()
962
+ self._http_bridge_background_cleanup_failed = False
963
+ self._stream_api_key_release_retry_semaphore = asyncio.Semaphore(_STREAM_API_KEY_RELEASE_RETRY_MAX_CONCURRENCY)
964
+ self._file_pin_session_factory = SessionLocal
965
+ self._http_bridge_lock = anyio.Lock()
966
+ self._work_admission: WorkAdmissionController | None = None
967
+ self._request_log_tasks: set[asyncio.Task[None]] = set()
968
+
969
+ def _get_work_admission(self) -> WorkAdmissionController:
970
+ if self._work_admission is None:
971
+ self._work_admission = WorkAdmissionController(
972
+ token_refresh_limit=TOKEN_REFRESH_LIMIT,
973
+ websocket_connect_limit=UPSTREAM_WEBSOCKET_CONNECT_LIMIT,
974
+ response_create_limit=get_settings().proxy_response_create_limit,
975
+ compact_response_create_limit=COMPACT_RESPONSE_CREATE_LIMIT,
976
+ admission_wait_timeout_seconds=_proxy_admission_wait_timeout_seconds(),
977
+ scheduler=self._scheduler,
978
+ )
979
+ return self._work_admission
980
+
981
+ async def thread_goal_request(
982
+ self,
983
+ operation: str,
984
+ payload: Mapping[str, JsonValue],
985
+ headers: Mapping[str, str],
986
+ *,
987
+ method: str = "POST",
988
+ codex_session_affinity: bool = True,
989
+ api_key: ApiKeyData | None = None,
990
+ ) -> dict[str, JsonValue]:
991
+ filtered = filter_inbound_headers(headers)
992
+ useragent, useragent_group, conversation_id = _request_log_client_fields(headers)
993
+ request_id = get_request_id() or ensure_request_id(None)
994
+ start = self._clock.monotonic()
995
+ base_settings = get_settings()
996
+ deadline = start + base_settings.proxy_request_budget_seconds
997
+ settings = await get_settings_cache().get()
998
+ bind_resilience_toggles(settings, startup_settings=base_settings) # C2-3 resilience toggles
999
+ affinity = _sticky_key_for_thread_goal_request(
1000
+ payload, headers, codex_session_affinity, settings.openai_cache_affinity_max_age_seconds
1001
+ )
1002
+ selection_model = api_key.enforced_model if api_key is not None else None
1003
+ routing_strategy = _routing_strategy(settings)
1004
+ account_id_value: str | None = None
1005
+ log_status = "error"
1006
+ log_error_code: str | None = None
1007
+ log_error_message: str | None = None
1008
+ failure_metadata = _RequestLogFailureMetadata()
1009
+ route_mode: str | None = None
1010
+ route_pool_id: str | None = None
1011
+ route_endpoint_id: str | None = None
1012
+ route_fallback_used: bool | None = None
1013
+ route_fail_closed_reason: str | None = None
1014
+ request_kind = f"thread_goal_{operation}"
1015
+
1016
+ try:
1017
+ selection = await self._select_account_with_budget_compatible(
1018
+ deadline,
1019
+ request_id=request_id,
1020
+ kind=request_kind,
1021
+ api_key=api_key,
1022
+ affinity_policy=affinity,
1023
+ prefer_earlier_reset_accounts=settings.prefer_earlier_reset_accounts,
1024
+ prefer_earlier_reset_window=_prefer_earlier_reset_window(settings),
1025
+ routing_strategy=routing_strategy,
1026
+ model=selection_model,
1027
+ )
1028
+ account = selection.account
1029
+ if not account:
1030
+ account = await self._select_codex_control_account_without_budget(
1031
+ affinity=affinity,
1032
+ api_key=api_key,
1033
+ traffic_class=TRAFFIC_CLASS_OPPORTUNISTIC
1034
+ if api_key is not None and api_key.traffic_class == TRAFFIC_CLASS_OPPORTUNISTIC
1035
+ else TRAFFIC_CLASS_FOREGROUND,
1036
+ prefer_earlier_reset_window=_prefer_earlier_reset_window(settings),
1037
+ )
1038
+ if account is None:
1039
+ log_error_code = selection.error_code or "no_accounts"
1040
+ log_error_message = selection.error_message or "No active accounts available"
1041
+ status_code, error_payload = selection_failure_response(selection)
1042
+ raise ProxyResponseError(status_code, error_payload)
1043
+ account_id_value = account.id
1044
+
1045
+ async def _call_goal(target: Account) -> dict[str, JsonValue]:
1046
+ nonlocal route_fallback_used, route_mode, route_pool_id, route_endpoint_id
1047
+ access_token = self._encryptor.decrypt(target.access_token_encrypted)
1048
+ upstream_account_id = _header_account_id(target.chatgpt_account_id)
1049
+ remaining_budget = self._remaining_budget_seconds(deadline)
1050
+ if remaining_budget <= 0:
1051
+ logger.warning(
1052
+ "Thread goal request budget exhausted before upstream call request_id=%s operation=%s "
1053
+ "account_id=%s",
1054
+ request_id,
1055
+ operation,
1056
+ target.id,
1057
+ )
1058
+ _raise_proxy_budget_exhausted()
1059
+ route = await self._resolve_upstream_route_for_account(target, operation=request_kind)
1060
+ if route is not None:
1061
+ route_mode = route.mode
1062
+ route_pool_id = route.pool_id
1063
+ route_endpoint_id = route.endpoint_id
1064
+ route_trace = UpstreamProxyRouteTrace()
1065
+ try:
1066
+ return await core_thread_goal_request(
1067
+ operation,
1068
+ payload,
1069
+ filtered,
1070
+ access_token,
1071
+ upstream_account_id,
1072
+ method=method,
1073
+ timeout_seconds=remaining_budget,
1074
+ route=route,
1075
+ allow_direct_egress=route is None,
1076
+ route_trace=route_trace,
1077
+ )
1078
+ finally:
1079
+ if route_trace.mode is not None:
1080
+ route_mode = route_trace.mode
1081
+ route_pool_id = route_trace.pool_id
1082
+ route_endpoint_id = route_trace.endpoint_id
1083
+ route_fallback_used = route_trace.fallback_used
1084
+
1085
+ async def _select_goal_failover(excluded_account_ids: set[str]) -> AccountSelection:
1086
+ return await self._select_account_with_budget(
1087
+ deadline,
1088
+ request_id=request_id,
1089
+ kind=request_kind,
1090
+ api_key=api_key,
1091
+ sticky_key=affinity.selection_key,
1092
+ sticky_kind=affinity.kind,
1093
+ reallocate_sticky=affinity.reallocate_sticky,
1094
+ sticky_source=affinity.codex_session_source,
1095
+ legacy_sticky_key=affinity.legacy_selection_key,
1096
+ legacy_continuity_source=affinity.legacy_continuity_source,
1097
+ sticky_seed_key=affinity.seed_selection_key,
1098
+ sticky_seed_kind=affinity.seed_selection_kind,
1099
+ sticky_max_age_seconds=affinity.max_age_seconds,
1100
+ prefer_earlier_reset_accounts=settings.prefer_earlier_reset_accounts,
1101
+ routing_strategy=routing_strategy,
1102
+ model=selection_model,
1103
+ exclude_account_ids=excluded_account_ids,
1104
+ )
1105
+
1106
+ try:
1107
+ account = await self._ensure_previsible_unary_fresh_with_failover(
1108
+ account,
1109
+ deadline=deadline,
1110
+ request_id=request_id,
1111
+ kind=request_kind,
1112
+ select_next_account=_select_goal_failover,
1113
+ )
1114
+ account_id_value = account.id
1115
+ response = await _call_goal(account)
1116
+ await self._load_balancer.record_success(account)
1117
+ log_status = "success"
1118
+ return response
1119
+ except RefreshError as refresh_exc:
1120
+ if refresh_exc.is_permanent:
1121
+ failed_account = _refresh_error_failed_account(refresh_exc, account)
1122
+ account_id_value = failed_account.id
1123
+ await self._load_balancer.mark_permanent_failure(failed_account, refresh_exc.code)
1124
+ raise ProxyResponseError(
1125
+ 401,
1126
+ openai_error(
1127
+ "invalid_api_key",
1128
+ refresh_exc.message,
1129
+ error_type="invalid_request_error",
1130
+ ),
1131
+ ) from refresh_exc
1132
+ except ProxyResponseError as exc:
1133
+ if exc.status_code != 401:
1134
+ failover = await self._retry_previsible_unary_call_failover(
1135
+ exc,
1136
+ account,
1137
+ deadline=deadline,
1138
+ select_next_account=_select_goal_failover,
1139
+ call_next=_call_goal,
1140
+ )
1141
+ if failover is not None:
1142
+ account, response = failover
1143
+ account_id_value = account.id
1144
+ log_status = "success"
1145
+ return response
1146
+ if exc.status_code == 401:
1147
+ try:
1148
+ remaining_budget = self._remaining_budget_seconds(deadline)
1149
+ if remaining_budget <= 0:
1150
+ logger.warning(
1151
+ "Thread goal request budget exhausted before forced refresh retry request_id=%s "
1152
+ "operation=%s account_id=%s",
1153
+ request_id,
1154
+ operation,
1155
+ account.id,
1156
+ )
1157
+ _raise_proxy_budget_exhausted()
1158
+ try:
1159
+ account = await self._ensure_previsible_unary_fresh_with_failover(
1160
+ account,
1161
+ deadline=deadline,
1162
+ request_id=request_id,
1163
+ kind=request_kind,
1164
+ select_next_account=_select_goal_failover,
1165
+ force=True,
1166
+ )
1167
+ except ProxyResponseError as refresh_failover_exc:
1168
+ failed_account = _proxy_response_failed_account(refresh_failover_exc, account)
1169
+ account_id_value = failed_account.id
1170
+ await self._handle_proxy_error(failed_account, refresh_failover_exc)
1171
+ raise
1172
+ account_id_value = account.id
1173
+ try:
1174
+ response = await _call_goal(account)
1175
+ await self._load_balancer.record_success(account)
1176
+ log_status = "success"
1177
+ return response
1178
+ except ProxyResponseError as retry_exc:
1179
+ await self._handle_proxy_error(account, retry_exc)
1180
+ if retry_exc.status_code == 401:
1181
+ selection = await self._select_account_with_budget_compatible(
1182
+ deadline,
1183
+ request_id=request_id,
1184
+ kind=request_kind,
1185
+ api_key=api_key,
1186
+ affinity_policy=affinity,
1187
+ prefer_earlier_reset_accounts=settings.prefer_earlier_reset_accounts,
1188
+ prefer_earlier_reset_window=_prefer_earlier_reset_window(settings),
1189
+ routing_strategy=routing_strategy,
1190
+ model=selection_model,
1191
+ exclude_account_ids={account.id},
1192
+ )
1193
+ if selection.account is not None:
1194
+ account = selection.account
1195
+ account_id_value = account.id
1196
+ account = await self._ensure_fresh_with_budget_or_auth_error(
1197
+ account,
1198
+ timeout_seconds=self._remaining_budget_seconds(deadline),
1199
+ )
1200
+ try:
1201
+ response = await _call_goal(account)
1202
+ await self._load_balancer.record_success(account)
1203
+ log_status = "success"
1204
+ return response
1205
+ except ProxyResponseError as failover_exc:
1206
+ await self._handle_proxy_error(account, failover_exc)
1207
+ raise
1208
+ raise
1209
+ except RefreshError as refresh_exc:
1210
+ if refresh_exc.is_permanent:
1211
+ failed_account = _refresh_error_failed_account(refresh_exc, account)
1212
+ account_id_value = failed_account.id
1213
+ await self._load_balancer.mark_permanent_failure(failed_account, refresh_exc.code)
1214
+ raise exc
1215
+ except (aiohttp.ClientError, asyncio.TimeoutError) as timeout_exc:
1216
+ logger.warning(
1217
+ "Thread goal forced refresh/connect failed request_id=%s operation=%s account_id=%s",
1218
+ request_id,
1219
+ operation,
1220
+ account.id,
1221
+ exc_info=True,
1222
+ )
1223
+ _raise_proxy_unavailable(str(timeout_exc) or "Request to upstream timed out")
1224
+ if operation == "get" and _is_missing_thread_goal_protocol_error(exc):
1225
+ log_status = "success"
1226
+ return {"goal": None}
1227
+ failed_account = _proxy_response_failed_account(exc, account)
1228
+ account_id_value = failed_account.id
1229
+ await self._handle_proxy_error(failed_account, exc)
1230
+ raise
1231
+ except ProxyResponseError as exc:
1232
+ failed_account = getattr(exc, _FAILED_ACCOUNT_ATTR, None)
1233
+ if isinstance(failed_account, Account):
1234
+ account_id_value = failed_account.id
1235
+ failure_metadata = _request_log_failure_metadata(exc)
1236
+ error = _parse_openai_error(exc.payload)
1237
+ log_error_code = log_error_code or _normalize_error_code(
1238
+ error.code if error else None,
1239
+ error.type if error else None,
1240
+ )
1241
+ log_error_message = log_error_message or (error.message if error else None)
1242
+ raise
1243
+ except UpstreamProxyRouteError as exc:
1244
+ route_fail_closed_reason = exc.reason
1245
+ log_error_code = "upstream_proxy_unavailable"
1246
+ log_error_message = exc.reason
1247
+ raise ProxyResponseError(
1248
+ 502,
1249
+ openai_error("upstream_proxy_unavailable", f"Upstream proxy route unavailable: {exc.reason}"),
1250
+ ) from exc
1251
+ finally:
1252
+ await self._write_request_log(
1253
+ account_id=account_id_value,
1254
+ api_key=api_key,
1255
+ request_id=request_id,
1256
+ model=None,
1257
+ latency_ms=int((self._clock.monotonic() - start) * 1000),
1258
+ status=log_status,
1259
+ error_code=log_error_code,
1260
+ error_message=log_error_message,
1261
+ transport=_REQUEST_TRANSPORT_HTTP,
1262
+ failure_phase=failure_metadata.failure_phase,
1263
+ failure_detail=failure_metadata.failure_detail,
1264
+ failure_exception_type=failure_metadata.failure_exception_type,
1265
+ upstream_status_code=failure_metadata.upstream_status_code,
1266
+ upstream_error_code=failure_metadata.upstream_error_code,
1267
+ bridge_stage=failure_metadata.bridge_stage,
1268
+ upstream_proxy_route_mode=route_mode,
1269
+ upstream_proxy_pool_id=route_pool_id,
1270
+ upstream_proxy_endpoint_id=route_endpoint_id,
1271
+ upstream_proxy_fallback_used=route_fallback_used if route_endpoint_id else None,
1272
+ upstream_proxy_fail_closed_reason=route_fail_closed_reason,
1273
+ useragent=useragent,
1274
+ useragent_group=useragent_group,
1275
+ conversation_id=conversation_id,
1276
+ )
1277
+
1278
+ async def _acquire_request_state_response_create_admission(
1279
+ self,
1280
+ request_state: _WebSocketRequestState,
1281
+ *,
1282
+ response_create_gate: asyncio.Semaphore,
1283
+ bridge_session: "_HTTPBridgeSession | None" = None,
1284
+ compact: bool = False,
1285
+ account_id: str | None = None,
1286
+ surface: str = "websocket",
1287
+ routing_tunables: RoutingTunables | None = None,
1288
+ dashboard_settings: DashboardSettings | None = None,
1289
+ ) -> None:
1290
+ scheduler = self._scheduler
1291
+ timeout_seconds = _proxy_admission_wait_timeout_seconds()
1292
+ if bridge_session is not None:
1293
+ timeout_seconds = _http_bridge_admission_timeout_seconds(
1294
+ request_state, timeout_seconds, get_settings(), now=self._clock.monotonic()
1295
+ )
1296
+ request_state.response_create_gate = response_create_gate
1297
+ request_state.response_create_gate_wait_started_at = self._clock.monotonic()
1298
+ if account_id is not None:
1299
+ # One cached snapshot for this lease operation. A caller that
1300
+ # already resolved the row for the same turn passes it in, and then
1301
+ # this helper awaits nothing here at all -- which the prewarm path
1302
+ # needs: it admits its warm-up while holding the session's
1303
+ # ``prewarm_lock``, and a cache refresh behind this read runs a DB
1304
+ # query under a process-global lock, so one stalled refresh would
1305
+ # suspend that critical section (issues #1971/#1972 wedged every
1306
+ # keyed submit on exactly this pattern). Tunables resolved by the
1307
+ # caller (bridge submit) still override the snapshot's.
1308
+ settings = dashboard_settings if dashboard_settings is not None else await get_settings_cache().get()
1309
+ request_state.account_response_create_lease = await self._acquire_account_response_create_lease_or_overload(
1310
+ account_id=account_id,
1311
+ request_id=request_state.request_id,
1312
+ surface=surface,
1313
+ concurrency_caps=effective_account_concurrency_caps(settings),
1314
+ routing_tunables=routing_tunables or effective_routing_tunables(settings),
1315
+ )
1316
+ request_state.account_response_create_release = self._load_balancer.release_account_lease
1317
+ try:
1318
+ await scheduler.wait_for(response_create_gate.acquire(), timeout=timeout_seconds)
1319
+ except TimeoutError as exc:
1320
+ await self._release_request_state_account_response_create_lease(request_state)
1321
+ request_state.response_create_gate = None
1322
+ request_state.response_create_gate_acquired = False
1323
+ request_state.awaiting_response_created = False
1324
+ pending_count = None
1325
+ queued_count = None
1326
+ pending_request_ids: list[str] | None = None
1327
+ pending_request_ages_seconds: list[float] | None = None
1328
+ should_retire_stuck_session = False
1329
+ stale_pending_requests_to_fail: list[_WebSocketRequestState] = []
1330
+ retry_circuit_attempt_selection = None
1331
+ if bridge_session is not None:
1332
+ now = self._clock.monotonic()
1333
+ stale_gate_snapshot = await self._snapshot_http_bridge_stale_gate_state(bridge_session, now=now)
1334
+ pending_states = stale_gate_snapshot.pending_states
1335
+ pending_count = len(pending_states)
1336
+ queued_count = stale_gate_snapshot.queued_count
1337
+ threshold_seconds = stale_gate_snapshot.threshold_seconds
1338
+ stale_pending_requests_to_fail = stale_gate_snapshot.stale_request_states
1339
+ should_retire_stuck_session = stale_gate_snapshot.should_retire
1340
+ retry_circuit_attempt_selection = stale_gate_snapshot.retry_circuit_attempt_selection
1341
+ pending_request_ids = [state.request_log_id or state.request_id for state in pending_states]
1342
+ pending_request_ages_seconds = [max(0.0, now - state.started_at) for state in pending_states]
1343
+ if not should_retire_stuck_session and any(
1344
+ max(0.0, now - state.started_at) >= threshold_seconds for state in pending_states
1345
+ ):
1346
+ # A gate waiter starved past the stuck threshold without the
1347
+ # watchdog firing: dump every pending state's verdict inputs
1348
+ # so the blocking condition is identifiable from prod logs.
1349
+ logger.warning(
1350
+ "http_bridge_stuck_watchdog_skipped session_closed=%s candidates=%s states=%s",
1351
+ bridge_session.closed,
1352
+ len(stale_pending_requests_to_fail),
1353
+ "; ".join(
1354
+ (
1355
+ f"id={state.request_log_id or state.request_id}"
1356
+ f" transport={state.transport}"
1357
+ f" skip_log={state.skip_request_log}"
1358
+ f" draining={state.draining_until_terminal}"
1359
+ f" prev_resp={state.previous_response_id is not None}"
1360
+ f" hard_anchor={state.hard_continuity_anchor}"
1361
+ f" gate_acq={state.response_create_gate_acquired}"
1362
+ f" awaiting={state.awaiting_response_created}"
1363
+ f" resp_id={state.response_id is not None}"
1364
+ f" events={state.response_event_count}"
1365
+ f" created_ms={state.latency_response_created_ms}"
1366
+ f" dsvis={state.downstream_visible}"
1367
+ f" age={max(0.0, now - state.started_at):.0f}"
1368
+ f" gate_wait_age={max(0.0, now - state.response_create_gate_wait_started_at) if state.response_create_gate_wait_started_at is not None else None}" # noqa: E501
1369
+ )
1370
+ for state in pending_states
1371
+ ),
1372
+ )
1373
+ _log_http_bridge_startup_wait_timeout(
1374
+ stage="response_create_gate",
1375
+ timeout_seconds=timeout_seconds,
1376
+ key=bridge_session.key if bridge_session is not None else None,
1377
+ request_id=request_state.request_id,
1378
+ request_model=request_state.model,
1379
+ pending_count=pending_count,
1380
+ queued_count=queued_count,
1381
+ available=getattr(response_create_gate, "_value", None),
1382
+ pending_request_ids=pending_request_ids,
1383
+ pending_request_ages_seconds=pending_request_ages_seconds,
1384
+ )
1385
+ if bridge_session is not None and stale_pending_requests_to_fail:
1386
+ await self._fail_stale_http_bridge_pending_requests(
1387
+ bridge_session,
1388
+ stale_pending_requests_to_fail,
1389
+ detail="response_create_gate_timeout_stuck_pending",
1390
+ retry_circuit_attempt_selection=retry_circuit_attempt_selection,
1391
+ )
1392
+ elif bridge_session is not None and should_retire_stuck_session:
1393
+ _record_http_bridge_stuck_retire(
1394
+ reason="response_create_gate_timeout_stuck_pending",
1395
+ session=bridge_session,
1396
+ )
1397
+ await self._retire_stale_pending_http_bridge_session(
1398
+ bridge_session,
1399
+ detail="response_create_gate_timeout_stuck_pending",
1400
+ retry_circuit_attempt_selection=retry_circuit_attempt_selection,
1401
+ )
1402
+ raise _http_bridge_startup_wait_timeout_error(
1403
+ "http_bridge_response_create_gate",
1404
+ code="response_create_gate_timeout",
1405
+ ) from exc
1406
+ except BaseException:
1407
+ await self._release_request_state_account_response_create_lease(request_state)
1408
+ request_state.response_create_gate = None
1409
+ request_state.response_create_gate_acquired = False
1410
+ request_state.awaiting_response_created = False
1411
+ raise
1412
+ request_state.response_create_gate_acquired = True
1413
+ request_state.awaiting_response_created = True
1414
+ if request_state.response_create_gate_wait_started_at is not None:
1415
+ request_state.latency_response_create_gate_wait_ms = int(
1416
+ max(0.0, self._clock.monotonic() - request_state.response_create_gate_wait_started_at) * 1000
1417
+ )
1418
+ try:
1419
+ request_state.response_create_admission = await self._get_work_admission().acquire_response_create(
1420
+ compact=compact
1421
+ )
1422
+ except BaseException:
1423
+ await self._release_request_state_account_response_create_lease(request_state)
1424
+ await _release_websocket_response_create_gate(request_state, response_create_gate, scheduler=scheduler)
1425
+ raise
1426
+ # The global response-create admission is a queue wait too, and a direct
1427
+ # WebSocket has no bridge-queue measurement: fold it into the gate wait
1428
+ # so the row carries the whole pre-send wait (the TTFT cohort sampler
1429
+ # skips any row with a non-zero wait).
1430
+ if request_state.response_create_gate_wait_started_at is not None:
1431
+ request_state.latency_response_create_gate_wait_ms = int(
1432
+ max(0.0, self._clock.monotonic() - request_state.response_create_gate_wait_started_at) * 1000
1433
+ )
1434
+
1435
+ async def _release_request_state_account_response_create_lease(
1436
+ self,
1437
+ request_state: "_WebSocketRequestState",
1438
+ ) -> None:
1439
+ lease = request_state.account_response_create_lease
1440
+ request_state.account_response_create_lease = None
1441
+ request_state.account_response_create_release = None
1442
+ await self._load_balancer.release_account_lease(lease)
1443
+
1444
+ async def _select_account_with_budget_compatible(self, deadline: float, **kwargs: object) -> AccountSelection:
1445
+ affinity_policy = kwargs.pop("affinity_policy", None)
1446
+ if isinstance(affinity_policy, _AffinityPolicy):
1447
+ # Expand once at the compatibility edge so transport callers cannot drift.
1448
+ kwargs.update(affinity_policy.selection_kwargs())
1449
+ required_capability_kwargs = {}
1450
+ if kwargs.get("require_security_work_authorized") is True:
1451
+ required_capability_kwargs["require_security_work_authorized"] = kwargs.pop(
1452
+ "require_security_work_authorized"
1453
+ )
1454
+ return await _call_with_supported_optional_kwargs(
1455
+ self._select_account_with_budget,
1456
+ deadline,
1457
+ optional_kwargs=kwargs,
1458
+ **required_capability_kwargs,
1459
+ )
1460
+
1461
+ @asynccontextmanager
1462
+ async def _accounts_refresh_scope(self) -> AsyncIterator[AccountsRepositoryPort]:
1463
+ # A self-contained repo prevents request cancellation from closing the
1464
+ # session under AuthManager's shielded refresh and stranding a connection.
1465
+ async with self._repo_factory() as repos:
1466
+ yield repos.accounts
1467
+
1468
+ async def _ensure_fresh(
1469
+ self,
1470
+ account: Account,
1471
+ *,
1472
+ force: bool = False,
1473
+ timeout_seconds: float | None = None,
1474
+ redact_sensitive_details: bool = False,
1475
+ ) -> Account:
1476
+ token = push_token_refresh_timeout_override(timeout_seconds)
1477
+ try:
1478
+ async with self._repo_factory() as repos:
1479
+ auth_manager = AuthManager(
1480
+ repos.accounts,
1481
+ acquire_refresh_admission=self._get_work_admission().acquire_token_refresh,
1482
+ refresh_repo_factory=self._accounts_refresh_scope,
1483
+ redact_sensitive_details=redact_sensitive_details,
1484
+ )
1485
+ refresh = auth_manager.ensure_fresh(account, force=force)
1486
+ if timeout_seconds is None:
1487
+ return await refresh
1488
+ return await self._scheduler.wait_for(refresh, timeout=max(0.001, timeout_seconds))
1489
+ finally:
1490
+ pop_token_refresh_timeout_override(token)
1491
+
1492
+ async def _ensure_fresh_with_budget(
1493
+ self,
1494
+ account: Account,
1495
+ *,
1496
+ force: bool = False,
1497
+ timeout_seconds: float | None = None,
1498
+ privacy_policy: CodexControlRequestPrivacyPolicy = CodexControlRequestPrivacyPolicy.STANDARD,
1499
+ ) -> Account:
1500
+ deadline = None if timeout_seconds is None else self._clock.monotonic() + timeout_seconds
1501
+ return await _recover_fresh_account(
1502
+ self,
1503
+ account,
1504
+ force=force,
1505
+ deadline=deadline,
1506
+ remaining_budget_seconds=self._remaining_budget_seconds,
1507
+ request_id=get_request_id(),
1508
+ privacy_policy=privacy_policy,
1509
+ )
1510
+
1511
+ async def _ensure_previsible_unary_fresh_with_failover(
1512
+ self,
1513
+ account: Account,
1514
+ *,
1515
+ deadline: float,
1516
+ request_id: str,
1517
+ kind: str,
1518
+ select_next_account: Callable[[set[str]], Awaitable[AccountSelection]],
1519
+ strict_account_id: str | None = None,
1520
+ force: bool = False,
1521
+ max_account_attempts: int = 2,
1522
+ privacy_policy: CodexControlRequestPrivacyPolicy = CodexControlRequestPrivacyPolicy.STANDARD,
1523
+ ) -> Account:
1524
+ current: Account = account
1525
+ excluded_account_ids: set[str] = set()
1526
+ attempt = 0
1527
+ force_current = force
1528
+ while True:
1529
+ attempt += 1
1530
+ remaining_budget = self._remaining_budget_seconds(deadline)
1531
+ if remaining_budget <= 0:
1532
+ logger.warning(
1533
+ "%s request budget exhausted before freshness check request_id=%s account_id=%s",
1534
+ kind,
1535
+ request_id,
1536
+ "<redacted>" if privacy_policy.redacts_sensitive_details else current.id,
1537
+ )
1538
+ _raise_proxy_budget_exhausted()
1539
+ try:
1540
+ if privacy_policy.redacts_sensitive_details:
1541
+ return await self._ensure_fresh_with_budget(
1542
+ current,
1543
+ force=force_current,
1544
+ timeout_seconds=remaining_budget,
1545
+ privacy_policy=privacy_policy,
1546
+ )
1547
+ return await self._ensure_fresh_with_budget(
1548
+ current,
1549
+ force=force_current,
1550
+ timeout_seconds=remaining_budget,
1551
+ )
1552
+ except RefreshError as exc:
1553
+ if not (is_transient_refresh_contention(exc) or exc.transport_error):
1554
+ # Permanent / non-transport refresh failures keep their prior
1555
+ # escalation: tag the failed account and propagate.
1556
+ setattr(exc, _FAILED_ACCOUNT_ATTR, current)
1557
+ raise
1558
+ current = await failover_after_previsible_refresh_error(
1559
+ self,
1560
+ exc,
1561
+ current,
1562
+ attempt=attempt,
1563
+ max_account_attempts=max_account_attempts,
1564
+ strict_account_id=strict_account_id,
1565
+ excluded_account_ids=excluded_account_ids,
1566
+ select_next_account=select_next_account,
1567
+ request_id=request_id,
1568
+ kind=kind,
1569
+ privacy_policy=privacy_policy,
1570
+ )
1571
+ force_current = False
1572
+ continue
1573
+ except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
1574
+ current = await failover_after_previsible_refresh_error(
1575
+ self,
1576
+ exc,
1577
+ current,
1578
+ attempt=attempt,
1579
+ max_account_attempts=max_account_attempts,
1580
+ strict_account_id=strict_account_id,
1581
+ excluded_account_ids=excluded_account_ids,
1582
+ select_next_account=select_next_account,
1583
+ request_id=request_id,
1584
+ kind=kind,
1585
+ privacy_policy=privacy_policy,
1586
+ )
1587
+ force_current = False
1588
+
1589
+ async def _retry_previsible_unary_call_failover(
1590
+ self,
1591
+ exc: ProxyResponseError,
1592
+ account: Account,
1593
+ *,
1594
+ deadline: float,
1595
+ select_next_account: Callable[[set[str]], Awaitable[AccountSelection]],
1596
+ call_next: Callable[[Account], Awaitable[Any]],
1597
+ strict_account_id: str | None = None,
1598
+ privacy_policy: CodexControlRequestPrivacyPolicy = CodexControlRequestPrivacyPolicy.STANDARD,
1599
+ ) -> tuple[Account, Any] | None:
1600
+ async def _handle_proxy_error(account: Account, error: ProxyResponseError) -> None:
1601
+ if privacy_policy.redacts_sensitive_details:
1602
+ await self._handle_proxy_error(
1603
+ account,
1604
+ error,
1605
+ privacy_policy=privacy_policy,
1606
+ )
1607
+ return
1608
+ await self._handle_proxy_error(account, error)
1609
+
1610
+ async def _ensure_fresh(account: Account, *, force: bool = False) -> Account:
1611
+ return await ensure_fresh_with_auth_error(
1612
+ self,
1613
+ account,
1614
+ force=force,
1615
+ timeout_seconds=self._remaining_budget_seconds(deadline),
1616
+ privacy_policy=privacy_policy,
1617
+ )
1618
+
1619
+ if hasattr(exc, _FAILED_ACCOUNT_ATTR):
1620
+ return None
1621
+ if not _should_failover_previsible_unary_proxy_error(exc):
1622
+ return None
1623
+ failed_account = _proxy_response_failed_account(exc, account)
1624
+ if strict_account_id is not None and failed_account.id == strict_account_id:
1625
+ return None
1626
+ selection = await select_next_account({failed_account.id})
1627
+ if selection.account is None:
1628
+ return None
1629
+ await _handle_proxy_error(failed_account, exc)
1630
+ try:
1631
+ next_account = await _ensure_fresh(selection.account)
1632
+ except ProxyResponseError as failover_exc:
1633
+ failover_failed_account = _proxy_response_failed_account(failover_exc, selection.account)
1634
+ setattr(failover_exc, _FAILED_ACCOUNT_ATTR, failover_failed_account)
1635
+ if failover_exc.status_code != 401:
1636
+ await _handle_proxy_error(failover_failed_account, failover_exc)
1637
+ raise
1638
+ try:
1639
+ result = await call_next(next_account)
1640
+ except ProxyResponseError as failover_exc:
1641
+ failover_failed_account = _proxy_response_failed_account(failover_exc, next_account)
1642
+ setattr(failover_exc, _FAILED_ACCOUNT_ATTR, failover_failed_account)
1643
+ if failover_exc.status_code == 401:
1644
+ remaining_budget = self._remaining_budget_seconds(deadline)
1645
+ if remaining_budget <= 0:
1646
+ _raise_proxy_budget_exhausted()
1647
+ try:
1648
+ refreshed_account = await _ensure_fresh(next_account, force=True)
1649
+ except ProxyResponseError as refresh_exc:
1650
+ refresh_failed_account = _proxy_response_failed_account(refresh_exc, next_account)
1651
+ setattr(refresh_exc, _FAILED_ACCOUNT_ATTR, refresh_failed_account)
1652
+ if refresh_exc.status_code != 401:
1653
+ await _handle_proxy_error(refresh_failed_account, refresh_exc)
1654
+ raise
1655
+ try:
1656
+ retry_result = await call_next(refreshed_account)
1657
+ except ProxyResponseError as retry_exc:
1658
+ retry_failed_account = _proxy_response_failed_account(retry_exc, refreshed_account)
1659
+ setattr(retry_exc, _FAILED_ACCOUNT_ATTR, retry_failed_account)
1660
+ await _handle_proxy_error(retry_failed_account, retry_exc)
1661
+ raise
1662
+ await self._load_balancer.record_success(refreshed_account)
1663
+ return refreshed_account, retry_result
1664
+ await _handle_proxy_error(failover_failed_account, failover_exc)
1665
+ raise
1666
+ await self._load_balancer.record_success(next_account)
1667
+ return next_account, result
1668
+
1669
+ async def _ensure_fresh_with_budget_or_auth_error(
1670
+ self,
1671
+ account: Account,
1672
+ *,
1673
+ force: bool = False,
1674
+ timeout_seconds: float | None = None,
1675
+ error_type: str = "invalid_request_error",
1676
+ privacy_policy: CodexControlRequestPrivacyPolicy = CodexControlRequestPrivacyPolicy.STANDARD,
1677
+ ) -> Account:
1678
+ try:
1679
+ if privacy_policy.redacts_sensitive_details:
1680
+ return await self._ensure_fresh_with_budget(
1681
+ account,
1682
+ force=force,
1683
+ timeout_seconds=timeout_seconds,
1684
+ privacy_policy=privacy_policy,
1685
+ )
1686
+ return await self._ensure_fresh_with_budget(account, force=force, timeout_seconds=timeout_seconds)
1687
+ except RefreshError as refresh_exc:
1688
+ failed_account = _refresh_error_failed_account(refresh_exc, account)
1689
+ if is_transient_refresh_contention(refresh_exc):
1690
+ # Transient cross-replica refresh contention (benign claim
1691
+ # contention OR a post-exchange persist/status CAS conflict): the
1692
+ # account is healthy. Surface a retryable, unpenalized
1693
+ # ``upstream_unavailable`` instead of a bogus 401 ``invalid_api_key``
1694
+ # — matching the previsible-unary/compact/streaming/WebSocket paths
1695
+ # — so file-upload, transcription, and codex-control post-401 forced
1696
+ # refreshes fail retryably.
1697
+ raise_proxy_unavailable_for_claim_contention(
1698
+ refresh_exc.message or str(refresh_exc) or "Request to upstream timed out",
1699
+ failed_account,
1700
+ )
1701
+ if refresh_exc.transport_error:
1702
+ # Genuine OAuth transport failure (NOT claim contention): keeps its
1703
+ # penalizable retryable ``upstream_unavailable``.
1704
+ _raise_proxy_unavailable_for_account(
1705
+ refresh_exc.message or str(refresh_exc) or "Request to upstream timed out",
1706
+ failed_account,
1707
+ )
1708
+ if refresh_exc.is_permanent:
1709
+ await self._load_balancer.mark_permanent_failure(failed_account, refresh_exc.code)
1710
+ raise ProxyResponseError(
1711
+ 401,
1712
+ openai_error(
1713
+ "invalid_api_key",
1714
+ refresh_exc.message,
1715
+ error_type=error_type,
1716
+ ),
1717
+ ) from refresh_exc
1718
+ except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
1719
+ _raise_proxy_unavailable(str(exc) or "Request to upstream timed out")
1720
+
1721
+ async def _select_account_with_budget(
1722
+ self,
1723
+ deadline: float,
1724
+ *,
1725
+ request_id: str,
1726
+ kind: str,
1727
+ request_stage: str = "first_turn",
1728
+ api_key: ApiKeyData | None = None,
1729
+ sticky_key: str | None = None,
1730
+ sticky_kind: StickySessionKind | None = None,
1731
+ reallocate_sticky: bool = False,
1732
+ sticky_source: _CodexSessionSource | None = None,
1733
+ legacy_sticky_key: str | None = None,
1734
+ legacy_continuity_source: _CodexSessionSource | None = None,
1735
+ sticky_seed_key: str | None = None,
1736
+ sticky_seed_kind: StickySessionKind | None = None,
1737
+ spill_bare_session_on_account_cap: bool = False,
1738
+ abandon_unavailable_legacy_owner: bool = False,
1739
+ require_unambiguous_account: bool = False,
1740
+ sticky_max_age_seconds: int | None = None,
1741
+ prefer_earlier_reset_accounts: bool = False,
1742
+ prefer_earlier_reset_window: ResetPreferenceWindow = "secondary",
1743
+ routing_strategy: RoutingStrategy = "capacity_weighted",
1744
+ model: str | None = None,
1745
+ service_tier: str | None = None,
1746
+ additional_limit_name: str | None = None,
1747
+ exclude_account_ids: Collection[str] | None = None,
1748
+ preferred_account_id: str | None = None,
1749
+ preferred_account_is_continuity_owner: bool = False,
1750
+ preferred_account_overrides_single_account_routing: bool = False,
1751
+ require_security_work_authorized: bool = False,
1752
+ lease_kind: Literal["response_create", "stream"] | None = None,
1753
+ estimated_lease_tokens: float = 0.0,
1754
+ fallback_on_preferred_account_unavailable: bool = True,
1755
+ traffic_class: TrafficClass = TRAFFIC_CLASS_FOREGROUND,
1756
+ redact_sensitive_details: bool = False,
1757
+ ) -> AccountSelection:
1758
+ remaining_budget = self._remaining_budget_seconds(deadline)
1759
+ if remaining_budget <= 0:
1760
+ logger.warning(
1761
+ "%s request budget exhausted before account selection request_id=%s", kind.title(), request_id
1762
+ )
1763
+ _raise_proxy_budget_exhausted()
1764
+ scoped_account_ids = (
1765
+ set(api_key.assigned_account_ids)
1766
+ if api_key is not None and api_key.account_assignment_scope_enabled
1767
+ else None
1768
+ )
1769
+ effective_traffic_class = (
1770
+ TRAFFIC_CLASS_OPPORTUNISTIC
1771
+ if api_key is not None and api_key.traffic_class == TRAFFIC_CLASS_OPPORTUNISTIC
1772
+ else traffic_class
1773
+ )
1774
+ excluded_account_ids_set = set(exclude_account_ids or ())
1775
+
1776
+ def log_account_id(account_id: str | None) -> str | None:
1777
+ return "<redacted>" if redact_sensitive_details and account_id is not None else account_id
1778
+
1779
+ logger.info(
1780
+ "Proxy account selection start request_id=%s kind=%s request_stage=%s model=%s "
1781
+ "additional_limit=%s sticky=%s sticky_kind=%s reallocate_sticky=%s prefer_earlier_reset=%s "
1782
+ "routing_strategy=%s api_key_present=%s api_key_scope_enabled=%s scoped_count=%s "
1783
+ "excluded_count=%s preferred_account_id=%s remaining_budget=%.2f",
1784
+ request_id,
1785
+ kind,
1786
+ request_stage,
1787
+ model,
1788
+ additional_limit_name,
1789
+ bool(sticky_key),
1790
+ sticky_kind.value if sticky_kind is not None else None,
1791
+ reallocate_sticky,
1792
+ prefer_earlier_reset_accounts,
1793
+ routing_strategy,
1794
+ api_key is not None,
1795
+ bool(api_key is not None and api_key.account_assignment_scope_enabled),
1796
+ None if scoped_account_ids is None else len(scoped_account_ids),
1797
+ len(excluded_account_ids_set),
1798
+ log_account_id(preferred_account_id),
1799
+ remaining_budget,
1800
+ )
1801
+ try:
1802
+ with self._scheduler.fail_after(remaining_budget):
1803
+ settings = await get_settings_cache().get()
1804
+ concurrency_caps = effective_account_concurrency_caps(settings)
1805
+ routing_tunables = effective_routing_tunables(settings) # C2-2 routing/overload
1806
+ stream_reserve_slots = (
1807
+ (
1808
+ get_settings().proxy_account_stream_recovery_reserve
1809
+ if getattr(settings, "proxy_account_stream_recovery_reserve", None) is None
1810
+ else settings.proxy_account_stream_recovery_reserve
1811
+ )
1812
+ if lease_kind == "stream" and request_stage != "reattach"
1813
+ else 0
1814
+ )
1815
+ api_key_fair_share_threshold_pct = _selection_api_key_fair_share_threshold_pct(
1816
+ settings, lease_kind=lease_kind, request_stage=request_stage
1817
+ )
1818
+ api_key_id = api_key.id if api_key is not None else None
1819
+ required_preferred_account = (
1820
+ preferred_account_id is not None and not fallback_on_preferred_account_unavailable
1821
+ )
1822
+ required_continuity = required_preferred_account and preferred_account_is_continuity_owner
1823
+ single_account_routing_id: str | None = None
1824
+ if _routing_strategy(settings) == "single_account" and (
1825
+ not required_preferred_account
1826
+ or (required_continuity and not preferred_account_overrides_single_account_routing)
1827
+ ):
1828
+ selected_account_id = (settings.single_account_id or "").strip()
1829
+ if not selected_account_id:
1830
+ return AccountSelection(
1831
+ account=None,
1832
+ error_message="Single account routing is enabled but no account is selected",
1833
+ error_code="single_account_not_configured",
1834
+ )
1835
+ if selected_account_id in excluded_account_ids_set:
1836
+ return AccountSelection(
1837
+ account=None,
1838
+ error_message="Selected single account is unavailable",
1839
+ error_code="single_account_unavailable",
1840
+ )
1841
+ if scoped_account_ids is not None and selected_account_id not in scoped_account_ids:
1842
+ return AccountSelection(
1843
+ account=None,
1844
+ error_message="Selected single account is outside the API key account scope",
1845
+ error_code="single_account_scope_mismatch",
1846
+ )
1847
+ single_account_routing_id = selected_account_id
1848
+ routing_strategy = "single_account"
1849
+ preferred_eligible = (
1850
+ preferred_account_id is not None
1851
+ and preferred_account_id not in excluded_account_ids_set
1852
+ and (
1853
+ scoped_account_ids is None
1854
+ or preferred_account_id in scoped_account_ids
1855
+ or (required_continuity and not preferred_account_overrides_single_account_routing)
1856
+ )
1857
+ and (
1858
+ single_account_routing_id is None
1859
+ or preferred_account_id == single_account_routing_id
1860
+ or required_continuity
1861
+ )
1862
+ )
1863
+ if preferred_account_id is not None and not preferred_eligible:
1864
+ logger.warning(
1865
+ "Proxy preferred account skipped request_id=%s kind=%s request_stage=%s "
1866
+ "preferred_account_id=%s excluded=%s outside_api_key_scope=%s",
1867
+ request_id,
1868
+ kind,
1869
+ request_stage,
1870
+ log_account_id(preferred_account_id),
1871
+ preferred_account_id in excluded_account_ids_set,
1872
+ scoped_account_ids is not None and preferred_account_id not in scoped_account_ids,
1873
+ )
1874
+ if not fallback_on_preferred_account_unavailable:
1875
+ return AccountSelection(
1876
+ account=None,
1877
+ error_message="Preferred account is not available",
1878
+ error_code="preferred_account_unavailable",
1879
+ )
1880
+ if preferred_eligible:
1881
+ preferred_sticky_inputs = _AffinityPolicy.preferred_owner_sticky_inputs(
1882
+ sticky_key,
1883
+ sticky_kind,
1884
+ reallocate_sticky,
1885
+ sticky_max_age_seconds,
1886
+ sticky_source,
1887
+ legacy_sticky_key,
1888
+ )
1889
+ preferred_selection = await self._load_balancer.select_account(
1890
+ dashboard_settings=settings, # C2-3 resilience toggles
1891
+ sticky_key=preferred_sticky_inputs[0],
1892
+ sticky_kind=preferred_sticky_inputs[1],
1893
+ reallocate_sticky=preferred_sticky_inputs[2],
1894
+ sticky_max_age_seconds=preferred_sticky_inputs[3],
1895
+ sticky_source=preferred_sticky_inputs[4],
1896
+ legacy_sticky_key=preferred_sticky_inputs[5],
1897
+ legacy_continuity_source=legacy_continuity_source,
1898
+ # Exact ownership chooses the account; a first-ever thread
1899
+ # still seeds atomically without overwriting a process default.
1900
+ sticky_seed_key=sticky_seed_key,
1901
+ sticky_seed_kind=sticky_seed_kind,
1902
+ abandon_unavailable_legacy_owner=abandon_unavailable_legacy_owner,
1903
+ prefer_earlier_reset_accounts=prefer_earlier_reset_accounts,
1904
+ prefer_earlier_reset_window=prefer_earlier_reset_window,
1905
+ routing_strategy=routing_strategy,
1906
+ relative_availability_power=_relative_availability_power(settings),
1907
+ relative_availability_top_k=_relative_availability_top_k(settings),
1908
+ routing_tunables=routing_tunables,
1909
+ model=model,
1910
+ service_tier=service_tier,
1911
+ additional_limit_name=additional_limit_name,
1912
+ account_ids=(
1913
+ {single_account_routing_id}
1914
+ if required_continuity and single_account_routing_id is not None
1915
+ else scoped_account_ids
1916
+ ),
1917
+ required_account_id=preferred_account_id,
1918
+ required_account_is_ownership_constraint=required_preferred_account,
1919
+ required_continuity_owner=required_continuity,
1920
+ require_unambiguous_account=require_unambiguous_account,
1921
+ require_security_work_authorized=require_security_work_authorized,
1922
+ budget_threshold_pct=_sticky_reallocation_primary_budget_threshold_pct(settings),
1923
+ secondary_budget_threshold_pct=_sticky_reallocation_secondary_budget_threshold_pct(settings),
1924
+ lease_kind=lease_kind,
1925
+ estimated_lease_tokens=estimated_lease_tokens,
1926
+ stream_reserve_slots=stream_reserve_slots,
1927
+ traffic_class=effective_traffic_class,
1928
+ concurrency_caps=concurrency_caps,
1929
+ redact_sensitive_details=redact_sensitive_details,
1930
+ allow_usage_exhaustion_error=not required_preferred_account,
1931
+ api_key_id=api_key_id,
1932
+ api_key_stream_fair_share_threshold_pct=api_key_fair_share_threshold_pct,
1933
+ )
1934
+ if preferred_selection.account is not None:
1935
+ logger.info(
1936
+ "Selected preferred account request_id=%s kind=%s request_stage=%s account_id=%s",
1937
+ request_id,
1938
+ kind,
1939
+ request_stage,
1940
+ log_account_id(preferred_account_id),
1941
+ )
1942
+ return preferred_selection
1943
+ if not fallback_on_preferred_account_unavailable:
1944
+ logger.warning(
1945
+ "Proxy preferred account unavailable request_id=%s kind=%s request_stage=%s "
1946
+ "preferred_account_id=%s error_code=%s error=%s",
1947
+ request_id,
1948
+ kind,
1949
+ request_stage,
1950
+ log_account_id(preferred_account_id),
1951
+ preferred_selection.error_code,
1952
+ preferred_selection.error_message,
1953
+ )
1954
+ return preferred_selection
1955
+ selection = await self._load_balancer.select_account(
1956
+ dashboard_settings=settings, # C2-3 resilience toggles
1957
+ sticky_key=sticky_key,
1958
+ sticky_kind=sticky_kind,
1959
+ reallocate_sticky=reallocate_sticky,
1960
+ sticky_source=sticky_source,
1961
+ legacy_sticky_key=legacy_sticky_key,
1962
+ legacy_continuity_source=legacy_continuity_source,
1963
+ sticky_seed_key=sticky_seed_key,
1964
+ sticky_seed_kind=sticky_seed_kind,
1965
+ spill_bare_session_on_account_cap=_AffinityPolicy.cap_spillover_allowed(
1966
+ spill_bare_session_on_account_cap,
1967
+ preferred_account_id,
1968
+ request_stage,
1969
+ ),
1970
+ abandon_unavailable_legacy_owner=abandon_unavailable_legacy_owner,
1971
+ require_unambiguous_account=require_unambiguous_account,
1972
+ sticky_max_age_seconds=sticky_max_age_seconds,
1973
+ prefer_earlier_reset_accounts=prefer_earlier_reset_accounts,
1974
+ prefer_earlier_reset_window=prefer_earlier_reset_window,
1975
+ routing_strategy=routing_strategy,
1976
+ relative_availability_power=_relative_availability_power(settings),
1977
+ relative_availability_top_k=_relative_availability_top_k(settings),
1978
+ model=model,
1979
+ service_tier=service_tier,
1980
+ additional_limit_name=additional_limit_name,
1981
+ account_ids=scoped_account_ids,
1982
+ required_account_id=single_account_routing_id,
1983
+ exclude_account_ids=excluded_account_ids_set,
1984
+ require_security_work_authorized=require_security_work_authorized,
1985
+ budget_threshold_pct=_sticky_reallocation_primary_budget_threshold_pct(settings),
1986
+ secondary_budget_threshold_pct=_sticky_reallocation_secondary_budget_threshold_pct(settings),
1987
+ lease_kind=lease_kind,
1988
+ estimated_lease_tokens=estimated_lease_tokens,
1989
+ stream_reserve_slots=stream_reserve_slots,
1990
+ traffic_class=effective_traffic_class,
1991
+ concurrency_caps=concurrency_caps,
1992
+ redact_sensitive_details=redact_sensitive_details,
1993
+ api_key_id=api_key_id,
1994
+ api_key_stream_fair_share_threshold_pct=api_key_fair_share_threshold_pct,
1995
+ routing_tunables=routing_tunables,
1996
+ )
1997
+ if selection.account is not None and selection.account.id in excluded_account_ids_set:
1998
+ logger.warning(
1999
+ "Proxy account selection returned excluded account request_id=%s kind=%s request_stage=%s "
2000
+ "account_id=%s excluded_count=%s",
2001
+ request_id,
2002
+ kind,
2003
+ request_stage,
2004
+ log_account_id(selection.account.id),
2005
+ len(excluded_account_ids_set),
2006
+ )
2007
+ return AccountSelection(
2008
+ account=None,
2009
+ error_message="No active accounts available",
2010
+ error_code="no_accounts",
2011
+ )
2012
+ logger.info(
2013
+ "Proxy account selection result request_id=%s kind=%s request_stage=%s model=%s "
2014
+ "selected_account_id=%s error_code=%s error=%s scoped_count=%s excluded_count=%s",
2015
+ request_id,
2016
+ kind,
2017
+ request_stage,
2018
+ model,
2019
+ log_account_id(selection.account.id) if selection.account is not None else None,
2020
+ selection.error_code,
2021
+ selection.error_message,
2022
+ None if scoped_account_ids is None else len(scoped_account_ids),
2023
+ len(excluded_account_ids_set),
2024
+ )
2025
+ return selection
2026
+ except TimeoutError:
2027
+ logger.warning("%s account selection exceeded request budget request_id=%s", kind.title(), request_id)
2028
+ _raise_proxy_budget_exhausted()
2029
+
2030
+ async def _acquire_account_response_create_lease_or_overload(
2031
+ self,
2032
+ *,
2033
+ account_id: str,
2034
+ request_id: str,
2035
+ surface: str,
2036
+ concurrency_caps: AccountConcurrencyCaps,
2037
+ routing_tunables: RoutingTunables | None = None,
2038
+ ) -> AccountLease:
2039
+ lease = await self._load_balancer.acquire_account_lease(
2040
+ account_id,
2041
+ kind="response_create",
2042
+ concurrency_caps=concurrency_caps,
2043
+ routing_tunables=routing_tunables,
2044
+ )
2045
+ if lease is not None:
2046
+ return lease
2047
+ inflight_create, inflight_stream, leased_tokens = await self._load_balancer.account_pressure_snapshot(
2048
+ account_id
2049
+ )
2050
+ logger.warning(
2051
+ "Responses account response-create cap reached request_id=%s surface=%s account_id=%s "
2052
+ "inflight_create=%s inflight_stream=%s leased_tokens=%.3f",
2053
+ request_id,
2054
+ surface,
2055
+ account_id,
2056
+ inflight_create,
2057
+ inflight_stream,
2058
+ leased_tokens,
2059
+ )
2060
+ raise ProxyResponseError(
2061
+ 429,
2062
+ openai_error(
2063
+ "account_response_create_cap",
2064
+ "Account response-create capacity is exhausted",
2065
+ error_type="rate_limit_error",
2066
+ ),
2067
+ )
2068
+
2069
+ async def check_opportunistic_admission(
2070
+ self,
2071
+ *,
2072
+ api_key: ApiKeyData | None,
2073
+ model: str | None,
2074
+ lease_kind: AccountLeaseKind | None = None,
2075
+ service_tier: str | None = None,
2076
+ observe_only: bool = False,
2077
+ ) -> AccountSelection:
2078
+ settings = await get_settings_cache().get()
2079
+ return await self._load_balancer.check_opportunistic_admission(
2080
+ dashboard_settings=settings, # C2-3 resilience toggles
2081
+ model=model,
2082
+ service_tier=service_tier,
2083
+ observe_only=observe_only,
2084
+ account_ids=opportunistic_admission_account_scope(settings, api_key),
2085
+ prefer_earlier_reset_accounts=settings.prefer_earlier_reset_accounts,
2086
+ prefer_earlier_reset_window=_prefer_earlier_reset_window(settings),
2087
+ routing_strategy=_routing_strategy(settings),
2088
+ budget_threshold_pct=_sticky_reallocation_primary_budget_threshold_pct(settings),
2089
+ secondary_budget_threshold_pct=_sticky_reallocation_secondary_budget_threshold_pct(settings),
2090
+ lease_kind=lease_kind,
2091
+ concurrency_caps=effective_account_concurrency_caps(settings),
2092
+ routing_tunables=effective_routing_tunables(settings),
2093
+ stream_reserve_slots=(
2094
+ (
2095
+ get_settings().proxy_account_stream_recovery_reserve
2096
+ if getattr(settings, "proxy_account_stream_recovery_reserve", None) is None
2097
+ else settings.proxy_account_stream_recovery_reserve
2098
+ )
2099
+ if lease_kind == "stream"
2100
+ else 0
2101
+ ),
2102
+ )
2103
+
2104
+ async def _handle_proxy_error(
2105
+ self,
2106
+ account: Account,
2107
+ exc: ProxyResponseError,
2108
+ *,
2109
+ privacy_policy: CodexControlRequestPrivacyPolicy = CodexControlRequestPrivacyPolicy.STANDARD,
2110
+ ) -> None:
2111
+ if is_claim_contention_unpenalized(exc):
2112
+ # Transient refresh-claim contention: the account is healthy (a peer
2113
+ # replica merely held its refresh claim), so it must not be penalized.
2114
+ return
2115
+ error = _parse_openai_error(exc.payload)
2116
+ code = _normalize_error_code(
2117
+ error.code if error else None,
2118
+ error.type if error else None,
2119
+ )
2120
+ if _is_account_neutral_error_code(code):
2121
+ return
2122
+ if privacy_policy.redacts_sensitive_details:
2123
+ await self._handle_stream_error(
2124
+ account,
2125
+ _upstream_error_from_openai(error),
2126
+ code,
2127
+ http_status=exc.status_code,
2128
+ privacy_policy=privacy_policy,
2129
+ retry_after_seconds=exc.retry_after_seconds,
2130
+ )
2131
+ return
2132
+ await self._handle_stream_error(
2133
+ account,
2134
+ _upstream_error_from_openai(error),
2135
+ code,
2136
+ http_status=exc.status_code,
2137
+ retry_after_seconds=exc.retry_after_seconds,
2138
+ )
2139
+
2140
+
2141
+ def _previous_response_id_from_not_found_message(message: str | None) -> str | None:
2142
+ return previous_response_id_from_not_found_message(message)
2143
+
2144
+
2145
+ def _message_mentions_previous_response_id(message: str | None, previous_response_id: str | None) -> bool:
2146
+ if message is None or previous_response_id is None:
2147
+ return False
2148
+ normalized_message = " ".join(message.split())
2149
+ normalized_previous_response_id = previous_response_id.strip()
2150
+ if not normalized_previous_response_id:
2151
+ return False
2152
+ identifier_pattern = re.escape(normalized_previous_response_id)
2153
+ return (
2154
+ re.search(
2155
+ rf"(?<![A-Za-z0-9_-]){identifier_pattern}(?![A-Za-z0-9_-])",
2156
+ normalized_message,
2157
+ )
2158
+ is not None
2159
+ )
2160
+
2161
+
2162
+ _MISSING_TOOL_OUTPUT_MESSAGE_PREFIXES = (
2163
+ "no tool output found for function call call_",
2164
+ "no tool output found for custom tool call call_",
2165
+ "no tool output found for apply patch call call_",
2166
+ "no tool output found for tool search call call_",
2167
+ )
2168
+
2169
+
2170
+ def _is_missing_tool_output_message(message: str | None) -> bool:
2171
+ if message is None:
2172
+ return False
2173
+ normalized = " ".join(message.lower().split())
2174
+ return normalized.startswith(_MISSING_TOOL_OUTPUT_MESSAGE_PREFIXES)
2175
+
2176
+
2177
+ def _is_missing_tool_output_error(
2178
+ *,
2179
+ code: str | None,
2180
+ param: OpenAIErrorParam | JsonValue,
2181
+ message: str | None,
2182
+ ) -> bool:
2183
+ param_state = coerce_error_param(param)
2184
+ if code != "invalid_request_error" or param_state.normalized != "input" or param_state.malformed:
2185
+ return False
2186
+ return _is_missing_tool_output_message(message)
2187
+
2188
+
2189
+ def _is_previous_response_not_found_error(
2190
+ *,
2191
+ code: str | None,
2192
+ param: OpenAIErrorParam | JsonValue,
2193
+ message: str | None,
2194
+ ) -> bool:
2195
+ return is_previous_response_not_found_error(code=code, param=param, message=message)
2196
+
2197
+
2198
+ def _compact_previous_response_not_found_error(exc: ProxyResponseError) -> ProxyResponseError | None:
2199
+ error = _parse_openai_error(exc.payload)
2200
+ if error is None:
2201
+ return None
2202
+ code = _normalize_error_code(error.code, error.type)
2203
+ if not _is_previous_response_not_found_error(
2204
+ code=code,
2205
+ param=error.param_state,
2206
+ message=error.message,
2207
+ ):
2208
+ return None
2209
+ return ProxyResponseError(
2210
+ 502,
2211
+ previous_response_stream_incomplete_error(),
2212
+ failure_phase=exc.failure_phase,
2213
+ retryable_same_contract=False,
2214
+ failure_detail="previous_response_not_found",
2215
+ failure_exception_type=exc.failure_exception_type,
2216
+ upstream_status_code=exc.upstream_status_code if exc.upstream_status_code is not None else exc.status_code,
2217
+ upstream_error_code=code,
2218
+ )
2219
+
2220
+
2221
+ def _proxy_response_error_code(exc: ProxyResponseError) -> str | None:
2222
+ error = _parse_openai_error(exc.payload)
2223
+ if error is None:
2224
+ return None
2225
+ return _normalize_error_code(error.code, error.type)
2226
+
2227
+
2228
+ _LOCAL_PROXY_ERROR_CODES = frozenset(
2229
+ {
2230
+ "bridge_owner_forward_failed",
2231
+ "bridge_instance_mismatch",
2232
+ "bridge_owner_unreachable",
2233
+ "preferred_account_unavailable",
2234
+ "previous_response_owner_unavailable",
2235
+ "insufficient_image_quota",
2236
+ "ip_forbidden",
2237
+ "no_accounts",
2238
+ "no_plan_support_for_model",
2239
+ "additional_quota_data_unavailable",
2240
+ "no_additional_quota_eligible_accounts",
2241
+ "payload_too_large",
2242
+ "proxy_overloaded",
2243
+ "upstream_request_timeout",
2244
+ "upstream_unavailable",
2245
+ }
2246
+ )
2247
+
2248
+
2249
+ def _request_log_failure_metadata(
2250
+ exc: ProxyResponseError,
2251
+ *,
2252
+ bridge_stage: str | None = None,
2253
+ ) -> _RequestLogFailureMetadata:
2254
+ upstream_error_code = exc.upstream_error_code or _proxy_response_error_code(exc)
2255
+ resolved_bridge_stage = bridge_stage
2256
+ if resolved_bridge_stage is None and (
2257
+ exc.failure_phase in {"owner_forward", "owner_forward_status"}
2258
+ or upstream_error_code in {"bridge_owner_unreachable", "bridge_owner_forward_failed"}
2259
+ ):
2260
+ resolved_bridge_stage = "owner_forward"
2261
+ upstream_status_code = exc.upstream_status_code
2262
+ if upstream_status_code is None and _should_infer_upstream_status_from_proxy_error(exc, upstream_error_code):
2263
+ upstream_status_code = exc.status_code
2264
+ return _RequestLogFailureMetadata(
2265
+ failure_phase=exc.failure_phase,
2266
+ failure_detail=exc.failure_detail,
2267
+ failure_exception_type=exc.failure_exception_type,
2268
+ upstream_status_code=upstream_status_code,
2269
+ upstream_error_code=upstream_error_code,
2270
+ bridge_stage=resolved_bridge_stage,
2271
+ )
2272
+
2273
+
2274
+ def _previous_response_id_from_payload(payload: Mapping[str, JsonValue] | None) -> str | None:
2275
+ if not isinstance(payload, Mapping):
2276
+ return None
2277
+ previous_response_id = payload.get("previous_response_id")
2278
+ if isinstance(previous_response_id, str) and previous_response_id.strip():
2279
+ return previous_response_id.strip()
2280
+ return None
2281
+
2282
+
2283
+ def _partial_output_proxy_error_event_block(
2284
+ exc: ProxyResponseError,
2285
+ *,
2286
+ response_id: str,
2287
+ previous_response_id: str | None,
2288
+ preferred_account_id: str | None,
2289
+ default_code: str,
2290
+ default_message: str,
2291
+ ) -> str:
2292
+ error = _parse_openai_error(exc.payload)
2293
+ error_code = _normalize_error_code(
2294
+ error.code if error else None,
2295
+ error.type if error else None,
2296
+ )
2297
+ error_message = error.message if error else None
2298
+ effective_previous_response_id = previous_response_id or _previous_response_id_from_not_found_message(
2299
+ error_message,
2300
+ )
2301
+ rewritten_error = _rewrite_previous_response_stream_error(
2302
+ previous_response_id=effective_previous_response_id,
2303
+ preferred_account_id=preferred_account_id,
2304
+ error_code=error_code,
2305
+ error_type=error.type if error else None,
2306
+ error_message=error_message,
2307
+ error_param=error.param_state if error else None,
2308
+ )
2309
+ if rewritten_error is not None:
2310
+ rewritten_code, rewritten_message, upstream_error_code = rewritten_error
2311
+ if upstream_error_code is None:
2312
+ event = response_failed_event(
2313
+ rewritten_code,
2314
+ rewritten_message,
2315
+ error_type="server_error",
2316
+ response_id=response_id,
2317
+ )
2318
+ return format_sse_event(event)
2319
+ event = response_failed_event(
2320
+ error_code or default_code,
2321
+ error_message or default_message,
2322
+ error_type=(error.type if error and error.type else "server_error"),
2323
+ response_id=response_id,
2324
+ error_param=error.param_state if error else None,
2325
+ )
2326
+ _apply_error_metadata(event["response"]["error"], error)
2327
+ return format_sse_event(event)
2328
+
2329
+
2330
+ def _routing_strategy(settings: DashboardSettings) -> RoutingStrategy:
2331
+ value = getattr(settings, "routing_strategy", None) or "capacity_weighted"
2332
+ if value == "single_account":
2333
+ return "single_account"
2334
+ if value == "sequential_drain":
2335
+ return "sequential_drain"
2336
+ if value == "reset_drain":
2337
+ return "reset_drain"
2338
+ if value == "round_robin":
2339
+ return "round_robin"
2340
+ if value == "usage_weighted":
2341
+ return "usage_weighted"
2342
+ if value == "relative_availability":
2343
+ return "relative_availability"
2344
+ if value == "fill_first":
2345
+ return "fill_first"
2346
+ return "capacity_weighted"
2347
+
2348
+
2349
+ def _relative_availability_power(settings: DashboardSettings) -> float:
2350
+ raw_value = getattr(settings, "relative_availability_power", None)
2351
+ value = float(raw_value) if raw_value is not None else 2.0
2352
+ return value if value > 0.0 else 2.0
2353
+
2354
+
2355
+ def _relative_availability_top_k(settings: DashboardSettings) -> int:
2356
+ raw_value = getattr(settings, "relative_availability_top_k", None)
2357
+ value = int(raw_value) if raw_value is not None else 5
2358
+ return min(max(value, 1), 20)
2359
+
2360
+
2361
+ def _prefer_earlier_reset_window(settings: DashboardSettings) -> ResetPreferenceWindow:
2362
+ return "primary" if getattr(settings, "prefer_earlier_reset_window", None) == "primary" else "secondary"
2363
+
2364
+
2365
+ def _sticky_reallocation_primary_budget_threshold_pct(settings: DashboardSettings) -> float:
2366
+ value = getattr(settings, "sticky_reallocation_primary_budget_threshold_pct", None)
2367
+ if value is None:
2368
+ value = getattr(settings, "sticky_reallocation_budget_threshold_pct", None)
2369
+ return float(value if value is not None else 95.0)
2370
+
2371
+
2372
+ def _sticky_reallocation_secondary_budget_threshold_pct(settings: DashboardSettings) -> float:
2373
+ value = getattr(settings, "sticky_reallocation_secondary_budget_threshold_pct", None)
2374
+ return float(value if value is not None else 100.0)
2375
+
2376
+
2377
+ def _proxy_request_timeout_event(request_id: str) -> ResponseFailedEvent:
2378
+ event = response_failed_event("upstream_request_timeout", "Proxy request budget exhausted", response_id=request_id)
2379
+ return synthetic_transport_failure_event(event)
2380
+
2381
+
2382
+ def _security_work_advisory_event(
2383
+ *,
2384
+ code: str,
2385
+ message: str,
2386
+ request_id: str | None,
2387
+ action: str,
2388
+ account_id: str | None = None,
2389
+ ) -> dict[str, JsonValue]:
2390
+ warning: dict[str, JsonValue] = {
2391
+ "code": code,
2392
+ "message": message,
2393
+ "category": "security_work_authorization",
2394
+ "action": action,
2395
+ }
2396
+ if request_id:
2397
+ warning["request_id"] = request_id
2398
+ if account_id:
2399
+ warning["account_id"] = account_id
2400
+ return {
2401
+ "type": "codex_lb.warning",
2402
+ "warning": warning,
2403
+ }
2404
+
2405
+
2406
+ def _is_security_work_authorization_required_error(code: str | None, message: str | None) -> bool:
2407
+ normalized_code = (code or "").strip().lower()
2408
+ if normalized_code == _SECURITY_WORK_AUTHORIZATION_REQUIRED_CODE:
2409
+ return True
2410
+ normalized_message = (message or "").strip().lower()
2411
+ if not normalized_message:
2412
+ return False
2413
+ return all(hint in normalized_message for hint in _SECURITY_WORK_AUTHORIZATION_REQUIRED_HINTS)
2414
+
2415
+
2416
+ def _raise_proxy_budget_exhausted() -> NoReturn:
2417
+ raise ProxyResponseError(
2418
+ 502,
2419
+ openai_error("upstream_request_timeout", "Proxy request budget exhausted"),
2420
+ )
2421
+
2422
+
2423
+ def _raise_proxy_unavailable(message: str) -> NoReturn:
2424
+ raise ProxyResponseError(
2425
+ 502,
2426
+ openai_error("upstream_unavailable", message),
2427
+ )
2428
+
2429
+
2430
+ _FAILED_ACCOUNT_ATTR = "_codex_lb_failed_account"
2431
+
2432
+
2433
+ def _proxy_response_failed_account(exc: ProxyResponseError, fallback: Account) -> Account:
2434
+ account = getattr(exc, _FAILED_ACCOUNT_ATTR, None)
2435
+ return account if isinstance(account, Account) else fallback
2436
+
2437
+
2438
+ def _refresh_error_failed_account(exc: RefreshError, fallback: Account) -> Account:
2439
+ account = getattr(exc, _FAILED_ACCOUNT_ATTR, None)
2440
+ return account if isinstance(account, Account) else fallback
2441
+
2442
+
2443
+ def _should_failover_previsible_unary_proxy_error(exc: ProxyResponseError) -> bool:
2444
+ if exc.failure_phase != "connect":
2445
+ return False
2446
+ error = _parse_openai_error(exc.payload)
2447
+ error_code = _normalize_error_code(error.code if error else None, error.type if error else None)
2448
+ # Typed transport provenance takes precedence over sanitized message text.
2449
+ return error_code == "upstream_unavailable" and (
2450
+ exc.retryable_same_contract
2451
+ if exc.failure_detail == "transport_error"
2452
+ else _should_retry_transient_stream_error("upstream_unavailable", error.message if error else None)
2453
+ )
2454
+
2455
+
2456
+ def _is_proxy_budget_exhausted_error(exc: ProxyResponseError) -> bool:
2457
+ error = _parse_openai_error(exc.payload)
2458
+ error_code = _normalize_error_code(error.code if error else None, error.type if error else None)
2459
+ error_message = error.message if error else None
2460
+ return error_code in {"upstream_request_timeout", "upstream_unavailable"} and (
2461
+ error_message == "Proxy request budget exhausted"
2462
+ )
2463
+
2464
+
2465
+ def _should_suppress_text_done_event(
2466
+ *,
2467
+ event_type: str | None,
2468
+ payload: dict[str, JsonValue] | None,
2469
+ suppress_text_done_events: bool,
2470
+ saw_text_delta: bool,
2471
+ ) -> bool:
2472
+ if not suppress_text_done_events or not saw_text_delta or event_type is None:
2473
+ return False
2474
+ if event_type == "response.output_text.done":
2475
+ return True
2476
+ if event_type == "response.content_part.done":
2477
+ return _is_text_content_part(payload)
2478
+ return False
2479
+
2480
+
2481
+ def _is_text_content_part(payload: dict[str, JsonValue] | None) -> bool:
2482
+ if payload is None:
2483
+ return False
2484
+ part = payload.get("part")
2485
+ if not isinstance(part, dict):
2486
+ return False
2487
+ part_type = part.get("type")
2488
+ return isinstance(part_type, str) and part_type in _TEXT_DONE_CONTENT_PART_TYPES
2489
+
2490
+
2491
+ def _input_prefix_matches_stored_context(
2492
+ input_value: JsonValue,
2493
+ *,
2494
+ stored_count: int,
2495
+ stored_fingerprint: str | None,
2496
+ ) -> bool:
2497
+ if stored_count <= 0 or stored_fingerprint is None:
2498
+ return False
2499
+ if not isinstance(input_value, list):
2500
+ return False
2501
+ if len(input_value) <= stored_count:
2502
+ return False
2503
+ return _fingerprint_input_items(cast(list[JsonValue], input_value)[:stored_count]) == stored_fingerprint
2504
+
2505
+
2506
+ def _is_missing_thread_goal_protocol_error(exc: ProxyResponseError) -> bool:
2507
+ if exc.status_code not in {404, 405}:
2508
+ return False
2509
+ error = _parse_openai_error(exc.payload)
2510
+ code = _normalize_error_code(
2511
+ error.code if error else None,
2512
+ error.type if error else None,
2513
+ )
2514
+ message = (error.message if error and error.message else "").strip().lower()
2515
+ if exc.status_code == 404:
2516
+ return code == "not_found" and message == "not found"
2517
+ return code == "method_not_allowed" and message == "method not allowed"
2518
+
2519
+
2520
+ def _headers_with_turn_state(headers: Mapping[str, str], turn_state: str | None) -> dict[str, str]:
2521
+ forwarded = dict(headers)
2522
+ if turn_state:
2523
+ forwarded["x-codex-turn-state"] = turn_state
2524
+ return forwarded
2525
+
2526
+
2527
+ def _record_same_account_takeover(*, preferred_account_id: str | None, selected_account_id: str | None) -> None:
2528
+ if not PROMETHEUS_AVAILABLE or bridge_same_account_takeover_total is None or preferred_account_id is None:
2529
+ return
2530
+ if selected_account_id is None:
2531
+ bridge_same_account_takeover_total.labels(outcome="fail").inc()
2532
+ elif selected_account_id == preferred_account_id:
2533
+ bridge_same_account_takeover_total.labels(outcome="success").inc()
2534
+ else:
2535
+ bridge_same_account_takeover_total.labels(outcome="fallback").inc()
2536
+
2537
+
2538
+ def _previous_response_owner_lookup_failed_error_envelope() -> OpenAIErrorEnvelope:
2539
+ return openai_error(
2540
+ "upstream_unavailable",
2541
+ "Previous response owner lookup failed; retry later.",
2542
+ error_type="server_error",
2543
+ )
2544
+
2545
+
2546
+ def _header_value_case_insensitive(headers: Mapping[str, str], name: str) -> str | None:
2547
+ target = name.lower()
2548
+ for key, value in headers.items():
2549
+ if key.lower() == target:
2550
+ return value
2551
+ return None
2552
+
2553
+
2554
+ def _headers_with_authorization(headers: Mapping[str, str], authorization: str | None) -> dict[str, str]:
2555
+ merged = dict(headers)
2556
+ if authorization is None:
2557
+ return merged
2558
+ if _header_value_case_insensitive(merged, "authorization") is not None:
2559
+ return merged
2560
+ merged["Authorization"] = authorization
2561
+ return merged
2562
+
2563
+
2564
+ def _service_tier_from_response(
2565
+ response: OpenAIResponsePayload | CompactResponsePayload | None,
2566
+ ) -> str | None:
2567
+ if response is None:
2568
+ return None
2569
+ extra = response.model_extra
2570
+ if not isinstance(extra, Mapping):
2571
+ return None
2572
+ return _normalize_service_tier_value(extra.get("service_tier"))
2573
+
2574
+
2575
+ def _service_tier_from_event_payload(payload: dict[str, JsonValue] | None) -> str | None:
2576
+ if not isinstance(payload, dict):
2577
+ return None
2578
+ response = payload.get("response")
2579
+ if not isinstance(response, dict):
2580
+ return None
2581
+ return _normalize_service_tier_value(response.get("service_tier"))
2582
+
2583
+
2584
+ def _effective_service_tier(requested_service_tier: str | None, actual_service_tier: str | None) -> str | None:
2585
+ if isinstance(actual_service_tier, str):
2586
+ return actual_service_tier
2587
+ if isinstance(requested_service_tier, str):
2588
+ return requested_service_tier
2589
+ return None
2590
+
2591
+
2592
+ def _normalize_service_tier_value(value: JsonValue) -> str | None:
2593
+ if not isinstance(value, str):
2594
+ return None
2595
+ stripped = value.strip()
2596
+ if not stripped:
2597
+ return None
2598
+ if stripped.lower() == "fast":
2599
+ return "priority"
2600
+ return stripped