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,778 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from collections.abc import Mapping
6
+ from datetime import timedelta
7
+ from typing import Any
8
+
9
+ from app.core.clients.proxy import ProxyResponseError
10
+ from app.core.clock import scheduler_for
11
+ from app.core.config.dashboard_overrides import effective_settings
12
+ from app.core.errors import openai_error
13
+ from app.core.metrics.prometheus import (
14
+ PROMETHEUS_AVAILABLE,
15
+ bridge_durable_recover_total,
16
+ bridge_instance_mismatch_total,
17
+ http_bridge_operation_abandonment_total,
18
+ )
19
+ from app.core.utils.time import utcnow
20
+ from app.db.models import StickySessionKind
21
+ from app.modules.proxy._service.http_bridge import helpers as _http_bridge_helpers
22
+ from app.modules.proxy._service.http_bridge.helpers import (
23
+ _await_task_deferring_cancellation,
24
+ _forget_http_bridge_denied_anchor_fence_owner,
25
+ _http_bridge_allow_durable_takeover,
26
+ _http_bridge_durable_lease_ttl_seconds,
27
+ _http_bridge_live_previous_response_alias_owner,
28
+ _http_bridge_live_turn_state_alias_owner,
29
+ _http_bridge_owner_lookup_unavailable_error_envelope,
30
+ _http_bridge_previous_response_alias_key,
31
+ _http_bridge_request_budget_seconds,
32
+ _http_bridge_turn_state_alias_key,
33
+ _is_missing_durable_bridge_table_error,
34
+ _log_http_bridge_event,
35
+ _persist_http_bridge_previous_response_alias,
36
+ _persist_http_bridge_turn_state_alias,
37
+ _reconcile_durable_http_bridge_ownership,
38
+ _record_bridge_reattach,
39
+ _register_http_bridge_turn_state_aliases_locked,
40
+ _renew_durable_http_bridge_lease,
41
+ _service_get_settings_cache,
42
+ _track_alias_registration,
43
+ )
44
+ from app.modules.proxy._service.http_bridge.protocol import _HTTPBridgeServiceProtocol
45
+ from app.modules.proxy._service.http_bridge.service_stubs import (
46
+ _headers_with_turn_state,
47
+ _service_get_settings,
48
+ )
49
+ from app.modules.proxy._service.support import _HTTPBridgeSession, _HTTPBridgeSessionKey
50
+ from app.modules.proxy.affinity import _AffinityPolicy, _extract_model_class
51
+ from app.modules.proxy.continuity import (
52
+ HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_REBINDABLE_KINDS,
53
+ is_http_bridge_account_neutral_replay,
54
+ without_http_bridge_session_affinity_headers,
55
+ )
56
+ from app.modules.proxy.durable_bridge_coordinator import DurableBridgeLookup
57
+ from app.modules.proxy.durable_bridge_repository import (
58
+ DurableBridgeAliasRegistration,
59
+ DurableBridgeAliasRegistrationReceipt,
60
+ )
61
+ from app.modules.proxy.durable_bridge_runtime import http_bridge_owner_process_epoch
62
+
63
+ logger = logging.getLogger("app.modules.proxy.service")
64
+
65
+
66
+ def _recovery_can_rebind_live_alias(session: _HTTPBridgeSession) -> bool:
67
+ return session.key.affinity_kind in HTTP_BRIDGE_ACCOUNT_NEUTRAL_REPLAY_REBINDABLE_KINDS or (
68
+ is_http_bridge_account_neutral_replay(
69
+ kind=session.key.affinity_kind,
70
+ key=session.key.affinity_key,
71
+ )
72
+ )
73
+
74
+
75
+ def _requires_durable_recovery_alias_serialization(session: _HTTPBridgeSession) -> bool:
76
+ return (
77
+ session.durable_session_id is not None
78
+ and session.durable_owner_epoch is not None
79
+ and is_http_bridge_account_neutral_replay(
80
+ kind=session.key.affinity_kind,
81
+ key=session.key.affinity_key,
82
+ )
83
+ )
84
+
85
+
86
+ class _HTTPBridgeSessionRegistryMixin:
87
+ async def prune_idle_http_bridge_sessions(self: Any) -> int:
88
+ """Run the idle sweep off the request path (issue #1354).
89
+
90
+ The sweep is otherwise reached only from
91
+ ``_get_or_create_http_bridge_session``, so a replica that stops taking
92
+ bridge requests keeps idle sessions' upstream WebSockets open until
93
+ restart. Heartbeat-driven, so it runs without traffic or leadership.
94
+ """
95
+ async with self._http_bridge_lock:
96
+ pruned_sessions = self._prune_http_bridge_sessions_locked()
97
+ if not pruned_sessions:
98
+ return 0
99
+ self._schedule_http_bridge_session_closes(pruned_sessions, reason="idle_sweep")
100
+ return len(pruned_sessions)
101
+
102
+ async def abandon_stale_http_bridge_operations(self: Any) -> int:
103
+ """Fence ownerless ambiguous operations after the bridge budget.
104
+
105
+ The in-memory registry and event batcher are both part of the safety
106
+ proof. A durable row is only eligible when no canonical or detached
107
+ generation, including terminal event settlement, still references it.
108
+ """
109
+ # M1 stream/bridge budgets: this pass runs from the ring heartbeat, outside
110
+ # any request binding, so the dashboard-managed bridge budget is applied
111
+ # from one snapshot read here (``effective_settings``), never the bare
112
+ # environment value. A snapshot failure keeps the environment budget.
113
+ settings = _service_get_settings()
114
+ try:
115
+ settings = effective_settings(await _service_get_settings_cache().get(), settings)
116
+ except Exception:
117
+ logger.warning(
118
+ "HTTP bridge stale operation abandonment could not read the dashboard settings snapshot; "
119
+ "using the environment budget",
120
+ exc_info=True,
121
+ )
122
+ inactivity_seconds = max(30.0 * 60.0, _http_bridge_request_budget_seconds(settings))
123
+ maintenance_now = utcnow()
124
+ cutoff = maintenance_now - timedelta(seconds=inactivity_seconds)
125
+ lease_expired_before = maintenance_now - timedelta(seconds=_http_bridge_durable_lease_ttl_seconds())
126
+ protected_operation_ids: set[str] = set()
127
+ async with self._http_bridge_lock:
128
+ local_sessions = [
129
+ *self._http_bridge_sessions.values(),
130
+ *self._http_bridge_detached_sessions.values(),
131
+ ]
132
+ for session in local_sessions:
133
+ protected_operation_ids.update(
134
+ operation_id
135
+ for request_state in tuple(session.pending_requests)
136
+ if (operation_id := getattr(request_state, "operation_id", None))
137
+ )
138
+
139
+ batcher = getattr(self, "_http_bridge_operation_event_batcher", None)
140
+ pending_operation_ids = getattr(batcher, "pending_operation_ids", None)
141
+ if callable(pending_operation_ids):
142
+ try:
143
+ protected_operation_ids.update(await pending_operation_ids())
144
+ except Exception:
145
+ logger.warning(
146
+ "Failed to snapshot HTTP bridge operation spool protection",
147
+ exc_info=True,
148
+ )
149
+ return 0
150
+
151
+ abandon_stale_operations = getattr(self._durable_bridge, "abandon_stale_operations", None)
152
+ if not callable(abandon_stale_operations):
153
+ return 0
154
+ try:
155
+ abandonments = await abandon_stale_operations(
156
+ cutoff=cutoff,
157
+ lease_expired_before=lease_expired_before,
158
+ protected_operation_ids=protected_operation_ids,
159
+ )
160
+ except Exception:
161
+ logger.warning("HTTP bridge stale operation abandonment failed", exc_info=True)
162
+ return 0
163
+ for abandonment in abandonments:
164
+ source_state = str(abandonment.source_state)
165
+ if PROMETHEUS_AVAILABLE and http_bridge_operation_abandonment_total is not None:
166
+ http_bridge_operation_abandonment_total.labels(source_state=source_state).inc()
167
+ logger.warning(
168
+ "Abandoned stale HTTP bridge operation",
169
+ extra={
170
+ "source_state": source_state,
171
+ "reason": "stale_owner",
172
+ "age_seconds": round(float(abandonment.age_seconds), 3),
173
+ "owner_lease_outcome": abandonment.owner_lease_outcome,
174
+ "session_hash": abandonment.session_hash,
175
+ },
176
+ )
177
+ return len(abandonments)
178
+
179
+ def _initialize_http_bridge_session_registry(self: _HTTPBridgeServiceProtocol) -> None:
180
+ # Canonical and detached registries both own live generations until
181
+ # common resource finalization removes the latter entry.
182
+ self._http_bridge_sessions = {}
183
+ self._http_bridge_detached_sessions = {}
184
+
185
+ async def close_all_http_bridge_sessions(self: _HTTPBridgeServiceProtocol) -> bool:
186
+ async with self._http_bridge_lock:
187
+ sessions_to_close, inflight_futures = self._take_all_http_bridge_sessions_locked()
188
+ shutdown_error = ProxyResponseError(
189
+ 503,
190
+ openai_error(
191
+ "upstream_unavailable",
192
+ "HTTP responses session bridge is shutting down",
193
+ error_type="server_error",
194
+ ),
195
+ )
196
+
197
+ async def finish_shutdown() -> bool:
198
+ for inflight_future in inflight_futures:
199
+ if inflight_future.done():
200
+ continue
201
+ inflight_future.set_exception(shutdown_error)
202
+ inflight_future.exception()
203
+ # The registry snapshot is no longer discoverable after the lock is
204
+ # released. Start every close concurrently, then await every result,
205
+ # so cancellation cannot strand the tail of a sequential close loop.
206
+ close_results = await asyncio.gather(
207
+ *(self._close_http_bridge_session(session) for session in sessions_to_close),
208
+ return_exceptions=True,
209
+ )
210
+ background_cleanup_drained = await self._drain_http_bridge_background_cleanup_tasks(reason="shutdown")
211
+ # Session/background cleanup may still enqueue durable operation
212
+ # events, so the spooler must outlive both. Close it before
213
+ # propagating an individual session-close failure: the batcher's
214
+ # flusher is a service-owned task and must not leak merely because
215
+ # one detached generation remains registered for a later retry.
216
+ event_batcher = getattr(self, "_http_bridge_operation_event_batcher", None)
217
+ close_batcher = getattr(event_batcher, "close", None)
218
+ if callable(close_batcher):
219
+ await close_batcher()
220
+ for result in close_results:
221
+ if isinstance(result, BaseException):
222
+ raise result
223
+ return background_cleanup_drained
224
+
225
+ shutdown_task = scheduler_for(self).create_task(finish_shutdown(), name="http-bridge-shutdown-close-all")
226
+ result, cancellation = await _await_task_deferring_cancellation(shutdown_task)
227
+ if cancellation is not None:
228
+ raise cancellation
229
+ return result
230
+
231
+ async def _register_http_bridge_turn_state(
232
+ self: _HTTPBridgeServiceProtocol,
233
+ session: _HTTPBridgeSession,
234
+ turn_state: str,
235
+ ) -> bool:
236
+ if _requires_durable_recovery_alias_serialization(session):
237
+ async with session.recovery_alias_lock:
238
+ return await self._register_http_bridge_turn_state_impl(session, turn_state)
239
+ return await self._register_http_bridge_turn_state_impl(session, turn_state)
240
+
241
+ async def _register_http_bridge_turn_state_impl(
242
+ self: _HTTPBridgeServiceProtocol,
243
+ session: _HTTPBridgeSession,
244
+ turn_state: str,
245
+ ) -> bool:
246
+ registered, _receipt = await self._register_http_bridge_turn_state_core(
247
+ session,
248
+ turn_state,
249
+ reversible=False,
250
+ )
251
+ return registered
252
+
253
+ async def _register_http_bridge_recovery_turn_state_locked(
254
+ self: _HTTPBridgeServiceProtocol,
255
+ session: _HTTPBridgeSession,
256
+ turn_state: str,
257
+ ) -> tuple[bool, DurableBridgeAliasRegistrationReceipt | None]:
258
+ if not _requires_durable_recovery_alias_serialization(session):
259
+ return False, None
260
+ return await self._register_http_bridge_turn_state_core(
261
+ session,
262
+ turn_state,
263
+ reversible=True,
264
+ )
265
+
266
+ async def _register_http_bridge_turn_state_core(
267
+ self: _HTTPBridgeServiceProtocol,
268
+ session: _HTTPBridgeSession,
269
+ turn_state: str,
270
+ *,
271
+ reversible: bool,
272
+ ) -> tuple[bool, DurableBridgeAliasRegistrationReceipt | None]:
273
+ defer_durable_publication = False
274
+ deferred_live_alias_owner: _HTTPBridgeSession | None = None
275
+ async with self._http_bridge_lock:
276
+ if session.closed or (
277
+ session.upstream_control.retire_after_drain
278
+ and self._http_bridge_sessions.get(session.key) is not session
279
+ ):
280
+ # A detached predecessor may finish its admitted response, but
281
+ # publishing continuity aliases under its reused key would make
282
+ # them resolve to the replacement generation (and account).
283
+ return False, None
284
+ account_neutral_recovery = is_http_bridge_account_neutral_replay(
285
+ kind=session.key.affinity_kind,
286
+ key=session.key.affinity_key,
287
+ )
288
+ if account_neutral_recovery and (session.durable_session_id is None or session.durable_owner_epoch is None):
289
+ return False, None
290
+ defer_durable_publication = (
291
+ account_neutral_recovery
292
+ and session.durable_session_id is not None
293
+ and session.durable_owner_epoch is not None
294
+ )
295
+ live_alias_owner = _http_bridge_live_turn_state_alias_owner(self, session, turn_state)
296
+ if live_alias_owner is not None:
297
+ can_rebind_recovery_alias = account_neutral_recovery and _recovery_can_rebind_live_alias(
298
+ live_alias_owner
299
+ )
300
+ if not can_rebind_recovery_alias:
301
+ return not account_neutral_recovery, None
302
+ if defer_durable_publication:
303
+ deferred_live_alias_owner = live_alias_owner
304
+ else:
305
+ live_alias_owner.downstream_turn_state_aliases.discard(turn_state)
306
+ live_alias_owner.turn_state_alias_registration_generations.pop(turn_state, None)
307
+ if live_alias_owner.downstream_turn_state == turn_state:
308
+ live_alias_owner.downstream_turn_state = None
309
+ if account_neutral_recovery:
310
+ session.codex_session = True
311
+ session.idle_ttl_seconds = max(
312
+ session.idle_ttl_seconds,
313
+ float(_http_bridge_helpers.HTTP_BRIDGE_CODEX_IDLE_TTL_SECONDS),
314
+ )
315
+ session.headers = without_http_bridge_session_affinity_headers(session.headers)
316
+ registration_generation = _track_alias_registration(session, turn_state, turn_state=True)
317
+ if not defer_durable_publication:
318
+ session.downstream_turn_state_aliases.add(turn_state)
319
+ if session.downstream_turn_state is None:
320
+ session.downstream_turn_state = turn_state
321
+ if live_alias_owner is not None:
322
+ alias_key = _http_bridge_turn_state_alias_key(turn_state, session.key.api_key_id)
323
+ self._http_bridge_turn_state_index[alias_key] = session.key
324
+ _register_http_bridge_turn_state_aliases_locked(self, session)
325
+ if session.durable_session_id is None or session.durable_owner_epoch is None:
326
+ return True, None
327
+ durable_result, receipt = await _persist_http_bridge_turn_state_alias(
328
+ self,
329
+ session,
330
+ turn_state=turn_state,
331
+ registration_generation=registration_generation,
332
+ instance_id=_service_get_settings().http_responses_session_bridge_instance_id,
333
+ lease_ttl_seconds=_http_bridge_durable_lease_ttl_seconds(),
334
+ local_alias_was_published=not defer_durable_publication,
335
+ reversible=reversible,
336
+ )
337
+ if not defer_durable_publication:
338
+ return True, receipt
339
+ if durable_result != DurableBridgeAliasRegistration.REGISTERED:
340
+ return False, receipt
341
+ async with self._http_bridge_lock:
342
+ if (
343
+ session.closed
344
+ or self._http_bridge_sessions.get(session.key) is not session
345
+ or session.turn_state_alias_registration_generations.get(turn_state) != registration_generation
346
+ ):
347
+ if session.turn_state_alias_registration_generations.get(turn_state) == registration_generation:
348
+ session.turn_state_alias_registration_generations.pop(turn_state, None)
349
+ return False, receipt
350
+ current_live_owner = _http_bridge_live_turn_state_alias_owner(self, session, turn_state)
351
+ if (
352
+ current_live_owner is not None
353
+ and current_live_owner is not deferred_live_alias_owner
354
+ and not _recovery_can_rebind_live_alias(current_live_owner)
355
+ ):
356
+ session.turn_state_alias_registration_generations.pop(turn_state, None)
357
+ return False, receipt
358
+ if current_live_owner is not None:
359
+ current_live_owner.downstream_turn_state_aliases.discard(turn_state)
360
+ current_live_owner.turn_state_alias_registration_generations.pop(turn_state, None)
361
+ if current_live_owner.downstream_turn_state == turn_state:
362
+ current_live_owner.downstream_turn_state = None
363
+ session.downstream_turn_state_aliases.add(turn_state)
364
+ if session.downstream_turn_state is None:
365
+ session.downstream_turn_state = turn_state
366
+ alias_key = _http_bridge_turn_state_alias_key(turn_state, session.key.api_key_id)
367
+ self._http_bridge_turn_state_index[alias_key] = session.key
368
+ _register_http_bridge_turn_state_aliases_locked(self, session)
369
+ return True, receipt
370
+
371
+ async def _register_http_bridge_previous_response_id(
372
+ self: _HTTPBridgeServiceProtocol,
373
+ session: _HTTPBridgeSession,
374
+ response_id: str,
375
+ *,
376
+ input_item_count: int | None = None,
377
+ input_full_fingerprint: str | None = None,
378
+ pending_tool_calls: Mapping[str, str] | None = None,
379
+ ) -> bool:
380
+ if _requires_durable_recovery_alias_serialization(session):
381
+ async with session.recovery_alias_lock:
382
+ return await self._register_http_bridge_previous_response_id_impl(
383
+ session,
384
+ response_id,
385
+ input_item_count=input_item_count,
386
+ input_full_fingerprint=input_full_fingerprint,
387
+ pending_tool_calls=pending_tool_calls,
388
+ )
389
+ return await self._register_http_bridge_previous_response_id_impl(
390
+ session,
391
+ response_id,
392
+ input_item_count=input_item_count,
393
+ input_full_fingerprint=input_full_fingerprint,
394
+ pending_tool_calls=pending_tool_calls,
395
+ )
396
+
397
+ async def _register_http_bridge_previous_response_id_impl(
398
+ self: _HTTPBridgeServiceProtocol,
399
+ session: _HTTPBridgeSession,
400
+ response_id: str,
401
+ *,
402
+ input_item_count: int | None = None,
403
+ input_full_fingerprint: str | None = None,
404
+ pending_tool_calls: Mapping[str, str] | None = None,
405
+ ) -> bool:
406
+ stripped_response_id = response_id.strip()
407
+ if not stripped_response_id:
408
+ return False
409
+ defer_durable_publication = False
410
+ deferred_live_alias_owner: _HTTPBridgeSession | None = None
411
+ async with self._http_bridge_lock:
412
+ if session.closed:
413
+ return False
414
+ if (
415
+ session.upstream_control.retire_after_drain
416
+ and self._http_bridge_sessions.get(session.key) is not session
417
+ ):
418
+ return False
419
+ alias_key = _http_bridge_previous_response_alias_key(stripped_response_id, session.key.api_key_id)
420
+ account_neutral_recovery = is_http_bridge_account_neutral_replay(
421
+ kind=session.key.affinity_kind,
422
+ key=session.key.affinity_key,
423
+ )
424
+ if account_neutral_recovery and (session.durable_session_id is None or session.durable_owner_epoch is None):
425
+ return False
426
+ defer_durable_publication = (
427
+ account_neutral_recovery
428
+ and session.durable_session_id is not None
429
+ and session.durable_owner_epoch is not None
430
+ )
431
+ live_alias_owner = _http_bridge_live_previous_response_alias_owner(
432
+ self,
433
+ session,
434
+ stripped_response_id,
435
+ )
436
+ if live_alias_owner is not None:
437
+ can_rebind_recovery_alias = account_neutral_recovery and _recovery_can_rebind_live_alias(
438
+ live_alias_owner
439
+ )
440
+ if not can_rebind_recovery_alias:
441
+ return not account_neutral_recovery
442
+ if defer_durable_publication:
443
+ deferred_live_alias_owner = live_alias_owner
444
+ else:
445
+ live_alias_owner.previous_response_ids.discard(stripped_response_id)
446
+ live_alias_owner.previous_response_alias_registration_generations.pop(stripped_response_id, None)
447
+ registration_generation = _track_alias_registration(session, stripped_response_id, turn_state=False)
448
+ if not defer_durable_publication:
449
+ self._http_bridge_previous_response_index[alias_key] = session.key
450
+ session.previous_response_ids.add(stripped_response_id)
451
+ if session.durable_session_id is None or session.durable_owner_epoch is None:
452
+ return True
453
+ durable_result = await _persist_http_bridge_previous_response_alias(
454
+ self,
455
+ session,
456
+ response_id=stripped_response_id,
457
+ registration_generation=registration_generation,
458
+ input_item_count=input_item_count,
459
+ input_full_fingerprint=input_full_fingerprint,
460
+ pending_tool_calls=pending_tool_calls,
461
+ instance_id=_service_get_settings().http_responses_session_bridge_instance_id,
462
+ lease_ttl_seconds=_http_bridge_durable_lease_ttl_seconds(),
463
+ local_alias_was_published=not defer_durable_publication,
464
+ )
465
+ if not defer_durable_publication:
466
+ # The in-memory alias stands either way, but the caller's
467
+ # quarantine clear and supersession are gated on the durable
468
+ # anchor actually advancing: a swallowed durable failure here
469
+ # left the old poisoned anchor stored for other replicas while
470
+ # this worker cleared its only protection.
471
+ return durable_result == DurableBridgeAliasRegistration.REGISTERED
472
+ if durable_result != DurableBridgeAliasRegistration.REGISTERED:
473
+ return False
474
+ async with self._http_bridge_lock:
475
+ if (
476
+ session.closed
477
+ or self._http_bridge_sessions.get(session.key) is not session
478
+ or session.previous_response_alias_registration_generations.get(stripped_response_id)
479
+ != registration_generation
480
+ ):
481
+ if (
482
+ session.previous_response_alias_registration_generations.get(stripped_response_id)
483
+ == registration_generation
484
+ ):
485
+ session.previous_response_alias_registration_generations.pop(stripped_response_id, None)
486
+ return False
487
+ current_live_owner = _http_bridge_live_previous_response_alias_owner(
488
+ self,
489
+ session,
490
+ stripped_response_id,
491
+ )
492
+ if (
493
+ current_live_owner is not None
494
+ and current_live_owner is not deferred_live_alias_owner
495
+ and not _recovery_can_rebind_live_alias(current_live_owner)
496
+ ):
497
+ session.previous_response_alias_registration_generations.pop(stripped_response_id, None)
498
+ return False
499
+ if current_live_owner is not None:
500
+ current_live_owner.previous_response_ids.discard(stripped_response_id)
501
+ current_live_owner.previous_response_alias_registration_generations.pop(
502
+ stripped_response_id,
503
+ None,
504
+ )
505
+ self._http_bridge_previous_response_index[alias_key] = session.key
506
+ session.previous_response_ids.add(stripped_response_id)
507
+ return True
508
+
509
+ async def _unregister_http_bridge_turn_states(
510
+ self: _HTTPBridgeServiceProtocol,
511
+ session: _HTTPBridgeSession,
512
+ ) -> None:
513
+ async with self._http_bridge_lock:
514
+ self._unregister_http_bridge_turn_states_locked(session)
515
+
516
+ async def _unregister_http_bridge_previous_response_ids(
517
+ self: _HTTPBridgeServiceProtocol,
518
+ session: _HTTPBridgeSession,
519
+ ) -> None:
520
+ async with self._http_bridge_lock:
521
+ self._unregister_http_bridge_previous_response_ids_locked(session)
522
+
523
+ async def _unregister_http_bridge_previous_response_id(
524
+ self: _HTTPBridgeServiceProtocol,
525
+ session: _HTTPBridgeSession,
526
+ response_id: str,
527
+ *,
528
+ expected_durable_session_id: str | None = None,
529
+ expected_durable_owner_epoch: int | None = None,
530
+ ) -> bool:
531
+ async with self._http_bridge_lock:
532
+ if (
533
+ expected_durable_session_id is not None and session.durable_session_id != expected_durable_session_id
534
+ ) or (
535
+ expected_durable_owner_epoch is not None and session.durable_owner_epoch != expected_durable_owner_epoch
536
+ ):
537
+ return False
538
+ self._unregister_http_bridge_previous_response_id_locked(session, response_id)
539
+ return True
540
+
541
+ def _detach_http_bridge_session_locked(
542
+ self: _HTTPBridgeServiceProtocol,
543
+ key: _HTTPBridgeSessionKey,
544
+ *,
545
+ expected_session: _HTTPBridgeSession | None = None,
546
+ mark_closed: bool = True,
547
+ ) -> _HTTPBridgeSession | None:
548
+ session = self._http_bridge_sessions.get(key)
549
+ if session is None or (expected_session is not None and session is not expected_session):
550
+ return None
551
+ self._http_bridge_sessions.pop(key, None)
552
+ if mark_closed:
553
+ session.closed = True
554
+ # Detachment removes only canonical routing. Even an idle generation
555
+ # marked closed may retain a slow-closing socket, reader, durable lease,
556
+ # or account lease, so lifecycle/capacity ownership lasts through the
557
+ # common resource-close finalizer for every detached generation.
558
+ self._http_bridge_detached_sessions[id(session)] = session
559
+ self._unregister_http_bridge_turn_states_locked(session)
560
+ self._unregister_http_bridge_previous_response_ids_locked(session)
561
+ return session
562
+
563
+ def _take_all_http_bridge_sessions_locked(
564
+ self: _HTTPBridgeServiceProtocol,
565
+ ) -> tuple[list[_HTTPBridgeSession], list[asyncio.Future[_HTTPBridgeSession]]]:
566
+ sessions = [*self._http_bridge_sessions.values(), *self._http_bridge_detached_sessions.values()]
567
+ inflight_futures = list(self._http_bridge_inflight_sessions.values())
568
+ # Shutdown removes canonical routing immediately, but resource ownership
569
+ # remains discoverable until each close succeeds. A failed close can
570
+ # then be retried by a later shutdown pass instead of orphaning its
571
+ # socket, durable lease, account lease, or unsettled requests.
572
+ for session in self._http_bridge_sessions.values():
573
+ self._http_bridge_detached_sessions[id(session)] = session
574
+ self._http_bridge_sessions.clear()
575
+ self._http_bridge_inflight_sessions.clear()
576
+ self._http_bridge_previous_response_index.clear()
577
+ return sessions, inflight_futures
578
+
579
+ def _unregister_http_bridge_turn_states_locked(
580
+ self: _HTTPBridgeServiceProtocol,
581
+ session: _HTTPBridgeSession,
582
+ ) -> None:
583
+ current_session = self._http_bridge_sessions.get(session.key)
584
+ for alias in tuple(session.downstream_turn_state_aliases):
585
+ alias_key = _http_bridge_turn_state_alias_key(alias, session.key.api_key_id)
586
+ if (
587
+ current_session is not None
588
+ and current_session is not session
589
+ and alias in current_session.downstream_turn_state_aliases
590
+ ):
591
+ continue
592
+ if self._http_bridge_turn_state_index.get(alias_key) == session.key:
593
+ self._http_bridge_turn_state_index.pop(alias_key, None)
594
+ session.downstream_turn_state_aliases.clear()
595
+ session.turn_state_alias_registration_generations.clear()
596
+
597
+ def _unregister_http_bridge_previous_response_ids_locked(
598
+ self: _HTTPBridgeServiceProtocol,
599
+ session: _HTTPBridgeSession,
600
+ ) -> None:
601
+ for response_id in tuple(session.previous_response_ids):
602
+ self._unregister_http_bridge_previous_response_id_locked(session, response_id)
603
+ session.previous_response_ids.clear()
604
+ session.previous_response_alias_registration_generations.clear()
605
+
606
+ def _unregister_http_bridge_previous_response_id_locked(
607
+ self: _HTTPBridgeServiceProtocol,
608
+ session: _HTTPBridgeSession,
609
+ response_id: str,
610
+ ) -> None:
611
+ if response_id not in session.previous_response_ids:
612
+ return
613
+ alias_key = _http_bridge_previous_response_alias_key(response_id, session.key.api_key_id)
614
+ current_session = self._http_bridge_sessions.get(session.key)
615
+ if (
616
+ not (
617
+ current_session is not None
618
+ and current_session is not session
619
+ and response_id in current_session.previous_response_ids
620
+ )
621
+ and self._http_bridge_previous_response_index.get(alias_key) == session.key
622
+ ):
623
+ self._http_bridge_previous_response_index.pop(alias_key, None)
624
+ session.previous_response_ids.discard(response_id)
625
+ session.previous_response_alias_registration_generations.pop(response_id, None)
626
+
627
+ def _promote_http_bridge_session_to_codex_affinity(
628
+ self: _HTTPBridgeServiceProtocol,
629
+ session: _HTTPBridgeSession,
630
+ *,
631
+ turn_state: str,
632
+ ) -> None:
633
+ session.affinity = _AffinityPolicy(key=turn_state, kind=StickySessionKind.CODEX_SESSION)
634
+ session.codex_session = True
635
+ session.downstream_turn_state = turn_state
636
+ session.downstream_turn_state_aliases.add(turn_state)
637
+ session.idle_ttl_seconds = max(
638
+ session.idle_ttl_seconds,
639
+ float(_http_bridge_helpers.HTTP_BRIDGE_CODEX_IDLE_TTL_SECONDS),
640
+ )
641
+ session.headers = _headers_with_turn_state(session.headers, turn_state)
642
+
643
+ async def _claim_durable_http_bridge_session(
644
+ self: _HTTPBridgeServiceProtocol,
645
+ session: _HTTPBridgeSession,
646
+ *,
647
+ allow_takeover: bool,
648
+ force_owner_epoch_advance: bool = False,
649
+ claim_account_id: str | None = None,
650
+ clear_latest_turn_state: bool = False,
651
+ record_restart_takeover: bool = False,
652
+ ) -> None:
653
+ current_instance = _service_get_settings().http_responses_session_bridge_instance_id
654
+ current_process_epoch = http_bridge_owner_process_epoch()
655
+ try:
656
+ lookup: DurableBridgeLookup | None = None
657
+ for claim_attempt in range(2):
658
+ lookup = await self._durable_bridge.claim_live_session(
659
+ session_key_kind=session.key.affinity_kind,
660
+ session_key_value=session.key.affinity_key,
661
+ api_key_id=session.key.api_key_id,
662
+ instance_id=current_instance,
663
+ owner_process_epoch=current_process_epoch,
664
+ lease_ttl_seconds=_http_bridge_durable_lease_ttl_seconds(),
665
+ account_id=claim_account_id or session.account.id,
666
+ model=session.request_model,
667
+ service_tier=session.request_service_tier,
668
+ latest_turn_state=None if clear_latest_turn_state else session.downstream_turn_state,
669
+ latest_response_id=None,
670
+ allow_takeover=allow_takeover,
671
+ force_owner_epoch_advance=force_owner_epoch_advance or claim_attempt > 0,
672
+ )
673
+ if lookup.owner_instance_id == current_instance:
674
+ break
675
+ if lookup.owner_instance_id is None and claim_attempt == 0:
676
+ # The lookup used to decide ``allow_takeover`` can race a
677
+ # concurrent close/release. If the first claim lands after
678
+ # ownership has already been cleared, retry once with the
679
+ # forced epoch-advance path instead of treating an ownerless
680
+ # row as a foreign replica.
681
+ await asyncio.sleep(0)
682
+ continue
683
+ if not allow_takeover or claim_attempt > 0:
684
+ break
685
+ if not _http_bridge_allow_durable_takeover(lookup):
686
+ # The claim reported a live foreign owner: we lost the race
687
+ # rather than hitting transient contention. The repository
688
+ # already dropped its takeover permission for that reason,
689
+ # and retrying here with a fresh call would restore it and
690
+ # steal the winner's live lease (issue #1695).
691
+ break
692
+ await asyncio.sleep(0)
693
+ assert lookup is not None
694
+ if lookup.owner_instance_id != current_instance:
695
+ _log_http_bridge_event(
696
+ "owner_mismatch_retry",
697
+ session.key,
698
+ account_id=None,
699
+ model=session.request_model,
700
+ detail=(
701
+ f"expected_instance={lookup.owner_instance_id}, "
702
+ f"current_instance={current_instance}, outcome=claim_rejected"
703
+ ),
704
+ cache_key_family=session.key.affinity_kind,
705
+ model_class=_extract_model_class(session.request_model) if session.request_model else None,
706
+ owner_check_applied=True,
707
+ )
708
+ if PROMETHEUS_AVAILABLE and bridge_instance_mismatch_total is not None:
709
+ bridge_instance_mismatch_total.labels(outcome="retry").inc()
710
+ raise ProxyResponseError(
711
+ 409,
712
+ openai_error(
713
+ "bridge_instance_mismatch",
714
+ "HTTP bridge session is owned by a different instance; retry to reach the correct replica",
715
+ error_type="server_error",
716
+ ),
717
+ )
718
+ async with session.lifecycle_lock:
719
+ previous_durable_session_id = session.durable_session_id
720
+ previous_durable_owner_epoch = session.durable_owner_epoch
721
+ next_owner_changed = (
722
+ previous_durable_session_id != lookup.session_id
723
+ or previous_durable_owner_epoch != lookup.owner_epoch
724
+ )
725
+ if next_owner_changed:
726
+ previous_owner_key = (
727
+ previous_durable_session_id
728
+ if previous_durable_session_id is not None
729
+ else f"local:{id(session)}"
730
+ )
731
+ _forget_http_bridge_denied_anchor_fence_owner(
732
+ self,
733
+ previous_owner_key,
734
+ owner_epoch=previous_durable_owner_epoch,
735
+ )
736
+ session.durable_session_id = lookup.session_id
737
+ session.durable_owner_epoch = lookup.owner_epoch
738
+ session.headers = _headers_with_turn_state(session.headers, session.downstream_turn_state)
739
+ if (
740
+ PROMETHEUS_AVAILABLE
741
+ and bridge_durable_recover_total is not None
742
+ and record_restart_takeover
743
+ and lookup.owner_epoch > 1
744
+ ):
745
+ bridge_durable_recover_total.labels(path="restart_takeover").inc()
746
+ _record_bridge_reattach(path="restart_takeover", outcome="success")
747
+ if session.key.affinity_kind == "session_header":
748
+ await self._durable_bridge.register_session_header(
749
+ session_id=lookup.session_id,
750
+ api_key_id=session.key.api_key_id,
751
+ session_header=session.key.affinity_key,
752
+ )
753
+ except Exception as exc:
754
+ if _is_missing_durable_bridge_table_error(exc):
755
+ if is_http_bridge_account_neutral_replay(
756
+ kind=session.key.affinity_kind,
757
+ key=session.key.affinity_key,
758
+ ):
759
+ raise ProxyResponseError(
760
+ 502,
761
+ _http_bridge_owner_lookup_unavailable_error_envelope(),
762
+ ) from exc
763
+ logger.warning("Durable bridge tables missing; using in-memory bridge session fallback", exc_info=True)
764
+ return
765
+ raise
766
+
767
+ async def _refresh_durable_http_bridge_session(
768
+ self: _HTTPBridgeServiceProtocol,
769
+ session: _HTTPBridgeSession,
770
+ ) -> None:
771
+ """Renew the durable lease; callers must hold ``self._http_bridge_lock``."""
772
+
773
+ await _renew_durable_http_bridge_lease(self, session)
774
+
775
+ async def reconcile_durable_http_bridge_ownership(self: _HTTPBridgeServiceProtocol) -> int:
776
+ """Close local sessions whose durable row is owned by another instance/epoch."""
777
+
778
+ return await _reconcile_durable_http_bridge_ownership(self)