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