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,233 @@
1
+ """Replica-local, fleet-relative output-throughput weight per (account, model).
2
+
3
+ First-token latency (``ttft_cohort.py``) misses the dominant per-account speed
4
+ difference seen in production: on ``gpt-5.6-sol`` the fleet splits into two
5
+ output-throughput cohorts (nine accounts near 40 tok/s, ten near 66-75 tok/s)
6
+ whose first-token latency is the same, while on ``gpt-6-astra`` the same
7
+ accounts are uniform. Throughput is therefore a property of the (account,
8
+ model) pair, not of the account, and a slow-on-one-model account must not be
9
+ discounted on a model where it is not slow.
10
+
11
+ This module keeps, per account and per model, a bounded list of recent output
12
+ throughputs -- total ``output_tokens`` over the generation span
13
+ ``latency_ms - latency_first_token_ms`` -- for successful ``normal`` turns
14
+ with at least 200 output tokens, a single upstream attempt and no local
15
+ queueing (the same funnel filters as the TTFT sampler), on any input size and
16
+ reasoning effort. ``output_tokens`` is the total the upstream reports,
17
+ reasoning tokens included. Where the reasoning lands depends on whether the
18
+ client asked for reasoning summaries: with summaries the first summary delta
19
+ is the first token, the reasoning runs inside the span and the total is what
20
+ the span produced; without summaries the whole reasoning prefix sits inside
21
+ the TTFT and the sample overstates the span's throughput by ``total /
22
+ visible`` -- several-fold on a turn that reasons for longer than it answers,
23
+ not a small bias. No numerator is exact for both kinds of turn (visible tokens
24
+ alone would under-read every summary-streaming turn by the same factor). The
25
+ total is kept because the comparison is fleet-relative: fresh draws spread
26
+ clients across accounts, so the summary/no-summary mix is shared and the
27
+ per-account medians move together; a per-account median only shifts when more
28
+ than half of that account's in-window samples are inflated, and the fleet
29
+ reference only when more than half of the accounts' medians are, so a spurious
30
+ discount needs an uneven mix that persists over most of an hour on most of the
31
+ fleet. Inflation reads *fast*, so it never discounts the account it lands on.
32
+
33
+ Estimator: the per-(account, model) estimate is the median of the sample
34
+ throughputs. Unlike first-token latency, throughput is not bimodal -- it is
35
+ the serving path's speed, largely independent of the prompt -- so a median
36
+ neither hides a mode share nor follows a single stalled stream (a network
37
+ pause or a slow client) or a short burst; the 200-token floor keeps the
38
+ quantization of ``tokens / duration`` small. The fleet reference is the median
39
+ of the per-account estimates for the requested model; an account below the
40
+ reference by more than the deadband is discounted to ``max(floor, account /
41
+ fleet)``. ``latency_cohort.apply_latency_cohort_weights`` combines this with the
42
+ TTFT multiplier as the minimum of the two (never their product) and only for
43
+ fresh weighted draws.
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ from collections.abc import Mapping
49
+ from statistics import median
50
+ from typing import Any
51
+
52
+ from app.modules.proxy._load_balancer.ttft_cohort import (
53
+ TTFT_MAX_SAMPLES,
54
+ TTFT_MIN_ACCOUNTS,
55
+ TTFT_SAMPLE_REQUEST_KIND,
56
+ TTFT_SAMPLE_WINDOW_SECONDS,
57
+ )
58
+ from app.modules.proxy._load_balancer.types import RuntimeState
59
+
60
+ # Same window/cap pattern as the TTFT sampler, per (account, model).
61
+ TPS_SAMPLE_WINDOW_SECONDS = TTFT_SAMPLE_WINDOW_SECONDS
62
+ TPS_MAX_SAMPLES = TTFT_MAX_SAMPLES
63
+ # Below this many samples for a model an account's estimate is noise and stays neutral.
64
+ TPS_MIN_SAMPLES = 8
65
+ # Short answers quantize ``tokens / duration`` too coarsely to compare.
66
+ TPS_SAMPLE_MIN_OUTPUT_TOKENS = 200
67
+ # Rows written without a model (direct WebSocket rows carry ``""``) or with the
68
+ # placeholder cannot be attributed to a per-model cohort.
69
+ _UNKNOWN_MODEL = "unknown"
70
+ # An account within 15% below the fleet reference is not discounted at all.
71
+ TPS_WEIGHT_DEADBAND = 0.15
72
+ # A 10x slower account still keeps half of its weight: the window keeps
73
+ # sampling it and the discount lifts as soon as its throughput recovers.
74
+ TPS_WEIGHT_FLOOR = 0.5
75
+
76
+
77
+ def _sampleable_model(model: str | None) -> str | None:
78
+ if not model or model == _UNKNOWN_MODEL:
79
+ return None
80
+ return model
81
+
82
+
83
+ def _prune(samples_by_model: dict[str, list[tuple[float, float]]], now: float) -> None:
84
+ oldest_kept = now - TPS_SAMPLE_WINDOW_SECONDS
85
+ for model in list(samples_by_model):
86
+ samples = samples_by_model[model]
87
+ stale = 0
88
+ for recorded_at, _ in samples:
89
+ if recorded_at >= oldest_kept:
90
+ break
91
+ stale += 1
92
+ if stale:
93
+ del samples[:stale]
94
+ excess = len(samples) - TPS_MAX_SAMPLES
95
+ if excess > 0:
96
+ del samples[:excess]
97
+ if not samples:
98
+ del samples_by_model[model]
99
+
100
+
101
+ def _throughput_sample(
102
+ *,
103
+ account_id: str | None,
104
+ status: str,
105
+ request_kind: str,
106
+ model: str | None,
107
+ latency_ms: int | None,
108
+ latency_first_token_ms: int | None,
109
+ output_tokens: int | None,
110
+ queued_wait_ms: int,
111
+ retried: bool,
112
+ ) -> tuple[str, float] | None:
113
+ """``(model, tokens_per_second)`` for an eligible row, else ``None``."""
114
+ sample_model = _sampleable_model(model)
115
+ if (
116
+ not account_id
117
+ or status != "success"
118
+ or request_kind != TTFT_SAMPLE_REQUEST_KIND
119
+ or retried
120
+ or sample_model is None
121
+ or latency_first_token_ms is None
122
+ or latency_first_token_ms < 0
123
+ or latency_ms is None
124
+ or latency_ms <= latency_first_token_ms
125
+ or output_tokens is None
126
+ or output_tokens < TPS_SAMPLE_MIN_OUTPUT_TOKENS
127
+ or queued_wait_ms > 0
128
+ ):
129
+ return None
130
+ return sample_model, output_tokens / ((latency_ms - latency_first_token_ms) / 1000.0)
131
+
132
+
133
+ def record_tps_sample(
134
+ balancer: Any,
135
+ *,
136
+ account_id: str | None,
137
+ status: str,
138
+ request_kind: str,
139
+ model: str | None,
140
+ latency_ms: int | None,
141
+ latency_first_token_ms: int | None,
142
+ output_tokens: int | None,
143
+ queued_wait_ms: int,
144
+ retried: bool = False,
145
+ ) -> None:
146
+ """Record one eligible output throughput for ``(account_id, model)`` at the balancer clock.
147
+
148
+ ``retried`` and ``queued_wait_ms`` have the meaning documented on
149
+ ``ttft_cohort.record_ttft_sample``: a retried send or a capacity wait
150
+ leaves a failed attempt inside ``latency_ms``, and a queued row's span is
151
+ not the upstream's. ``latency_ms`` MUST end at the upstream terminal event,
152
+ not at the end of downstream delivery or local settlement/cleanup: the
153
+ request-log funnel passes the ``latency_upstream_terminal_ms`` each path
154
+ stamps when it parses the terminal frame (HTTP stream settlement, bridge
155
+ reader, WebSocket reader) in place of the row's latency. The throughput is
156
+ ``output_tokens`` over the generation span
157
+ ``latency_ms - latency_first_token_ms``; rows without a
158
+ model, with the ``unknown`` placeholder, without a first token, with a
159
+ non-positive span or with fewer than ``TPS_SAMPLE_MIN_OUTPUT_TOKENS``
160
+ output tokens add nothing.
161
+
162
+ Same concurrency contract as the TTFT sampler: synchronous, lock-free, no
163
+ await between reading and writing the per-account map; no-op when
164
+ ``balancer`` does not expose the runtime map or clock (test doubles).
165
+ """
166
+ runtime_map = getattr(balancer, "_runtime", None)
167
+ clock = getattr(balancer, "_clock", None)
168
+ if not isinstance(runtime_map, dict) or clock is None or not account_id:
169
+ return
170
+ sample = _throughput_sample(
171
+ account_id=account_id,
172
+ status=status,
173
+ request_kind=request_kind,
174
+ model=model,
175
+ latency_ms=latency_ms,
176
+ latency_first_token_ms=latency_first_token_ms,
177
+ output_tokens=output_tokens,
178
+ queued_wait_ms=queued_wait_ms,
179
+ retried=retried,
180
+ )
181
+ if sample is None:
182
+ return
183
+ sample_model, tokens_per_second = sample
184
+ now = float(clock.time())
185
+ runtime = runtime_map.setdefault(account_id, RuntimeState())
186
+ samples_by_model = runtime.tps_samples
187
+ if samples_by_model is None:
188
+ samples_by_model = runtime.tps_samples = {}
189
+ samples_by_model.setdefault(sample_model, []).append((now, tokens_per_second))
190
+ _prune(samples_by_model, now)
191
+
192
+
193
+ def account_tps_estimate(runtime: RuntimeState | None, model: str | None, now: float) -> float | None:
194
+ """Median in-window throughput (tok/s) of ``runtime`` on ``model``; ``None`` below ``TPS_MIN_SAMPLES``."""
195
+ if runtime is None or not runtime.tps_samples or not model:
196
+ return None
197
+ samples = runtime.tps_samples.get(model)
198
+ if not samples:
199
+ return None
200
+ oldest_kept = now - TPS_SAMPLE_WINDOW_SECONDS
201
+ values = [tps for recorded_at, tps in samples if recorded_at >= oldest_kept]
202
+ if len(values) < TPS_MIN_SAMPLES:
203
+ return None
204
+ return float(median(values))
205
+
206
+
207
+ def tps_weight_multiplier(account_tps: float, fleet_tps: float) -> float:
208
+ """Draw-weight multiplier in ``[floor, 1.0]`` for an account throughput relative to the fleet."""
209
+ if fleet_tps <= 0.0 or account_tps <= 0.0 or account_tps >= fleet_tps * (1.0 - TPS_WEIGHT_DEADBAND):
210
+ return 1.0
211
+ return max(TPS_WEIGHT_FLOOR, account_tps / fleet_tps)
212
+
213
+
214
+ def fleet_tps_estimates(
215
+ runtime_by_account_id: Mapping[str, RuntimeState],
216
+ model: str | None,
217
+ now: float,
218
+ ) -> tuple[dict[str, float], float | None]:
219
+ """Per-account estimates on ``model`` and the fleet reference (their median).
220
+
221
+ The reference is ``None`` -- every multiplier neutral -- when no model is
222
+ requested or fewer than ``TTFT_MIN_ACCOUNTS`` accounts hold enough samples
223
+ for that model; evidence on other models never contributes.
224
+ """
225
+ if not model:
226
+ return {}, None
227
+ estimates = {
228
+ account_id: estimate
229
+ for account_id, runtime in runtime_by_account_id.items()
230
+ if (estimate := account_tps_estimate(runtime, model, now)) is not None
231
+ }
232
+ fleet_tps = median(estimates.values()) if len(estimates) >= TTFT_MIN_ACCOUNTS else None
233
+ return estimates, fleet_tps
@@ -0,0 +1,214 @@
1
+ """Replica-local, fleet-relative first-token latency weight per account.
2
+
3
+ The balancer has no latency signal: two accounts with equal remaining credits
4
+ receive the same share of fresh weighted draws even when one of them answers
5
+ the same small prompts noticeably later (a cohort whose upstream reasons on a
6
+ large share of turns, or one on a slower upstream path). Every conversation
7
+ that lands there pays that cohort for its whole sticky lifetime.
8
+
9
+ This module keeps a bounded list of recent first-token latencies per account
10
+ for one narrow slice of traffic -- successful ``normal`` turns with a small
11
+ uncached input, no or low reasoning effort, a single upstream attempt and no
12
+ local queueing (the request kinds ``warmup``, ``compaction`` and
13
+ ``realtime_live``, any row that waited on the response-create gate or bridge
14
+ queue, and any WebSocket/bridge row whose first-token latency spans a retried
15
+ send or an account-capacity wait are excluded) -- and derives a
16
+ draw-weight multiplier relative to the fleet: an account whose estimate sits
17
+ above the fleet median by more than the deadband is discounted to
18
+ ``max(floor, fleet / account)``. ``latency_cohort.apply_latency_cohort_weights``
19
+ combines it with the per-model output-throughput multiplier
20
+ (``throughput_cohort.py``) as the minimum of the two and applies it to fresh
21
+ weighted draws; sticky owners, deterministic probes and deterministic
22
+ strategies never consult it. The signal is replica-local and never persisted.
23
+
24
+ Estimator: the per-account estimate is the mean of the samples below the top
25
+ decile (an upper-trimmed mean). The slow cohort observed in production is
26
+ bimodal -- a large share of its turns reason before the first token -- so a
27
+ plain median sits on the no-reasoning mode and misses the share, while a plain
28
+ mean is dominated by a single 30 s stall; trimming the tail keeps the mode
29
+ share visible and drops the stall. The fleet reference is the median of the
30
+ per-account estimates, so a uniformly slow fleet (upstream-wide degradation)
31
+ stays neutral. Estimator changes are one-function edits here, not settings.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import math
37
+ from collections.abc import Mapping
38
+ from statistics import median
39
+ from typing import Any
40
+
41
+ from app.modules.proxy._load_balancer.types import RuntimeState
42
+
43
+ # One hour of eligible turns: long enough for a quiet account to keep evidence,
44
+ # short enough that an account changing cohort is re-weighted within the hour.
45
+ TTFT_SAMPLE_WINDOW_SECONDS = 3600.0
46
+ # Memory bound per account when an account exceeds ~one eligible turn a
47
+ # minute; the window bounds staleness, the cap bounds the list.
48
+ TTFT_MAX_SAMPLES = 64
49
+ # Below this many samples an account's estimate is noise and stays neutral.
50
+ TTFT_MIN_SAMPLES = 8
51
+ # Below this many accounts with evidence there is no fleet to be relative to.
52
+ TTFT_MIN_ACCOUNTS = 3
53
+ # Only the small-input slice is comparable across accounts; the cap applies
54
+ # to uncached input tokens (input minus cached prefix) so long-lived sticky
55
+ # sessions with a large cached prefix keep sampling.
56
+ TTFT_SAMPLE_MAX_INPUT_TOKENS = 20_000
57
+ TTFT_SAMPLE_EFFORTS: frozenset[str | None] = frozenset({None, "minimal", "low"})
58
+ TTFT_SAMPLE_REQUEST_KIND = "normal"
59
+ # Share of the slowest samples dropped from each account's estimate.
60
+ TTFT_TRIM_FRACTION = 0.1
61
+ # An account within 15% of the fleet reference is not discounted at all.
62
+ TTFT_WEIGHT_DEADBAND = 0.15
63
+ # A 10x outlier still keeps half of its weight: the window keeps sampling it
64
+ # and the discount lifts as soon as its samples recover.
65
+ TTFT_WEIGHT_FLOOR = 0.5
66
+
67
+
68
+ def _prune(samples: list[tuple[float, int]], now: float) -> None:
69
+ oldest_kept = now - TTFT_SAMPLE_WINDOW_SECONDS
70
+ stale = 0
71
+ for recorded_at, _ in samples:
72
+ if recorded_at >= oldest_kept:
73
+ break
74
+ stale += 1
75
+ if stale:
76
+ del samples[:stale]
77
+ excess = len(samples) - TTFT_MAX_SAMPLES
78
+ if excess > 0:
79
+ del samples[:excess]
80
+
81
+
82
+ def _eligible(
83
+ *,
84
+ account_id: str | None,
85
+ status: str,
86
+ request_kind: str,
87
+ latency_first_token_ms: int | None,
88
+ input_tokens: int | None,
89
+ cached_input_tokens: int | None,
90
+ reasoning_effort: str | None,
91
+ latency_upstream_send_ms: int | None,
92
+ queued_wait_ms: int,
93
+ retried: bool,
94
+ ) -> bool:
95
+ return (
96
+ bool(account_id)
97
+ and status == "success"
98
+ and request_kind == TTFT_SAMPLE_REQUEST_KIND
99
+ and not retried
100
+ and latency_first_token_ms is not None
101
+ and latency_upstream_send_ms is not None
102
+ and 0 <= latency_upstream_send_ms <= latency_first_token_ms
103
+ and input_tokens is not None
104
+ and input_tokens - (cached_input_tokens or 0) < TTFT_SAMPLE_MAX_INPUT_TOKENS
105
+ and reasoning_effort in TTFT_SAMPLE_EFFORTS
106
+ and queued_wait_ms <= 0
107
+ )
108
+
109
+
110
+ def record_ttft_sample(
111
+ balancer: Any,
112
+ *,
113
+ account_id: str | None,
114
+ status: str,
115
+ request_kind: str,
116
+ latency_first_token_ms: int | None,
117
+ input_tokens: int | None,
118
+ cached_input_tokens: int | None = None,
119
+ reasoning_effort: str | None,
120
+ latency_upstream_send_ms: int | None,
121
+ queued_wait_ms: int,
122
+ retried: bool = False,
123
+ ) -> None:
124
+ """Record one eligible first-token latency for ``account_id`` at the balancer clock.
125
+
126
+ The sample is ``latency_first_token_ms - latency_upstream_send_ms``: the
127
+ row's first-token latency runs from the request start, and on the bridge
128
+ path the start precedes session lookup, reconnect, prewarm, image inlining
129
+ and payload slimming, so the account is only charged from its
130
+ ``response.create`` send. ``latency_upstream_send_ms`` is the start-to-send
131
+ offset (``0`` when the caller's clock already starts at the send, as the
132
+ HTTP stream's attempt clock does); a row without one (``None``) or with a
133
+ send after its first token has no usable anchor and is never sampled.
134
+ ``retried`` marks a row whose first-token latency spans more than one
135
+ upstream send or an account-capacity wait (bridge retries, transparent
136
+ direct-WebSocket replays after a dropped upstream, retries after a
137
+ capacity wait or an owner-pinned quota error); such a latency includes
138
+ the failed attempt and the reconnect and, after an account switch, would
139
+ be charged to the destination account, so it is never sampled.
140
+ ``queued_wait_ms`` is the response-create gate wait (which includes the
141
+ global response-create admission wait) plus the bridge-queue wait.
142
+
143
+ Called from the request-log funnel on stream close. Synchronous and
144
+ lock-free on purpose: there is no await between reading and writing the
145
+ per-account list, so on the single-threaded loop it cannot interleave
146
+ with selection, and the stream close never waits on the per-account lock
147
+ that ``record_errors`` holds across a database write. No-op when
148
+ ``balancer`` does not expose the runtime map or clock (test doubles).
149
+ """
150
+ runtime_map = getattr(balancer, "_runtime", None)
151
+ clock = getattr(balancer, "_clock", None)
152
+ if not isinstance(runtime_map, dict) or clock is None:
153
+ return
154
+ if not _eligible(
155
+ account_id=account_id,
156
+ status=status,
157
+ request_kind=request_kind,
158
+ latency_first_token_ms=latency_first_token_ms,
159
+ input_tokens=input_tokens,
160
+ cached_input_tokens=cached_input_tokens,
161
+ reasoning_effort=reasoning_effort,
162
+ latency_upstream_send_ms=latency_upstream_send_ms,
163
+ queued_wait_ms=queued_wait_ms,
164
+ retried=retried,
165
+ ):
166
+ return
167
+ if account_id is None or latency_first_token_ms is None or latency_upstream_send_ms is None:
168
+ return # narrowed by ``_eligible``
169
+ now = float(clock.time())
170
+ runtime = runtime_map.setdefault(account_id, RuntimeState())
171
+ samples = runtime.ttft_samples
172
+ if samples is None:
173
+ samples = runtime.ttft_samples = []
174
+ samples.append((now, int(latency_first_token_ms - latency_upstream_send_ms)))
175
+ _prune(samples, now)
176
+
177
+
178
+ def account_ttft_estimate_ms(runtime: RuntimeState | None, now: float) -> float | None:
179
+ """Upper-trimmed mean of the in-window samples; ``None`` below ``TTFT_MIN_SAMPLES``."""
180
+ if runtime is None or not runtime.ttft_samples:
181
+ return None
182
+ oldest_kept = now - TTFT_SAMPLE_WINDOW_SECONDS
183
+ values = sorted(ttft_ms for recorded_at, ttft_ms in runtime.ttft_samples if recorded_at >= oldest_kept)
184
+ if len(values) < TTFT_MIN_SAMPLES:
185
+ return None
186
+ kept = values[: len(values) - math.ceil(len(values) * TTFT_TRIM_FRACTION)]
187
+ return sum(kept) / len(kept)
188
+
189
+
190
+ def ttft_weight_multiplier(account_ms: float, fleet_ms: float) -> float:
191
+ """Draw-weight multiplier in ``[floor, 1.0]`` for an account estimate relative to the fleet."""
192
+ if fleet_ms <= 0.0 or account_ms <= fleet_ms * (1.0 + TTFT_WEIGHT_DEADBAND):
193
+ return 1.0
194
+ return max(TTFT_WEIGHT_FLOOR, fleet_ms / account_ms)
195
+
196
+
197
+ def fleet_ttft_estimates(
198
+ runtime_by_account_id: Mapping[str, RuntimeState],
199
+ now: float,
200
+ ) -> tuple[dict[str, float], float | None]:
201
+ """Per-account estimates and the fleet reference (their median).
202
+
203
+ Computed over the whole runtime map so a selection narrowed to a subset is
204
+ still weighed against the full pool; the reference is ``None`` -- every
205
+ multiplier neutral -- while fewer than ``TTFT_MIN_ACCOUNTS`` accounts hold
206
+ enough samples.
207
+ """
208
+ estimates = {
209
+ account_id: estimate
210
+ for account_id, runtime in runtime_by_account_id.items()
211
+ if (estimate := account_ttft_estimate_ms(runtime, now)) is not None
212
+ }
213
+ fleet_ms = median(estimates.values()) if len(estimates) >= TTFT_MIN_ACCOUNTS else None
214
+ return estimates, fleet_ms
@@ -0,0 +1,97 @@
1
+ """Dashboard-managed routing weights and overload isolation (C2-2 routing/overload).
2
+
3
+ Five balancer knobs -- the overload isolation window, the error-rate weighting
4
+ kill switch, the in-flight pressure penalty, the leased-token weight and the
5
+ account lease TTL -- live in ``dashboard_settings`` with the process
6
+ environment as the fallback the dashboard inherits while its column is NULL
7
+ (``configuration-tiers``: code default < environment < dashboard).
8
+
9
+ Selection is a hot path that must not touch the database and must not read
10
+ settings while holding a runtime lock, so the values are resolved *once per
11
+ selection or lease operation* from the ``SettingsCache`` snapshot the proxy
12
+ service already holds for that stage (the same snapshot the concurrency caps
13
+ come from), frozen into a :class:`RoutingTunables`, and threaded through the
14
+ selection entry points next to ``routing_strategy`` and the caps; nothing is
15
+ re-read inside a lock section. Stages of one request share the cache's TTL
16
+ window. A caller without a snapshot (tests, tools) resolves from the
17
+ environment alone.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+
24
+ from app.core.config.inheritable import resolve_inheritable
25
+ from app.modules.proxy._load_balancer.types import AccountLeaseKind
26
+
27
+ # Code defaults, kept in step with the ``Settings`` fields of the same name so
28
+ # a caller without any settings object still gets the shipped behaviour.
29
+ DEFAULT_OVERLOAD_ISOLATION_SECONDS = 1800
30
+ DEFAULT_ERROR_RATE_WEIGHTING_ENABLED = True
31
+ DEFAULT_INFLIGHT_PENALTY_PCT = 2.5
32
+ DEFAULT_LEASE_TOKEN_WEIGHT = 1.0
33
+ DEFAULT_LEASE_TTL_SECONDS = 900.0
34
+
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class RoutingTunables:
38
+ """Effective routing/overload knobs for one selection or lease operation."""
39
+
40
+ overload_isolation_seconds: float = float(DEFAULT_OVERLOAD_ISOLATION_SECONDS)
41
+ error_rate_weighting_enabled: bool = DEFAULT_ERROR_RATE_WEIGHTING_ENABLED
42
+ inflight_penalty_pct: float = DEFAULT_INFLIGHT_PENALTY_PCT
43
+ lease_token_weight: float = DEFAULT_LEASE_TOKEN_WEIGHT
44
+ lease_ttl_seconds: float = DEFAULT_LEASE_TTL_SECONDS
45
+
46
+
47
+ def resolve_routing_tunables(
48
+ dashboard_settings: object | None,
49
+ *,
50
+ startup_settings: object,
51
+ ) -> RoutingTunables:
52
+ """Resolve the five knobs as code default < environment < dashboard.
53
+
54
+ ``dashboard_settings`` is the cached ``dashboard_settings`` row (or ``None``
55
+ when the caller has no snapshot); ``startup_settings`` is the process
56
+ ``Settings``. Both are read with ``getattr`` so partial stand-ins used by
57
+ tests resolve to the code default for the fields they omit.
58
+ """
59
+
60
+ def pick[T: (int, float, bool)](name: str, default: T) -> T:
61
+ column = getattr(dashboard_settings, name, None)
62
+ environment = getattr(startup_settings, name, None)
63
+ return resolve_inheritable(column, environment, default).value
64
+
65
+ return RoutingTunables(
66
+ overload_isolation_seconds=float(pick("proxy_overload_isolation_seconds", DEFAULT_OVERLOAD_ISOLATION_SECONDS)),
67
+ error_rate_weighting_enabled=bool(
68
+ pick("proxy_account_error_rate_weighting_enabled", DEFAULT_ERROR_RATE_WEIGHTING_ENABLED)
69
+ ),
70
+ inflight_penalty_pct=float(pick("proxy_account_inflight_penalty_pct", DEFAULT_INFLIGHT_PENALTY_PCT)),
71
+ lease_token_weight=float(pick("proxy_account_lease_token_weight", DEFAULT_LEASE_TOKEN_WEIGHT)),
72
+ lease_ttl_seconds=float(pick("proxy_account_lease_ttl_seconds", DEFAULT_LEASE_TTL_SECONDS)),
73
+ )
74
+
75
+
76
+ _ACCOUNT_STREAM_LEASE_STALE_GRACE_SECONDS = 60.0
77
+
78
+
79
+ def account_lease_stale_ttl_seconds(
80
+ kind: AccountLeaseKind, settings: object, *, routing_tunables: RoutingTunables
81
+ ) -> float:
82
+ """Age after which a lease of ``kind`` is reclaimed as stale.
83
+
84
+ The lease TTL comes from the dashboard snapshot; a stream lease also
85
+ outlives the effective request budgets (``settings`` carries the C2-1
86
+ dashboard overlay) plus a grace period.
87
+ """
88
+ ttl_seconds = float(routing_tunables.lease_ttl_seconds)
89
+ if kind != "stream":
90
+ return ttl_seconds
91
+ valid_stream_budget_seconds = max(
92
+ ttl_seconds,
93
+ float(getattr(settings, "proxy_request_budget_seconds", ttl_seconds)),
94
+ float(getattr(settings, "http_responses_stream_request_budget_seconds", ttl_seconds)),
95
+ float(getattr(settings, "http_responses_session_bridge_request_budget_seconds", ttl_seconds)),
96
+ )
97
+ return max(ttl_seconds, valid_stream_budget_seconds + _ACCOUNT_STREAM_LEASE_STALE_GRACE_SECONDS)
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ AccountLeaseKind = Literal["response_create", "stream"]
7
+
8
+ MAX_SELECTION_ATTEMPTS = 4
9
+
10
+
11
+ @dataclass
12
+ class RuntimeState:
13
+ reset_at: float | None = None
14
+ cooldown_until: float | None = None
15
+ last_error_at: float | None = None
16
+ last_selected_at: float | None = None
17
+ error_count: int = 0
18
+ version: int = 0
19
+ health_version: int = 0
20
+ blocked_at: float | None = None
21
+ health_tier: int = 0
22
+ drain_entered_at: float | None = None
23
+ probe_success_streak: int = 0
24
+ inflight_response_creates: int = 0
25
+ inflight_streams: int = 0
26
+ leased_tokens: float = 0.0
27
+ leases: dict[str, AccountLease] | None = None
28
+ stream_key_inflight: dict[str, int] | None = None
29
+ # Upstream-overload soft backoff (see ``_load_balancer/overload_backoff.py``).
30
+ # Recent ``server_is_overloaded`` rejection times inside the trip window,
31
+ # the deprioritization deadline once tripped, the exponential level, and
32
+ # when the level last tripped (for decay). Replica-local, never persisted.
33
+ overload_rejections: list[float] | None = None
34
+ # Recent *soft* overload observations: bare ``server_error`` terminals that
35
+ # upstream returns for the same admission-rejection condition but without
36
+ # the explicit overload code. Counted at a fractional weight so a genuine
37
+ # one-off fault never trips the window on its own, while a sustained
38
+ # ``server_error`` refusal still deprioritizes the account.
39
+ soft_overload_rejections: list[float] | None = None
40
+ overload_backoff_until: float | None = None
41
+ overload_backoff_level: int = 0
42
+ overload_last_trip_at: float | None = None
43
+ # Isolation stage deadline (subset of the backoff deadline): while set and
44
+ # in the future, soft sticky owners are rerouted too.
45
+ overload_isolated_until: float | None = None
46
+ # Short burst cooldown set by a code-less upstream HTTP 429 (per-account
47
+ # burst/concurrency rejection, no ``rate_limit_exceeded`` / usage code):
48
+ # fresh (unbound) selection and fresh sticky bindings avoid the account
49
+ # until this deadline while another candidate exists. Established sticky
50
+ # owners and hard continuity owners are untouched. Replica-local, never
51
+ # persisted, independent of ``cooldown_until`` / persisted RATE_LIMITED.
52
+ burst_backoff_until: float | None = None
53
+ # Recent upstream outcome window (see ``_load_balancer/error_rate.py``):
54
+ # minute bucket -> [successes, failures], pruned to the configured window.
55
+ # Feeds the selection weight multiplier; replica-local, never persisted.
56
+ outcome_buckets: dict[int, list[int]] | None = None
57
+ # Recent eligible first-token latencies (see ``_load_balancer/ttft_cohort.py``):
58
+ # ``(recorded_at, ttft_ms)`` pruned to the window/cap. Replica-local, never persisted.
59
+ ttft_samples: list[tuple[float, int]] | None = None
60
+ # Recent eligible output throughputs per model (see
61
+ # ``_load_balancer/throughput_cohort.py``): model -> ``(recorded_at, tokens_per_second)``
62
+ # pruned to the window/cap; models without in-window samples are dropped.
63
+ tps_samples: dict[str, list[tuple[float, float]]] | None = None
64
+ # Transition-log gates of ``_load_balancer/latency_cohort.py``: the last
65
+ # applied first-token multiplier (model-agnostic) and the last applied
66
+ # non-neutral throughput multiplier per model (neutral models are dropped,
67
+ # so the map only grows with models the account is discounted on).
68
+ ttft_weight: float = 1.0
69
+ tps_weights: dict[str, float] | None = None
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class ProbeReservation:
74
+ account_id: str
75
+ previous_last_selected_at: float | None
76
+ reserved_at: float
77
+ expected_runtime_version: int
78
+
79
+
80
+ @dataclass(frozen=True, slots=True)
81
+ class AccountLease:
82
+ lease_id: str
83
+ account_id: str
84
+ kind: AccountLeaseKind
85
+ acquired_at: float
86
+ estimated_tokens: float = 0.0
87
+ api_key_id: str | None = None
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class AccountConcurrencyCaps:
92
+ response_create_limit: int
93
+ stream_limit: int
94
+ configured_response_create_limit: int | None = None
95
+ configured_stream_limit: int | None = None
96
+ replica_count: int = 1