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