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,931 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from collections.abc import AsyncIterator, Iterable, Mapping
5
+ from dataclasses import dataclass, field
6
+ from typing import Literal, cast
7
+
8
+ from pydantic import BaseModel, ConfigDict, ValidationError
9
+
10
+ from app.core.errors import (
11
+ OpenAIErrorParam,
12
+ is_previous_response_not_found_public_shape,
13
+ previous_response_stream_incomplete_error,
14
+ sanitize_public_error_detail,
15
+ )
16
+ from app.core.openai.models import OpenAIError, OpenAIErrorEnvelope, ResponseUsage
17
+ from app.core.openai.parsing import classify_event_type
18
+ from app.core.types import JsonValue
19
+ from app.core.utils.json_guards import is_json_mapping
20
+ from app.core.utils.sse import format_sse_data, parse_sse_data_json
21
+
22
+
23
+ class ChatToolCallFunction(BaseModel):
24
+ model_config = ConfigDict(extra="forbid")
25
+
26
+ name: str | None = None
27
+ arguments: str | None = None
28
+
29
+
30
+ class ChatToolCallDelta(BaseModel):
31
+ model_config = ConfigDict(extra="forbid")
32
+
33
+ index: int
34
+ id: str | None = None
35
+ type: str = "function"
36
+ function: ChatToolCallFunction | None = None
37
+
38
+
39
+ class ChatChunkDelta(BaseModel):
40
+ model_config = ConfigDict(extra="forbid")
41
+
42
+ role: str | None = None
43
+ content: str | None = None
44
+ refusal: str | None = None
45
+ tool_calls: list[ChatToolCallDelta] | None = None
46
+
47
+
48
+ class ChatChunkChoice(BaseModel):
49
+ model_config = ConfigDict(extra="forbid")
50
+
51
+ index: int
52
+ delta: ChatChunkDelta
53
+ finish_reason: str | None = None
54
+
55
+
56
+ class ChatCompletionChunk(BaseModel):
57
+ model_config = ConfigDict(extra="forbid")
58
+
59
+ id: str
60
+ object: str = "chat.completion.chunk"
61
+ created: int
62
+ model: str
63
+ choices: list[ChatChunkChoice]
64
+ usage: "ChatCompletionUsage | None" = None
65
+
66
+
67
+ class ChatMessageToolCall(BaseModel):
68
+ model_config = ConfigDict(extra="forbid")
69
+
70
+ id: str | None = None
71
+ type: str = "function"
72
+ function: ChatToolCallFunction | None = None
73
+
74
+
75
+ class ChatCompletionMessage(BaseModel):
76
+ model_config = ConfigDict(extra="forbid")
77
+
78
+ role: str
79
+ content: str | None = None
80
+ refusal: str | None = None
81
+ tool_calls: list[ChatMessageToolCall] | None = None
82
+
83
+
84
+ class ChatCompletionChoice(BaseModel):
85
+ model_config = ConfigDict(extra="forbid")
86
+
87
+ index: int
88
+ message: ChatCompletionMessage
89
+ finish_reason: str | None = None
90
+
91
+
92
+ class ChatCompletionUsage(BaseModel):
93
+ model_config = ConfigDict(extra="forbid")
94
+
95
+ prompt_tokens: int | None = None
96
+ completion_tokens: int | None = None
97
+ total_tokens: int | None = None
98
+ prompt_tokens_details: "ChatPromptTokensDetails | None" = None
99
+ completion_tokens_details: "ChatCompletionTokensDetails | None" = None
100
+
101
+
102
+ class ChatPromptTokensDetails(BaseModel):
103
+ model_config = ConfigDict(extra="forbid")
104
+
105
+ cached_tokens: int | None = None
106
+
107
+
108
+ class ChatCompletionTokensDetails(BaseModel):
109
+ model_config = ConfigDict(extra="forbid")
110
+
111
+ reasoning_tokens: int | None = None
112
+
113
+
114
+ class ChatCompletion(BaseModel):
115
+ model_config = ConfigDict(extra="forbid")
116
+
117
+ id: str
118
+ object: str = "chat.completion"
119
+ created: int
120
+ model: str
121
+ choices: list[ChatCompletionChoice]
122
+ usage: ChatCompletionUsage | None = None
123
+
124
+
125
+ ChatCompletionResult = ChatCompletion | OpenAIErrorEnvelope
126
+
127
+
128
+ @dataclass
129
+ class ToolCallIndex:
130
+ indexes: dict[str, int] = field(default_factory=dict)
131
+ next_index: int = 0
132
+ output_index_map: dict[int, int] = field(default_factory=dict)
133
+
134
+ def index_for_output_index(self, output_index: int | None, call_id: str | None, name: str | None) -> int:
135
+ """Resolve tool call index, preferring output_index mapping when available.
136
+
137
+ Always records the resolved index against every routing key that is
138
+ present (``output_index``, ``call_id``/``name``) so a later event for
139
+ the same logical tool call that only carries one of those identifiers
140
+ does not get assigned a new slot. Without this, a stream that first
141
+ routes by ``item_id`` + ``output_index`` and later emits a
142
+ ``call_id``-only event without ``output_index`` (e.g. legacy delta
143
+ formats) would split one tool call into two ``tool_calls[]`` slots and
144
+ reintroduce the duplication this change fixes.
145
+ """
146
+ key = _tool_call_key(call_id, name)
147
+ if output_index is not None and output_index in self.output_index_map:
148
+ idx = self.output_index_map[output_index]
149
+ if key is not None and key not in self.indexes:
150
+ self.indexes[key] = idx
151
+ return idx
152
+ if key is None:
153
+ if output_index is not None:
154
+ idx = self.output_index_map.get(output_index, self.next_index)
155
+ if output_index not in self.output_index_map:
156
+ self.output_index_map[output_index] = idx
157
+ self.next_index = max(self.next_index, idx + 1)
158
+ return idx
159
+ return 0
160
+ if key not in self.indexes:
161
+ self.indexes[key] = self.next_index
162
+ self.next_index += 1
163
+ idx = self.indexes[key]
164
+ if output_index is not None and output_index not in self.output_index_map:
165
+ self.output_index_map[output_index] = idx
166
+ return idx
167
+
168
+ def register_alias(self, alias_id: str, index: int) -> None:
169
+ """Register an additional ID that maps to the same index."""
170
+ key = f"id:{alias_id}"
171
+ if key not in self.indexes:
172
+ self.indexes[key] = index
173
+
174
+ def register_output_index(self, output_index: int, index: int) -> None:
175
+ """Map a Responses API output_index to a tool call index."""
176
+ if output_index not in self.output_index_map:
177
+ self.output_index_map[output_index] = index
178
+
179
+
180
+ @dataclass
181
+ class _ChatChunkState:
182
+ tool_index: ToolCallIndex = field(default_factory=ToolCallIndex)
183
+ tool_calls: list["ToolCallState"] = field(default_factory=list)
184
+ saw_tool_call: bool = False
185
+ sent_role: bool = False
186
+
187
+
188
+ @dataclass
189
+ class ToolCallDelta:
190
+ index: int
191
+ call_id: str | None
192
+ name: str | None
193
+ arguments: str | None
194
+ tool_type: str | None
195
+ arguments_mode: Literal["append", "replace"] = "append"
196
+
197
+ def to_chunk_call(self) -> ChatToolCallDelta:
198
+ function = _build_tool_call_function(self.name, self.arguments)
199
+ return ChatToolCallDelta(
200
+ index=self.index,
201
+ id=self.call_id,
202
+ type=self.tool_type or "function",
203
+ function=function,
204
+ )
205
+
206
+
207
+ @dataclass
208
+ class ToolCallState:
209
+ index: int
210
+ call_id: str | None = None
211
+ name: str | None = None
212
+ arguments: str = ""
213
+ tool_type: str = "function"
214
+ emitted_call_id: str | None = None
215
+ emitted_name: str | None = None
216
+ emitted_arguments: str = ""
217
+ emitted_tool_type: str | None = None
218
+
219
+ def apply_delta(self, delta: ToolCallDelta) -> None:
220
+ if delta.call_id:
221
+ self.call_id = delta.call_id
222
+ if delta.name:
223
+ self.name = delta.name
224
+ if delta.arguments is not None:
225
+ if delta.arguments_mode == "replace":
226
+ self.arguments = delta.arguments
227
+ elif delta.arguments:
228
+ self.arguments += delta.arguments
229
+ if delta.tool_type:
230
+ self.tool_type = delta.tool_type
231
+
232
+ def build_stream_delta(self) -> ToolCallDelta | None:
233
+ call_id = _pending_stream_value(self.emitted_call_id, self.call_id)
234
+ name = _pending_stream_value(self.emitted_name, self.name)
235
+ arguments = _pending_stream_arguments(self.emitted_arguments, self.arguments)
236
+ tool_type = self.tool_type or "function"
237
+ if call_id is None and name is None and arguments is None and self.emitted_tool_type == tool_type:
238
+ return None
239
+
240
+ if call_id is not None:
241
+ self.emitted_call_id = self.call_id
242
+ if name is not None:
243
+ self.emitted_name = self.name
244
+ if arguments is not None:
245
+ self.emitted_arguments = self.arguments
246
+ self.emitted_tool_type = tool_type
247
+ return ToolCallDelta(
248
+ index=self.index,
249
+ call_id=call_id,
250
+ name=name,
251
+ arguments=arguments,
252
+ tool_type=tool_type,
253
+ )
254
+
255
+ def to_message_tool_call(self) -> ChatMessageToolCall | None:
256
+ function = _build_tool_call_function(self.name, self.arguments or None)
257
+ if self.call_id is None and function is None:
258
+ return None
259
+ return ChatMessageToolCall(
260
+ id=self.call_id,
261
+ type=self.tool_type or "function",
262
+ function=function,
263
+ )
264
+
265
+
266
+ def _build_tool_call_function(name: str | None, arguments: str | None) -> ChatToolCallFunction | None:
267
+ if name is None and arguments is None:
268
+ return None
269
+ return ChatToolCallFunction(name=name, arguments=arguments)
270
+
271
+
272
+ def _parse_data(line: str) -> dict[str, JsonValue] | None:
273
+ return parse_sse_data_json(line)
274
+
275
+
276
+ def iter_chat_chunks(
277
+ lines: Iterable[str],
278
+ model: str,
279
+ *,
280
+ created: int | None = None,
281
+ state: _ChatChunkState | None = None,
282
+ include_usage: bool = False,
283
+ ) -> Iterable[str]:
284
+ created = created or int(time.time())
285
+ state = state or _ChatChunkState()
286
+ for line in lines:
287
+ payload = _parse_data(line)
288
+ if not payload:
289
+ continue
290
+ event_type = classify_event_type(payload)
291
+ if event_type in ("response.output_text.delta", "response.refusal.delta"):
292
+ delta_text = payload.get("delta")
293
+ role = None
294
+ if not state.sent_role:
295
+ role = "assistant"
296
+ if event_type == "response.refusal.delta":
297
+ delta_obj = ChatChunkDelta(
298
+ role=role,
299
+ refusal=delta_text if isinstance(delta_text, str) else None,
300
+ )
301
+ else:
302
+ delta_obj = ChatChunkDelta(
303
+ role=role,
304
+ content=delta_text if isinstance(delta_text, str) else None,
305
+ )
306
+ chunk = ChatCompletionChunk(
307
+ id="chatcmpl_temp",
308
+ created=created,
309
+ model=model,
310
+ choices=[
311
+ ChatChunkChoice(
312
+ index=0,
313
+ delta=delta_obj,
314
+ finish_reason=None,
315
+ )
316
+ ],
317
+ )
318
+ yield _dump_chunk(chunk, include_usage=include_usage)
319
+ if role is not None:
320
+ state.sent_role = True
321
+ tool_delta = _tool_call_delta_from_payload(payload, state.tool_index)
322
+ if tool_delta is not None:
323
+ tool_state = _merge_tool_call_delta(state.tool_calls, tool_delta)
324
+ stream_delta = tool_state.build_stream_delta()
325
+ if stream_delta is not None:
326
+ state.saw_tool_call = True
327
+ role = None
328
+ if not state.sent_role:
329
+ role = "assistant"
330
+ chunk = ChatCompletionChunk(
331
+ id="chatcmpl_temp",
332
+ created=created,
333
+ model=model,
334
+ choices=[
335
+ ChatChunkChoice(
336
+ index=0,
337
+ delta=ChatChunkDelta(
338
+ role=role,
339
+ tool_calls=[stream_delta.to_chunk_call()],
340
+ ),
341
+ finish_reason=None,
342
+ )
343
+ ],
344
+ )
345
+ yield _dump_chunk(chunk, include_usage=include_usage)
346
+ if role is not None:
347
+ state.sent_role = True
348
+ if event_type in ("response.failed", "error"):
349
+ error = None
350
+ if event_type == "response.failed":
351
+ response = payload.get("response")
352
+ if isinstance(response, dict):
353
+ maybe_error = response.get("error")
354
+ if isinstance(maybe_error, dict) and maybe_error:
355
+ error = maybe_error
356
+ else:
357
+ maybe_error = payload.get("error")
358
+ if isinstance(maybe_error, dict) and maybe_error:
359
+ error = maybe_error
360
+ if error is not None:
361
+ error_mapping = cast(Mapping[str, JsonValue], error)
362
+ parsed_error = _parse_error_mapping(error_mapping)
363
+ if parsed_error is not None and is_previous_response_not_found_public_shape(
364
+ code=parsed_error.code,
365
+ param=parsed_error.param_state,
366
+ message=parsed_error.message,
367
+ ):
368
+ error_payload = {"error": cast(JsonValue, previous_response_stream_incomplete_error()["error"])}
369
+ else:
370
+ error_payload = {"error": sanitize_public_error_detail(error_mapping)}
371
+ else:
372
+ error_payload = cast(
373
+ dict[str, JsonValue],
374
+ _default_error_envelope().model_dump(mode="json", exclude_none=True),
375
+ )
376
+ yield _dump_sse(error_payload)
377
+ yield "data: [DONE]\n\n"
378
+ return
379
+ if event_type in ("response.completed", "response.incomplete"):
380
+ for tool_state in state.tool_calls:
381
+ stream_delta = tool_state.build_stream_delta()
382
+ if stream_delta is None:
383
+ continue
384
+ state.saw_tool_call = True
385
+ role = None
386
+ if not state.sent_role:
387
+ role = "assistant"
388
+ chunk = ChatCompletionChunk(
389
+ id="chatcmpl_temp",
390
+ created=created,
391
+ model=model,
392
+ choices=[
393
+ ChatChunkChoice(
394
+ index=0,
395
+ delta=ChatChunkDelta(
396
+ role=role,
397
+ tool_calls=[stream_delta.to_chunk_call()],
398
+ ),
399
+ finish_reason=None,
400
+ )
401
+ ],
402
+ )
403
+ yield _dump_chunk(chunk, include_usage=include_usage)
404
+ if role is not None:
405
+ state.sent_role = True
406
+ usage = None
407
+ if include_usage:
408
+ response = payload.get("response")
409
+ if isinstance(response, dict):
410
+ usage = _map_usage(_parse_usage(response.get("usage")))
411
+ finish_reason = "tool_calls" if state.saw_tool_call else "stop"
412
+ if event_type == "response.incomplete" and not state.saw_tool_call:
413
+ finish_reason = _finish_reason_from_incomplete(payload.get("response"))
414
+ done = ChatCompletionChunk(
415
+ id="chatcmpl_temp",
416
+ created=created,
417
+ model=model,
418
+ choices=[
419
+ ChatChunkChoice(
420
+ index=0,
421
+ delta=ChatChunkDelta(),
422
+ finish_reason=finish_reason,
423
+ )
424
+ ],
425
+ )
426
+ yield _dump_chunk(done, include_usage=include_usage)
427
+ if include_usage:
428
+ usage_chunk = ChatCompletionChunk(
429
+ id="chatcmpl_temp",
430
+ created=created,
431
+ model=model,
432
+ choices=[],
433
+ usage=usage,
434
+ )
435
+ yield _dump_chunk(usage_chunk, include_usage=include_usage)
436
+ yield "data: [DONE]\n\n"
437
+ return
438
+
439
+
440
+ async def stream_chat_chunks(
441
+ stream: AsyncIterator[str],
442
+ model: str,
443
+ *,
444
+ include_usage: bool = False,
445
+ ) -> AsyncIterator[str]:
446
+ created = int(time.time())
447
+ state = _ChatChunkState()
448
+ terminal_chunk_sent = False
449
+ async for line in stream:
450
+ if terminal_chunk_sent:
451
+ continue
452
+ for chunk in iter_chat_chunks(
453
+ [line],
454
+ model=model,
455
+ created=created,
456
+ state=state,
457
+ include_usage=include_usage,
458
+ ):
459
+ yield chunk
460
+ if chunk.strip() == "data: [DONE]":
461
+ terminal_chunk_sent = True
462
+ break
463
+ if not terminal_chunk_sent:
464
+ yield _dump_sse(_upstream_stream_truncated_error_payload())
465
+ yield "data: [DONE]\n\n"
466
+
467
+
468
+ async def collect_chat_completion(stream: AsyncIterator[str], model: str) -> ChatCompletionResult:
469
+ created = int(time.time())
470
+ content_parts: list[str] = []
471
+ refusal_parts: list[str] = []
472
+ response_id: str | None = None
473
+ usage: ResponseUsage | None = None
474
+ incomplete_reason: str | None = None
475
+ tool_index = ToolCallIndex()
476
+ tool_calls: list[ToolCallState] = []
477
+ terminal_error: ChatCompletionResult | None = None
478
+ terminal_event_seen = False
479
+
480
+ async for line in stream:
481
+ payload = _parse_data(line)
482
+ if not payload:
483
+ continue
484
+ event_type = classify_event_type(payload)
485
+ if event_type == "response.output_text.delta":
486
+ delta = payload.get("delta")
487
+ if isinstance(delta, str):
488
+ content_parts.append(delta)
489
+ if event_type == "response.refusal.delta":
490
+ delta = payload.get("delta")
491
+ if isinstance(delta, str):
492
+ refusal_parts.append(delta)
493
+ tool_delta = _tool_call_delta_from_payload(payload, tool_index)
494
+ if tool_delta is not None:
495
+ _merge_tool_call_delta(tool_calls, tool_delta)
496
+ if event_type in ("response.failed", "error"):
497
+ if terminal_error is not None:
498
+ continue
499
+ error = None
500
+ if event_type == "response.failed":
501
+ response = payload.get("response")
502
+ if isinstance(response, dict):
503
+ maybe_error = response.get("error")
504
+ if isinstance(maybe_error, dict):
505
+ error = maybe_error
506
+ else:
507
+ maybe_error = payload.get("error")
508
+ if isinstance(maybe_error, dict):
509
+ error = maybe_error
510
+ if error is None:
511
+ terminal_error = _default_error_envelope()
512
+ else:
513
+ error_mapping = cast(Mapping[str, JsonValue], error)
514
+ parsed_error = _parse_error_mapping(error_mapping)
515
+ if parsed_error is not None and is_previous_response_not_found_public_shape(
516
+ code=parsed_error.code,
517
+ param=parsed_error.param_state,
518
+ message=parsed_error.message,
519
+ ):
520
+ terminal_error = OpenAIErrorEnvelope(
521
+ error=OpenAIError.model_validate(previous_response_stream_incomplete_error()["error"])
522
+ )
523
+ else:
524
+ terminal_error = _error_envelope_from_payload(error_mapping)
525
+ continue
526
+ if terminal_error is not None:
527
+ continue
528
+ if event_type in ("response.completed", "response.incomplete"):
529
+ terminal_event_seen = True
530
+ response = payload.get("response")
531
+ if isinstance(response, dict):
532
+ response_id_value = response.get("id")
533
+ if isinstance(response_id_value, str):
534
+ response_id = response_id_value
535
+ usage = _parse_usage(response.get("usage"))
536
+ if event_type == "response.incomplete":
537
+ incomplete_reason = _finish_reason_from_incomplete(response)
538
+
539
+ if terminal_error is not None:
540
+ return terminal_error
541
+ if not terminal_event_seen:
542
+ return _upstream_stream_truncated_error()
543
+
544
+ message_content: str | None = "".join(content_parts)
545
+ message_refusal = "".join(refusal_parts) or None
546
+ message_tool_calls = _compact_tool_calls(tool_calls)
547
+ has_tool_calls = bool(message_tool_calls)
548
+ finish_reason = "tool_calls" if has_tool_calls else (incomplete_reason or "stop")
549
+ if (has_tool_calls or message_refusal) and not message_content:
550
+ message_content = None
551
+ message = ChatCompletionMessage(
552
+ role="assistant",
553
+ content=message_content,
554
+ refusal=message_refusal,
555
+ tool_calls=message_tool_calls or None,
556
+ )
557
+ choice = ChatCompletionChoice(
558
+ index=0,
559
+ message=message,
560
+ finish_reason=finish_reason,
561
+ )
562
+ completion = ChatCompletion(
563
+ id=response_id or "chatcmpl_temp",
564
+ created=created,
565
+ model=model,
566
+ choices=[choice],
567
+ usage=_map_usage(usage),
568
+ )
569
+ return completion
570
+
571
+
572
+ def _map_usage(usage: ResponseUsage | None) -> ChatCompletionUsage | None:
573
+ if usage is None:
574
+ return None
575
+ prompt_tokens = usage.input_tokens
576
+ completion_tokens = usage.output_tokens
577
+ total_tokens = usage.total_tokens
578
+ if prompt_tokens is None and completion_tokens is None and total_tokens is None:
579
+ return None
580
+ prompt_details = None
581
+ cached_tokens = usage.input_tokens_details.cached_tokens if usage.input_tokens_details else None
582
+ if cached_tokens is not None:
583
+ prompt_details = ChatPromptTokensDetails(cached_tokens=cached_tokens)
584
+
585
+ completion_details = None
586
+ reasoning_tokens = usage.output_tokens_details.reasoning_tokens if usage.output_tokens_details else None
587
+ if reasoning_tokens is not None:
588
+ completion_details = ChatCompletionTokensDetails(reasoning_tokens=reasoning_tokens)
589
+ return ChatCompletionUsage(
590
+ prompt_tokens=prompt_tokens,
591
+ completion_tokens=completion_tokens,
592
+ total_tokens=total_tokens,
593
+ prompt_tokens_details=prompt_details,
594
+ completion_tokens_details=completion_details,
595
+ )
596
+
597
+
598
+ def _parse_usage(value: JsonValue) -> ResponseUsage | None:
599
+ if not isinstance(value, dict):
600
+ return None
601
+ try:
602
+ return ResponseUsage.model_validate(value)
603
+ except ValidationError:
604
+ return None
605
+
606
+
607
+ def _dump_chunk(chunk: ChatCompletionChunk, *, include_usage: bool = False) -> str:
608
+ payload = chunk.model_dump(mode="json", exclude_none=True)
609
+ if include_usage and "usage" not in payload:
610
+ payload["usage"] = None
611
+ return _dump_sse(payload)
612
+
613
+
614
+ def _dump_sse(payload: Mapping[str, JsonValue]) -> str:
615
+ return format_sse_data(payload)
616
+
617
+
618
+ def _upstream_stream_truncated_error_payload() -> dict[str, JsonValue]:
619
+ return {
620
+ "error": {
621
+ "message": "Responses stream ended before a terminal event",
622
+ "type": "server_error",
623
+ "code": "upstream_stream_truncated",
624
+ }
625
+ }
626
+
627
+
628
+ def _upstream_stream_truncated_error() -> OpenAIErrorEnvelope:
629
+ return OpenAIErrorEnvelope.model_validate(_upstream_stream_truncated_error_payload())
630
+
631
+
632
+ def _finish_reason_from_incomplete(response: JsonValue | None) -> str:
633
+ response_mapping = _as_mapping(response)
634
+ if response_mapping is None:
635
+ return "stop"
636
+ details = _as_mapping(response_mapping.get("incomplete_details"))
637
+ if details is not None:
638
+ reason = details.get("reason")
639
+ if reason in ("max_output_tokens", "max_tokens"):
640
+ return "length"
641
+ if reason == "content_filter":
642
+ return "content_filter"
643
+ return "stop"
644
+
645
+
646
+ def _default_error_envelope() -> OpenAIErrorEnvelope:
647
+ return OpenAIErrorEnvelope(
648
+ error=OpenAIError(
649
+ message="Upstream error",
650
+ type="server_error",
651
+ code="upstream_error",
652
+ )
653
+ )
654
+
655
+
656
+ def _error_envelope_from_payload(payload: Mapping[str, JsonValue]) -> OpenAIErrorEnvelope:
657
+ error = _parse_error_mapping(sanitize_public_error_detail(payload))
658
+ if error is None:
659
+ return _default_error_envelope()
660
+ return OpenAIErrorEnvelope(error=error)
661
+
662
+
663
+ def _parse_error_mapping(payload: Mapping[str, JsonValue]) -> OpenAIError | None:
664
+ """Parse an upstream error while retaining explicit ``param`` presence."""
665
+
666
+ normalized = _normalize_error_payload(payload)
667
+ if not normalized and "param" not in payload:
668
+ return None
669
+ param_state = OpenAIErrorParam.from_mapping(payload)
670
+ try:
671
+ error = OpenAIError.model_validate(normalized)
672
+ except ValidationError:
673
+ message = payload.get("message")
674
+ error_type = payload.get("type")
675
+ code = payload.get("code")
676
+ param = payload.get("param")
677
+ plan_type = payload.get("plan_type")
678
+ error = OpenAIError(
679
+ message=message if isinstance(message, str) else None,
680
+ type=error_type if isinstance(error_type, str) else None,
681
+ code=code if isinstance(code, str) else None,
682
+ param=param if isinstance(param, str) else None,
683
+ plan_type=plan_type if isinstance(plan_type, str) else None,
684
+ resets_at=_coerce_number(payload.get("resets_at")),
685
+ resets_in_seconds=_coerce_number(payload.get("resets_in_seconds")),
686
+ )
687
+ error.set_param_state(param_state)
688
+ return error
689
+
690
+
691
+ def _normalize_error_payload(payload: Mapping[str, JsonValue]) -> dict[str, JsonValue]:
692
+ normalized: dict[str, JsonValue] = {}
693
+ for key in ("message", "type", "code", "param", "plan_type"):
694
+ value = payload.get(key)
695
+ if isinstance(value, str):
696
+ normalized[key] = value
697
+ for key in ("resets_at", "resets_in_seconds"):
698
+ value = payload.get(key)
699
+ number = _coerce_number(value)
700
+ if number is not None:
701
+ normalized[key] = number
702
+ return normalized
703
+
704
+
705
+ def _coerce_number(value: JsonValue) -> int | float | None:
706
+ if isinstance(value, (int, float)):
707
+ return value
708
+ if isinstance(value, str):
709
+ try:
710
+ return float(value.strip())
711
+ except ValueError:
712
+ return None
713
+ return None
714
+
715
+
716
+ def _resolve_output_index(payload: Mapping[str, JsonValue]) -> int | None:
717
+ """Extract output_index from a Responses API event payload."""
718
+ val = payload.get("output_index")
719
+ if isinstance(val, int):
720
+ return val
721
+ return None
722
+
723
+
724
+ def _register_item_id_alias(payload: Mapping[str, JsonValue], index: int, indexer: ToolCallIndex) -> None:
725
+ """Register item.id from output_item events as an index alias.
726
+
727
+ The Responses API emits output_item.added/done events with item.id
728
+ (e.g. "fc_01ee...") and item.call_id (e.g. "call_kI8y...").
729
+ Subsequent function_call_arguments.delta/done events reference the
730
+ tool call via item_id (= item.id), not call_id. By registering
731
+ item.id as an alias when we first see the output_item event, later
732
+ delta/done events can resolve to the correct index.
733
+ """
734
+ item = payload.get("item")
735
+ if not isinstance(item, dict):
736
+ return
737
+ item_id = item.get("id")
738
+ call_id = item.get("call_id")
739
+ if isinstance(item_id, str) and item_id:
740
+ if item_id != call_id:
741
+ indexer.register_alias(item_id, index)
742
+
743
+ output_index = _resolve_output_index(payload)
744
+ if output_index is not None:
745
+ indexer.register_output_index(output_index, index)
746
+
747
+
748
+ def _tool_call_delta_from_payload(payload: Mapping[str, JsonValue], indexer: ToolCallIndex) -> ToolCallDelta | None:
749
+ if not _is_tool_call_event(payload):
750
+ return None
751
+ fields = _extract_tool_call_fields(payload)
752
+ if fields is None:
753
+ return None
754
+ call_id, name, arguments, tool_type = fields
755
+ output_index = _resolve_output_index(payload)
756
+ routing_id = call_id
757
+ if routing_id is None:
758
+ item = payload.get("item")
759
+ routing_id = _first_str(
760
+ payload.get("item_id"),
761
+ item.get("id") if isinstance(item, dict) else None,
762
+ )
763
+ index = indexer.index_for_output_index(output_index, routing_id, name)
764
+ _register_item_id_alias(payload, index, indexer)
765
+ return ToolCallDelta(
766
+ index=index,
767
+ call_id=call_id,
768
+ name=name,
769
+ arguments=arguments,
770
+ tool_type=tool_type,
771
+ arguments_mode=_tool_call_arguments_mode(payload),
772
+ )
773
+
774
+
775
+ def _is_tool_call_event(payload: Mapping[str, JsonValue]) -> bool:
776
+ event_type = payload.get("type")
777
+ if isinstance(event_type, str) and ("tool_call" in event_type or "function_call" in event_type):
778
+ return True
779
+ item = _as_mapping(payload.get("item"))
780
+ if item is not None:
781
+ item_type = item.get("type")
782
+ if isinstance(item_type, str) and ("tool" in item_type or "function" in item_type):
783
+ return True
784
+ if any(key in item for key in ("call_id", "tool_call_id", "arguments", "function", "name")):
785
+ return True
786
+ if any(key in payload for key in ("call_id", "tool_call_id")):
787
+ return True
788
+ if "arguments" in payload and ("name" in payload or "function" in payload):
789
+ return True
790
+ return False
791
+
792
+
793
+ def _extract_tool_call_fields(
794
+ payload: Mapping[str, JsonValue],
795
+ ) -> tuple[str | None, str | None, str | None, str | None] | None:
796
+ candidate = _select_tool_call_candidate(payload)
797
+ delta = candidate.get("delta")
798
+ delta_map = _as_mapping(delta)
799
+ delta_text = delta if isinstance(delta, str) else None
800
+
801
+ call_id = _first_str(
802
+ candidate.get("call_id"),
803
+ candidate.get("tool_call_id"),
804
+ candidate.get("id"),
805
+ )
806
+ if call_id is None and delta_map is not None:
807
+ call_id = _first_str(
808
+ delta_map.get("id"),
809
+ delta_map.get("call_id"),
810
+ delta_map.get("tool_call_id"),
811
+ )
812
+
813
+ name = _first_str(candidate.get("name"), candidate.get("tool_name"))
814
+ if name is None and delta_map is not None:
815
+ name = _first_str(delta_map.get("name"))
816
+ if name is None:
817
+ function = _as_mapping(candidate.get("function"))
818
+ if function is not None:
819
+ name = _first_str(function.get("name"))
820
+ if name is None and delta_map is not None:
821
+ function = _as_mapping(delta_map.get("function"))
822
+ if function is not None:
823
+ name = _first_str(function.get("name"))
824
+
825
+ arguments = None
826
+ candidate_arguments = candidate.get("arguments")
827
+ if isinstance(candidate_arguments, str):
828
+ arguments = candidate_arguments
829
+ if arguments is None and isinstance(delta_text, str):
830
+ arguments = delta_text
831
+ if arguments is None and delta_map is not None:
832
+ delta_arguments = delta_map.get("arguments")
833
+ if isinstance(delta_arguments, str):
834
+ arguments = delta_arguments
835
+ else:
836
+ function = _as_mapping(delta_map.get("function"))
837
+ if function is not None:
838
+ function_arguments = function.get("arguments")
839
+ if isinstance(function_arguments, str):
840
+ arguments = function_arguments
841
+
842
+ tool_type = _first_str(candidate.get("tool_type"), candidate.get("type"))
843
+ if tool_type and tool_type.startswith("response."):
844
+ tool_type = None
845
+ if tool_type in ("tool_call", "function_call"):
846
+ tool_type = "function"
847
+
848
+ if call_id is None and name is None and arguments is None:
849
+ return None
850
+ return call_id, name, arguments, tool_type
851
+
852
+
853
+ def _select_tool_call_candidate(payload: Mapping[str, JsonValue]) -> Mapping[str, JsonValue]:
854
+ item = _as_mapping(payload.get("item"))
855
+ if item is not None:
856
+ item_type = item.get("type")
857
+ if isinstance(item_type, str) and ("tool" in item_type or "function" in item_type):
858
+ return item
859
+ if any(key in item for key in ("call_id", "tool_call_id", "arguments", "function", "name")):
860
+ return item
861
+ return payload
862
+
863
+
864
+ def _tool_call_key(call_id: str | None, name: str | None) -> str | None:
865
+ if call_id:
866
+ return f"id:{call_id}"
867
+ if name:
868
+ return f"name:{name}"
869
+ return None
870
+
871
+
872
+ def _tool_call_arguments_mode(payload: Mapping[str, JsonValue]) -> Literal["append", "replace"]:
873
+ event_type = payload.get("type")
874
+ if isinstance(event_type, str):
875
+ if event_type.endswith(".delta"):
876
+ return "append"
877
+ if event_type.endswith(".done"):
878
+ return "replace"
879
+ if event_type == "response.output_item.added":
880
+ return "replace"
881
+ return "replace"
882
+
883
+
884
+ def _as_mapping(value: JsonValue) -> Mapping[str, JsonValue] | None:
885
+ if is_json_mapping(value):
886
+ return value
887
+ return None
888
+
889
+
890
+ def _first_str(*values: JsonValue) -> str | None:
891
+ for value in values:
892
+ if isinstance(value, str) and value:
893
+ return value
894
+ return None
895
+
896
+
897
+ def _merge_tool_call_delta(tool_calls: list[ToolCallState], delta: ToolCallDelta) -> ToolCallState:
898
+ while len(tool_calls) <= delta.index:
899
+ tool_calls.append(ToolCallState(index=len(tool_calls)))
900
+ tool_calls[delta.index].apply_delta(delta)
901
+ return tool_calls[delta.index]
902
+
903
+
904
+ def _compact_tool_calls(tool_calls: list[ToolCallState]) -> list[ChatMessageToolCall]:
905
+ cleaned: list[ChatMessageToolCall] = []
906
+ for call in tool_calls:
907
+ tool_call = call.to_message_tool_call()
908
+ if tool_call is not None:
909
+ cleaned.append(tool_call)
910
+ return cleaned
911
+
912
+
913
+ def _pending_stream_value(emitted: str | None, current: str | None) -> str | None:
914
+ if current is None:
915
+ return None
916
+ if emitted is None or emitted != current:
917
+ return current
918
+ return None
919
+
920
+
921
+ def _pending_stream_arguments(emitted: str, current: str) -> str | None:
922
+ if not current or current == emitted:
923
+ return None
924
+ if not emitted:
925
+ return current
926
+ if current.startswith(emitted):
927
+ suffix = current[len(emitted) :]
928
+ return suffix or None
929
+ # Chat Completions tool-call arguments are append-only, so snapshot rewrites
930
+ # that change previously emitted bytes cannot be represented safely.
931
+ return None