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,1063 @@
1
+ """Fail-closed checks for moving a retained Responses request between accounts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import deque
6
+ from collections.abc import Container, Mapping
7
+ from dataclasses import dataclass
8
+ from typing import cast
9
+ from urllib.parse import urlsplit
10
+
11
+ from app.core.openai.requests import extract_input_file_ids
12
+ from app.core.types import JsonValue
13
+
14
+ _TOOL_CALL_TYPE_BY_OUTPUT_TYPE = {
15
+ "function_call_output": "function_call",
16
+ "custom_tool_call_output": "custom_tool_call",
17
+ "apply_patch_call_output": "apply_patch_call",
18
+ }
19
+ _TOOL_CALL_TYPES = frozenset(_TOOL_CALL_TYPE_BY_OUTPUT_TYPE.values())
20
+ _ACCOUNT_NEUTRAL_REPLAY_OMITTED_ITEM_TYPES = frozenset(
21
+ {"reasoning", "tool_search_call", "tool_search_output", "web_search_call"}
22
+ )
23
+ _INTERNAL_CHAT_MESSAGE_METADATA_FIELD = "internal_chat_message_metadata_passthrough"
24
+ _ACCOUNT_NEUTRAL_INTERNAL_CHAT_MESSAGE_METADATA_FIELDS = frozenset({"turn_id"})
25
+ _ACCOUNT_NEUTRAL_TOOL_TYPES = frozenset({"custom", "function", "web_search", "web_search_preview"})
26
+ _ACCOUNT_NEUTRAL_TOOL_DECLARATION_FIELDS = {
27
+ "custom": frozenset({"description", "format", "name", "type"}),
28
+ "function": frozenset({"description", "name", "parameters", "strict", "type"}),
29
+ "web_search": frozenset({"filters", "search_context_size", "type", "user_location"}),
30
+ "web_search_preview": frozenset({"filters", "search_context_size", "type", "user_location"}),
31
+ }
32
+ _ACCOUNT_NEUTRAL_TOOL_CHOICE_STRINGS = frozenset({"auto", "none", "required"})
33
+ _ACCOUNT_NEUTRAL_WEB_SEARCH_CONTEXT_SIZES = frozenset({"high", "low", "medium"})
34
+ _ACCOUNT_NEUTRAL_WEB_SEARCH_FILTER_FIELDS = frozenset({"allowed_domains"})
35
+ _ACCOUNT_NEUTRAL_WEB_SEARCH_LOCATION_FIELDS = frozenset({"city", "country", "region", "timezone", "type"})
36
+ _ACCOUNT_NEUTRAL_MESSAGE_ROLES = frozenset({"assistant", "developer", "system", "user"})
37
+ _ACCOUNT_NEUTRAL_INPUT_ITEM_TYPES = frozenset(
38
+ {
39
+ "additional_tools",
40
+ "apply_patch_call",
41
+ "apply_patch_call_output",
42
+ "custom_tool_call",
43
+ "custom_tool_call_output",
44
+ "function_call",
45
+ "function_call_output",
46
+ "input_file",
47
+ "input_image",
48
+ "input_text",
49
+ "message",
50
+ }
51
+ )
52
+ _ACCOUNT_NEUTRAL_MESSAGE_CONTENT_TYPES = frozenset(
53
+ {"input_file", "input_image", "input_text", "output_text", "refusal", "text"}
54
+ )
55
+ _ACCOUNT_NEUTRAL_MESSAGE_FIELDS = frozenset(
56
+ {"content", "id", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "phase", "role", "status", "type"}
57
+ )
58
+ _ACCOUNT_NEUTRAL_CONTENT_FIELDS = {
59
+ "input_file": frozenset({"file_data", "file_id", "file_url", "filename", "type"}),
60
+ "input_image": frozenset({"detail", "file_id", "image_url", "type"}),
61
+ "input_text": frozenset({"text", "type"}),
62
+ "output_text": frozenset({"text", "type"}),
63
+ "refusal": frozenset({"refusal", "type"}),
64
+ "text": frozenset({"text", "type"}),
65
+ }
66
+ _ACCOUNT_NEUTRAL_INPUT_ITEM_FIELDS = {
67
+ "additional_tools": frozenset({"role", "tools", "type"}),
68
+ "apply_patch_call": frozenset(
69
+ {
70
+ "call_id",
71
+ "caller",
72
+ "id",
73
+ "input",
74
+ _INTERNAL_CHAT_MESSAGE_METADATA_FIELD,
75
+ "operation",
76
+ "patch",
77
+ "status",
78
+ "type",
79
+ }
80
+ ),
81
+ "apply_patch_call_output": frozenset(
82
+ {"call_id", "caller", "id", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "output", "status", "type"}
83
+ ),
84
+ "custom_tool_call": frozenset(
85
+ {"call_id", "caller", "id", "input", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "name", "status", "type"}
86
+ ),
87
+ "custom_tool_call_output": frozenset(
88
+ {"call_id", "caller", "id", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "output", "status", "type"}
89
+ ),
90
+ "function_call": frozenset(
91
+ {"arguments", "call_id", "caller", "id", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "name", "status", "type"}
92
+ ),
93
+ "function_call_output": frozenset(
94
+ {"call_id", "caller", "id", _INTERNAL_CHAT_MESSAGE_METADATA_FIELD, "output", "status", "type"}
95
+ ),
96
+ }
97
+ _ACCOUNT_NEUTRAL_ITEM_STATUSES = frozenset({"completed", "failed"})
98
+ _ACCOUNT_NEUTRAL_APPLY_PATCH_OPERATION_FIELDS = {
99
+ "create_file": frozenset({"diff", "path", "type"}),
100
+ "delete_file": frozenset({"path", "type"}),
101
+ "update_file": frozenset({"diff", "path", "type"}),
102
+ }
103
+ _ACCOUNT_NEUTRAL_REASONING_CONFIG_FIELDS = frozenset({"effort", "summary"})
104
+ _ACCOUNT_NEUTRAL_CLIENT_METADATA_FIELDS = frozenset(
105
+ {
106
+ "ws_request_header_x_openai_internal_codex_responses_lite",
107
+ "x-codex-installation-id",
108
+ "x-codex-parent-thread-id",
109
+ "x-codex-turn-metadata",
110
+ "x-codex-window-id",
111
+ "x-openai-subagent",
112
+ }
113
+ )
114
+ _ACCOUNT_SCOPED_HOSTED_INPUT_TYPES = frozenset(
115
+ {
116
+ "code_interpreter_call",
117
+ "computer_call",
118
+ "computer_call_output",
119
+ "file_search_call",
120
+ "image_generation_call",
121
+ "item_reference",
122
+ }
123
+ )
124
+ _RESPONSES_PAYLOAD_FIELDS_WITH_DEDICATED_VALIDATION = frozenset(
125
+ {
126
+ "conversation",
127
+ "client_metadata",
128
+ "include",
129
+ "input",
130
+ "instructions",
131
+ "metadata",
132
+ "model",
133
+ "parallel_tool_calls",
134
+ "previous_response_id",
135
+ "prompt",
136
+ "prompt_cache_key",
137
+ "reasoning",
138
+ "service_tier",
139
+ "store",
140
+ "stream",
141
+ "text",
142
+ "tool_choice",
143
+ "tools",
144
+ "truncation",
145
+ }
146
+ )
147
+
148
+
149
+ @dataclass(frozen=True, slots=True)
150
+ class AccountNeutralReplayProjection:
151
+ input_items: list[JsonValue]
152
+ stored_prefix_count: int
153
+ canonical_lite_developer_index: int | None = None
154
+ """Projected index of the canonical Responses-Lite developer instruction.
155
+
156
+ Set only when the original stored prefix begins with a valid
157
+ ``additional_tools`` bundle and the developer message is that bundle's
158
+ original immediate successor. ``None`` keeps every stored developer
159
+ position fail-closed, so projection can never make a non-adjacent
160
+ developer message look canonical.
161
+ """
162
+
163
+
164
+ def project_responses_input_for_account_neutral_fresh_replay(
165
+ input_items: list[JsonValue],
166
+ *,
167
+ stored_count: int,
168
+ preserve_developer_message_ids: bool = False,
169
+ ) -> AccountNeutralReplayProjection | None:
170
+ """Remove known response-owned bookkeeping after durable prefix proof.
171
+
172
+ ``preserve_developer_message_ids`` is classification-only evidence for
173
+ inline Responses-Lite messages. A projection created with that option must
174
+ not be serialized as an account-neutral replay payload.
175
+ """
176
+
177
+ if stored_count <= 0 or stored_count > len(input_items):
178
+ return None
179
+
180
+ projected_items: list[JsonValue] = []
181
+ projected_stored_count = 0
182
+ canonical_lite_developer_index: int | None = None
183
+ prefix_begins_with_lite_tool_bundle = stored_count >= 2 and _is_canonical_lite_tool_bundle(input_items[0])
184
+ for index, item in enumerate(input_items):
185
+ projected_item = _project_account_neutral_replay_item(
186
+ item,
187
+ preserve_developer_message_ids=preserve_developer_message_ids,
188
+ )
189
+ if projected_item is not None:
190
+ projected_items.append(projected_item)
191
+ # The canonical position is the bundle's original immediate
192
+ # successor. Anchoring on the original index keeps a projected-out
193
+ # item between the bundle and a later developer message from
194
+ # collapsing into the same slot.
195
+ if (
196
+ index == 1
197
+ and prefix_begins_with_lite_tool_bundle
198
+ and len(projected_items) == 2
199
+ and _is_inline_developer_message(item)
200
+ ):
201
+ canonical_lite_developer_index = 1
202
+ if index + 1 == stored_count:
203
+ projected_stored_count = len(projected_items)
204
+
205
+ return AccountNeutralReplayProjection(
206
+ input_items=projected_items,
207
+ stored_prefix_count=projected_stored_count,
208
+ canonical_lite_developer_index=canonical_lite_developer_index,
209
+ )
210
+
211
+
212
+ def _is_canonical_lite_tool_bundle(item: JsonValue) -> bool:
213
+ return (
214
+ isinstance(item, dict)
215
+ and item.get("type") == "additional_tools"
216
+ and item.get("role") == "developer"
217
+ and _input_item_has_only_known_fields(item, "additional_tools")
218
+ and _tools_are_account_neutral(item.get("tools"))
219
+ )
220
+
221
+
222
+ def _is_inline_developer_message(item: JsonValue) -> bool:
223
+ return isinstance(item, dict) and item.get("role") == "developer" and item.get("type") != "additional_tools"
224
+
225
+
226
+ def _project_account_neutral_replay_item(
227
+ item: JsonValue,
228
+ *,
229
+ preserve_developer_message_ids: bool,
230
+ ) -> JsonValue | None:
231
+ if not isinstance(item, dict):
232
+ return item
233
+
234
+ item_type = item.get("type")
235
+ if preserve_developer_message_ids and item.get("role") == "developer" and item_type != "additional_tools":
236
+ # Classification must see every inline developer-role item before
237
+ # projection can omit response-owned bookkeeping types. The
238
+ # additional_tools bundle is a distinct Responses-Lite input item,
239
+ # not an inline developer message.
240
+ return item
241
+ if item_type is not None and not isinstance(item_type, str):
242
+ return item
243
+ if item_type == "reasoning" or (
244
+ item_type in _ACCOUNT_NEUTRAL_REPLAY_OMITTED_ITEM_TYPES and item.get("status") == "completed"
245
+ ):
246
+ return None
247
+
248
+ if "id" not in item:
249
+ return item
250
+ projected_item = dict(item)
251
+ projected_item.pop("id")
252
+ return projected_item
253
+
254
+
255
+ def responses_input_items_are_self_contained_fresh_replay(input_items: list[JsonValue]) -> bool:
256
+ unsettled_call_ids_by_type: dict[str, set[str]] = {item_type: set() for item_type in _TOOL_CALL_TYPES}
257
+ seen_call_ids: set[str] = set()
258
+ settled_call_ids: set[str] = set()
259
+ for item in input_items:
260
+ if not isinstance(item, dict):
261
+ return False
262
+ if "type" in item and not _is_nonblank_string(item.get("type")):
263
+ return False
264
+ if item.get("id") not in (None, ""):
265
+ return False
266
+ if not _internal_chat_message_metadata_is_account_neutral(item.get(_INTERNAL_CHAT_MESSAGE_METADATA_FIELD)):
267
+ return False
268
+ item_type_value = item.get("type")
269
+ item_type = item_type_value if isinstance(item_type_value, str) else None
270
+ if not _input_item_has_only_known_fields(item, item_type):
271
+ return False
272
+ call_id_value = item.get("call_id")
273
+ call_id = call_id_value if isinstance(call_id_value, str) and call_id_value else None
274
+ if item_type in _TOOL_CALL_TYPES:
275
+ if (
276
+ call_id is None
277
+ or call_id in seen_call_ids
278
+ or not _caller_is_self_contained(item)
279
+ or not _tool_call_is_self_contained(item_type, item)
280
+ ):
281
+ return False
282
+ seen_call_ids.add(call_id)
283
+ unsettled_call_ids_by_type[item_type].add(call_id)
284
+ continue
285
+ call_item_type = _TOOL_CALL_TYPE_BY_OUTPUT_TYPE.get(item_type or "")
286
+ if call_item_type is not None:
287
+ if (
288
+ call_id is None
289
+ or call_id not in unsettled_call_ids_by_type[call_item_type]
290
+ or call_id in settled_call_ids
291
+ or not _caller_is_self_contained(item)
292
+ or not _tool_output_is_self_contained(item_type or "", item)
293
+ ):
294
+ return False
295
+ unsettled_call_ids_by_type[call_item_type].remove(call_id)
296
+ settled_call_ids.add(call_id)
297
+ return all(not call_ids for call_ids in unsettled_call_ids_by_type.values())
298
+
299
+
300
+ def _internal_chat_message_metadata_is_account_neutral(value: JsonValue | None) -> bool:
301
+ if value is None:
302
+ return True
303
+ return (
304
+ isinstance(value, dict)
305
+ and set(value) == _ACCOUNT_NEUTRAL_INTERNAL_CHAT_MESSAGE_METADATA_FIELDS
306
+ and _is_nonblank_string(value.get("turn_id"))
307
+ )
308
+
309
+
310
+ def responses_input_suffix_retains_prior_output(
311
+ input_items: list[JsonValue],
312
+ *,
313
+ stored_count: int,
314
+ canonical_lite_developer_index: int | None = None,
315
+ ) -> bool:
316
+ """Prove that a stored input prefix is followed by prior output and new input."""
317
+
318
+ if stored_count <= 0 or len(input_items) <= stored_count:
319
+ return False
320
+ prefix_state = _direct_tool_call_prefix_state(
321
+ input_items[:stored_count],
322
+ canonical_lite_developer_index=canonical_lite_developer_index,
323
+ )
324
+ if prefix_state is None:
325
+ return False
326
+ pending_suffix_calls, seen_suffix_call_ids = prefix_state
327
+ retained_output_seen = False
328
+ retained_output_is_final_answer = False
329
+ fresh_followup_seen = False
330
+ fresh_followup_count = 0
331
+ fresh_followup_is_user_message = False
332
+ fresh_developer_followup_seen = False
333
+ for item in input_items[stored_count:]:
334
+ if fresh_developer_followup_seen or not isinstance(item, dict):
335
+ return False
336
+ item_type_value = item.get("type")
337
+ if "type" in item and not _is_nonblank_string(item_type_value):
338
+ return False
339
+ item_type = item_type_value if isinstance(item_type_value, str) else None
340
+ if item_type in _TOOL_CALL_TYPES:
341
+ if item.get("status") not in (None, "completed"):
342
+ return False
343
+ call_id = item.get("call_id")
344
+ if not isinstance(call_id, str) or not call_id or call_id in seen_suffix_call_ids:
345
+ return False
346
+ seen_suffix_call_ids.add(call_id)
347
+ pending_suffix_calls.append((item_type, call_id))
348
+ # Without a persisted output manifest, a call/output pair cannot
349
+ # prove that an omitted parallel call was not part of the response.
350
+ # Require a later completed assistant message as the turn boundary.
351
+ retained_output_seen = False
352
+ retained_output_is_final_answer = False
353
+ fresh_followup_seen = False
354
+ fresh_followup_count = 0
355
+ fresh_followup_is_user_message = False
356
+ continue
357
+ call_type = _TOOL_CALL_TYPE_BY_OUTPUT_TYPE.get(item_type or "")
358
+ if call_type is not None:
359
+ if item.get("status") not in (None, "completed", "failed"):
360
+ return False
361
+ call_id = item.get("call_id")
362
+ if not isinstance(call_id, str) or not pending_suffix_calls:
363
+ return False
364
+ if pending_suffix_calls[0] != (call_type, call_id):
365
+ return False
366
+ pending_suffix_calls.popleft()
367
+ continue
368
+ if item_type in (None, "message") and item.get("role") == "assistant":
369
+ if pending_suffix_calls or not _is_retained_response_message(item):
370
+ return False
371
+ retained_output_seen = True
372
+ retained_output_is_final_answer = item.get("phase") == "final_answer"
373
+ fresh_followup_seen = False
374
+ fresh_followup_count = 0
375
+ fresh_followup_is_user_message = False
376
+ continue
377
+ if _is_fresh_followup_input(item):
378
+ if not retained_output_seen or pending_suffix_calls:
379
+ return False
380
+ fresh_followup_seen = True
381
+ fresh_followup_count += 1
382
+ fresh_followup_is_user_message = item_type in (None, "message") and item.get("role") == "user"
383
+ continue
384
+ if _fresh_developer_message_is_transparent(item):
385
+ if (
386
+ not fresh_followup_seen
387
+ or fresh_followup_count != 1
388
+ or not fresh_followup_is_user_message
389
+ or not retained_output_is_final_answer
390
+ or pending_suffix_calls
391
+ ):
392
+ return False
393
+ fresh_developer_followup_seen = True
394
+ continue
395
+ return False
396
+ return retained_output_seen and fresh_followup_seen and not pending_suffix_calls
397
+
398
+
399
+ def responses_input_suffix_matches_pending_tool_calls(
400
+ input_items: list[JsonValue],
401
+ *,
402
+ stored_count: int,
403
+ pending_tool_calls: Mapping[str, str],
404
+ canonical_lite_developer_index: int | None = None,
405
+ ) -> bool:
406
+ """Prove the suffix exactly settles the durable prior-response call manifest."""
407
+
408
+ if stored_count <= 0 or len(input_items) <= stored_count or not pending_tool_calls:
409
+ return False
410
+ prefix_state = _direct_tool_call_prefix_state(
411
+ input_items[:stored_count],
412
+ allow_historical_developer_interleave=True,
413
+ canonical_lite_developer_index=canonical_lite_developer_index,
414
+ )
415
+ if prefix_state is None or prefix_state[0] or prefix_state[1] & pending_tool_calls.keys():
416
+ return False
417
+ suffix = input_items[stored_count:]
418
+ if (
419
+ len(suffix) == 3
420
+ and isinstance(suffix[1], dict)
421
+ and _fresh_developer_message_is_transparent(suffix[1])
422
+ and _fresh_developer_interleave_is_bounded(suffix, index=1)
423
+ ):
424
+ suffix = [suffix[0], suffix[2]]
425
+ if not all(
426
+ isinstance(item, dict)
427
+ and isinstance(item.get("type"), str)
428
+ and item.get("type") in (_TOOL_CALL_TYPES | _TOOL_CALL_TYPE_BY_OUTPUT_TYPE.keys())
429
+ for item in suffix
430
+ ):
431
+ return False
432
+ if not responses_input_items_are_self_contained_fresh_replay(suffix):
433
+ return False
434
+ suffix_calls: dict[str, str] = {}
435
+ suffix_outputs: dict[str, str] = {}
436
+ for item in cast(list[dict[str, JsonValue]], suffix):
437
+ item_type = cast(str, item["type"])
438
+ call_id = cast(str, item["call_id"])
439
+ if item_type in _TOOL_CALL_TYPES:
440
+ suffix_calls[call_id] = item_type
441
+ else:
442
+ suffix_outputs[call_id] = _TOOL_CALL_TYPE_BY_OUTPUT_TYPE[item_type]
443
+ expected = dict(pending_tool_calls)
444
+ return suffix_calls == expected and suffix_outputs == expected
445
+
446
+
447
+ def _direct_tool_call_prefix_state(
448
+ input_items: list[JsonValue],
449
+ *,
450
+ allow_historical_developer_interleave: bool = False,
451
+ canonical_lite_developer_index: int | None = None,
452
+ ) -> tuple[deque[tuple[str, str]], set[str]] | None:
453
+ pending_calls: deque[tuple[str, str]] = deque()
454
+ seen_call_ids: set[str] = set()
455
+ # A pending window opens when ``pending_calls`` becomes non-empty and closes when it
456
+ # drains. Historical interleaving is proven only for a window that never held more than
457
+ # one outstanding call and never consumed more than one developer message.
458
+ pending_window_developer_seen = False
459
+ pending_window_held_parallel_calls = False
460
+ for index, item in enumerate(input_items):
461
+ if not isinstance(item, dict):
462
+ return None
463
+ item_type_value = item.get("type")
464
+ if item_type_value is not None and not isinstance(item_type_value, str):
465
+ return None
466
+ item_type = item_type_value if isinstance(item_type_value, str) else None
467
+ if item.get("role") == "developer" and item_type != "additional_tools":
468
+ developer_message_is_transparent = _historical_pending_developer_message_is_transparent(
469
+ item,
470
+ item_type=item_type,
471
+ )
472
+ # Canonical position is proven by the projection against the
473
+ # original input, not by adjacency inside the projected prefix.
474
+ occupies_canonical_lite_position = (
475
+ canonical_lite_developer_index is not None and index == canonical_lite_developer_index
476
+ )
477
+ if developer_message_is_transparent and occupies_canonical_lite_position:
478
+ continue
479
+ historical_interleave_is_bounded = (
480
+ allow_historical_developer_interleave
481
+ and len(pending_calls) == 1
482
+ and not pending_window_held_parallel_calls
483
+ and not pending_window_developer_seen
484
+ )
485
+ if developer_message_is_transparent and historical_interleave_is_bounded:
486
+ pending_window_developer_seen = True
487
+ continue
488
+ return None
489
+ if item_type in _TOOL_CALL_TYPES:
490
+ if item.get("status") not in (None, "completed"):
491
+ return None
492
+ call_id = item.get("call_id")
493
+ if not isinstance(call_id, str) or not call_id or call_id in seen_call_ids:
494
+ return None
495
+ seen_call_ids.add(call_id)
496
+ pending_calls.append((item_type, call_id))
497
+ if len(pending_calls) > 1:
498
+ # A window that already spent its interleaved developer message must not
499
+ # become parallel afterwards, otherwise a batch is admitted by ordering the
500
+ # developer message before the second call.
501
+ if pending_window_developer_seen:
502
+ return None
503
+ pending_window_held_parallel_calls = True
504
+ continue
505
+ call_type = _TOOL_CALL_TYPE_BY_OUTPUT_TYPE.get(item_type or "")
506
+ if call_type is not None:
507
+ if item.get("status") not in (None, "completed", "failed"):
508
+ return None
509
+ call_id = item.get("call_id")
510
+ if not isinstance(call_id, str) or not pending_calls:
511
+ return None
512
+ if pending_calls[0] != (call_type, call_id):
513
+ return None
514
+ pending_calls.popleft()
515
+ if not pending_calls:
516
+ pending_window_developer_seen = False
517
+ pending_window_held_parallel_calls = False
518
+ continue
519
+ if pending_calls and (
520
+ (item_type in (None, "message") and _is_one_of(item.get("role"), _ACCOUNT_NEUTRAL_MESSAGE_ROLES))
521
+ or item_type in {"input_file", "input_image", "input_text"}
522
+ ):
523
+ return None
524
+ # Call-like items outside the supported direct tool-call vocabulary
525
+ # (e.g. computer_call) are tolerated in the prefix but must still
526
+ # surface their IDs for collision checks: a pending call that reuses
527
+ # one of them cannot be proven fresh.
528
+ fallthrough_call_id = item.get("call_id")
529
+ if isinstance(fallthrough_call_id, str) and fallthrough_call_id:
530
+ seen_call_ids.add(fallthrough_call_id)
531
+ return pending_calls, seen_call_ids
532
+
533
+
534
+ def _historical_pending_developer_message_is_transparent(
535
+ item: Mapping[str, JsonValue],
536
+ *,
537
+ item_type: str | None,
538
+ ) -> bool:
539
+ return (
540
+ item_type in (None, "message")
541
+ and ("type" not in item or _is_nonblank_string(item.get("type")))
542
+ and item.get("role") == "developer"
543
+ and item.get("id") is None
544
+ and item.get("phase") is None
545
+ and item.get("status") in (None, "completed")
546
+ and _internal_chat_message_metadata_is_account_neutral(item.get(_INTERNAL_CHAT_MESSAGE_METADATA_FIELD))
547
+ and _input_item_has_only_known_fields(item, item_type)
548
+ and _message_has_valid_account_neutral_content(item)
549
+ )
550
+
551
+
552
+ def _fresh_developer_interleave_is_bounded(
553
+ input_items: list[JsonValue],
554
+ *,
555
+ index: int,
556
+ ) -> bool:
557
+ if len(input_items) != 3 or index != 1:
558
+ return False
559
+ preceding_item = input_items[0]
560
+ following_item = input_items[2]
561
+ if not isinstance(preceding_item, dict) or not isinstance(following_item, dict):
562
+ return False
563
+ call_type = preceding_item.get("type")
564
+ output_type = following_item.get("type")
565
+ call_id = preceding_item.get("call_id")
566
+ return (
567
+ call_type == "custom_tool_call"
568
+ and output_type == "custom_tool_call_output"
569
+ and _is_nonblank_string(call_id)
570
+ and following_item.get("call_id") == call_id
571
+ )
572
+
573
+
574
+ def _fresh_developer_message_is_transparent(
575
+ item: Mapping[str, JsonValue],
576
+ ) -> bool:
577
+ item_type_value = item.get("type")
578
+ item_type = item_type_value if isinstance(item_type_value, str) else None
579
+ metadata = item.get(_INTERNAL_CHAT_MESSAGE_METADATA_FIELD)
580
+ content = item.get("content")
581
+ return (
582
+ ("type" not in item or _is_nonblank_string(item.get("type")))
583
+ and item_type in (None, "message")
584
+ and item.get("role") == "developer"
585
+ and item.get("id") in (None, "")
586
+ and item.get("phase") is None
587
+ and item.get("status") in (None, "completed")
588
+ and isinstance(metadata, dict)
589
+ and _internal_chat_message_metadata_is_account_neutral(metadata)
590
+ and _input_item_has_only_known_fields(item, item_type)
591
+ and isinstance(content, list)
592
+ and len(content) == 1
593
+ and isinstance(content[0], dict)
594
+ and content[0].get("type") == "input_text"
595
+ and _input_content_part_is_self_contained(
596
+ cast(dict[str, JsonValue], content[0]),
597
+ allow_output=False,
598
+ )
599
+ )
600
+
601
+
602
+ def _is_retained_response_message(item: Mapping[str, JsonValue]) -> bool:
603
+ item_type = item.get("type")
604
+ if (
605
+ item_type not in (None, "message")
606
+ or item.get("role") != "assistant"
607
+ or item.get("status") not in (None, "completed")
608
+ ):
609
+ return False
610
+ return _message_has_valid_account_neutral_content(item)
611
+
612
+
613
+ def _is_fresh_followup_input(item: Mapping[str, JsonValue]) -> bool:
614
+ item_type = item.get("type")
615
+ if _is_one_of(item_type, {"input_file", "input_image", "input_text"}):
616
+ return _input_content_part_is_self_contained(item, allow_output=False)
617
+ return (
618
+ item_type in (None, "message")
619
+ and item.get("role") == "user"
620
+ and _message_has_valid_account_neutral_content(item)
621
+ )
622
+
623
+
624
+ def _tool_call_is_self_contained(item_type: str, item: Mapping[str, JsonValue]) -> bool:
625
+ if item.get("status") not in (None, "completed"):
626
+ return False
627
+ if item_type == "function_call":
628
+ return _is_nonblank_string(item.get("name")) and isinstance(item.get("arguments"), str)
629
+ if item_type == "custom_tool_call":
630
+ return _is_nonblank_string(item.get("name")) and isinstance(item.get("input"), str)
631
+ operation = item.get("operation")
632
+ patch = item.get("patch")
633
+ input_value = item.get("input")
634
+ if sum(field in item for field in ("operation", "patch", "input")) != 1:
635
+ return False
636
+ if "operation" in item:
637
+ return _apply_patch_operation_is_self_contained(operation)
638
+ if "patch" in item:
639
+ return _is_nonblank_string(patch)
640
+ return _is_nonblank_string(input_value)
641
+
642
+
643
+ def _caller_is_self_contained(item: Mapping[str, JsonValue]) -> bool:
644
+ caller = item.get("caller")
645
+ return caller is None or caller == {"type": "direct"}
646
+
647
+
648
+ def _input_item_has_only_known_fields(item: Mapping[str, JsonValue], item_type: str | None) -> bool:
649
+ if item_type in (None, "message"):
650
+ allowed_fields = _ACCOUNT_NEUTRAL_MESSAGE_FIELDS
651
+ elif item_type in _ACCOUNT_NEUTRAL_CONTENT_FIELDS:
652
+ allowed_fields = _ACCOUNT_NEUTRAL_CONTENT_FIELDS[item_type]
653
+ else:
654
+ allowed_fields = _ACCOUNT_NEUTRAL_INPUT_ITEM_FIELDS.get(item_type or "")
655
+ if allowed_fields is None:
656
+ return False
657
+ status = item.get("status")
658
+ return not any(key not in allowed_fields for key in item) and (
659
+ status is None or (isinstance(status, str) and status in _ACCOUNT_NEUTRAL_ITEM_STATUSES)
660
+ )
661
+
662
+
663
+ def _apply_patch_operation_is_self_contained(operation: JsonValue | None) -> bool:
664
+ if not isinstance(operation, dict):
665
+ return False
666
+ operation_type = operation.get("type")
667
+ allowed_fields = (
668
+ _ACCOUNT_NEUTRAL_APPLY_PATCH_OPERATION_FIELDS.get(operation_type) if isinstance(operation_type, str) else None
669
+ )
670
+ if allowed_fields is None or set(operation) != allowed_fields:
671
+ return False
672
+ return _is_nonblank_string(operation.get("path")) and (
673
+ operation_type == "delete_file" or isinstance(operation.get("diff"), str)
674
+ )
675
+
676
+
677
+ def _tool_output_is_self_contained(item_type: str, item: Mapping[str, JsonValue]) -> bool:
678
+ if item.get("status") not in (None, "completed", "failed"):
679
+ return False
680
+ output = item.get("output")
681
+ if isinstance(output, str):
682
+ return True
683
+ if item_type == "apply_patch_call_output":
684
+ return output is None and _is_one_of(item.get("status"), {"completed", "failed"})
685
+ return (
686
+ isinstance(output, list)
687
+ and bool(output)
688
+ and all(
689
+ isinstance(part, dict) and _input_content_part_is_self_contained(part, allow_output=False)
690
+ for part in output
691
+ )
692
+ )
693
+
694
+
695
+ def _is_nonblank_string(value: JsonValue | None) -> bool:
696
+ return isinstance(value, str) and bool(value.strip())
697
+
698
+
699
+ def _is_one_of(value: JsonValue | None, options: Container[str]) -> bool:
700
+ """Membership for a JSON value: lists/objects in a string slot are never members (and never raise)."""
701
+
702
+ return isinstance(value, str) and value in options
703
+
704
+
705
+ def responses_payload_is_account_neutral_fresh_replay(payload: Mapping[str, JsonValue]) -> bool:
706
+ """Return whether a full request can move accounts without stored upstream state."""
707
+
708
+ if payload.get("conversation") not in (None, ""):
709
+ return False
710
+ if payload.get("previous_response_id") not in (None, ""):
711
+ return False
712
+ if payload.get("prompt") not in (None, ""):
713
+ return False
714
+ if any(key not in _RESPONSES_PAYLOAD_FIELDS_WITH_DEDICATED_VALIDATION for key in payload):
715
+ return False
716
+ if not _reasoning_config_is_account_neutral(payload.get("reasoning")):
717
+ return False
718
+ if not _tool_choice_is_account_neutral(payload.get("tool_choice")):
719
+ return False
720
+ if not _text_controls_are_account_neutral(payload.get("text")):
721
+ return False
722
+ if not _client_metadata_is_account_neutral(payload.get("client_metadata")):
723
+ return False
724
+
725
+ input_value = payload.get("input")
726
+ if input_value is None or isinstance(input_value, str):
727
+ input_items: list[JsonValue] = []
728
+ elif isinstance(input_value, list):
729
+ input_items = cast(list[JsonValue], input_value)
730
+ else:
731
+ return False
732
+ # ``extract_input_file_ids`` tests item types by set membership; a list or
733
+ # object in the ``type`` slot is not a replayable item and must not raise.
734
+ if any(isinstance(item, dict) and "type" in item and not isinstance(item["type"], str) for item in input_items):
735
+ return False
736
+ if extract_input_file_ids(input_items):
737
+ return False
738
+ if any(
739
+ isinstance(item, dict)
740
+ and isinstance(item.get("type"), str)
741
+ and item.get("type") not in _ACCOUNT_NEUTRAL_INPUT_ITEM_TYPES
742
+ for item in input_items
743
+ ):
744
+ return False
745
+ if not responses_input_items_are_self_contained_fresh_replay(input_items):
746
+ return False
747
+ if not _input_items_have_valid_account_neutral_shape(input_items):
748
+ return False
749
+ if _contains_account_scoped_input_state(input_items):
750
+ return False
751
+
752
+ tools = payload.get("tools")
753
+ if tools is None:
754
+ return True
755
+ return _tools_are_account_neutral(tools)
756
+
757
+
758
+ def _reasoning_config_is_account_neutral(reasoning: JsonValue | None) -> bool:
759
+ if reasoning is None:
760
+ return True
761
+ return (
762
+ isinstance(reasoning, dict)
763
+ and all(key in _ACCOUNT_NEUTRAL_REASONING_CONFIG_FIELDS for key in reasoning)
764
+ and all(value is None or isinstance(value, str) for value in reasoning.values())
765
+ )
766
+
767
+
768
+ def _text_controls_are_account_neutral(text: JsonValue | None) -> bool:
769
+ if text is None:
770
+ return True
771
+ if not isinstance(text, dict) or not set(text) <= {"format", "verbosity"}:
772
+ return False
773
+ verbosity = text.get("verbosity")
774
+ if verbosity is not None and not _is_one_of(verbosity, {"low", "medium", "high"}):
775
+ return False
776
+ format_value = text.get("format")
777
+ if format_value is None:
778
+ return True
779
+ if not isinstance(format_value, dict):
780
+ return False
781
+ format_type = format_value.get("type")
782
+ if _is_one_of(format_type, {"text", "json_object"}):
783
+ return set(format_value) == {"type"}
784
+ if format_type != "json_schema" or not set(format_value) <= {
785
+ "description",
786
+ "name",
787
+ "schema",
788
+ "strict",
789
+ "type",
790
+ }:
791
+ return False
792
+ return (
793
+ _is_nonblank_string(format_value.get("name"))
794
+ and isinstance(format_value.get("schema"), dict)
795
+ and (format_value.get("strict") is None or isinstance(format_value.get("strict"), bool))
796
+ and (format_value.get("description") is None or isinstance(format_value.get("description"), str))
797
+ )
798
+
799
+
800
+ def _client_metadata_is_account_neutral(client_metadata: JsonValue | None) -> bool:
801
+ if client_metadata is None:
802
+ return True
803
+ if not isinstance(client_metadata, dict) or not set(client_metadata) <= _ACCOUNT_NEUTRAL_CLIENT_METADATA_FIELDS:
804
+ return False
805
+ return (
806
+ all(_is_nonblank_string(value) for value in client_metadata.values())
807
+ and client_metadata.get(
808
+ "ws_request_header_x_openai_internal_codex_responses_lite",
809
+ "true",
810
+ )
811
+ == "true"
812
+ )
813
+
814
+
815
+ def _tools_are_account_neutral(tools: JsonValue) -> bool:
816
+ return isinstance(tools, list) and all(
817
+ isinstance(tool, dict) and _tool_declaration_is_account_neutral(tool) for tool in tools
818
+ )
819
+
820
+
821
+ def _tool_declaration_is_account_neutral(tool: Mapping[str, JsonValue]) -> bool:
822
+ tool_type = tool.get("type")
823
+ if not isinstance(tool_type, str) or tool_type not in _ACCOUNT_NEUTRAL_TOOL_TYPES:
824
+ return False
825
+ if any(key not in _ACCOUNT_NEUTRAL_TOOL_DECLARATION_FIELDS[tool_type] for key in tool):
826
+ return False
827
+ if _contains_account_scoped_tool_state(tool):
828
+ return False
829
+ if tool_type in {"custom", "function"} and not _is_nonblank_string(tool.get("name")):
830
+ return False
831
+ if tool.get("description") is not None and not isinstance(tool.get("description"), str):
832
+ return False
833
+ if tool_type == "function":
834
+ return (tool.get("parameters") is None or isinstance(tool.get("parameters"), dict)) and (
835
+ tool.get("strict") is None or isinstance(tool.get("strict"), bool)
836
+ )
837
+ if tool_type == "custom":
838
+ return _custom_tool_format_is_account_neutral(tool.get("format"))
839
+ return _web_search_tool_options_are_account_neutral(tool_type, tool)
840
+
841
+
842
+ def _web_search_tool_options_are_account_neutral(
843
+ tool_type: str,
844
+ tool: Mapping[str, JsonValue],
845
+ ) -> bool:
846
+ filters = tool.get("filters")
847
+ if filters is not None:
848
+ if not isinstance(filters, dict) or not set(filters) <= _ACCOUNT_NEUTRAL_WEB_SEARCH_FILTER_FIELDS:
849
+ return False
850
+ allowed_domains = filters.get("allowed_domains")
851
+ if allowed_domains is not None and not (
852
+ isinstance(allowed_domains, list) and all(_is_nonblank_string(domain) for domain in allowed_domains)
853
+ ):
854
+ return False
855
+
856
+ search_context_size = tool.get("search_context_size")
857
+ if search_context_size is not None and not _is_one_of(
858
+ search_context_size, _ACCOUNT_NEUTRAL_WEB_SEARCH_CONTEXT_SIZES
859
+ ):
860
+ return False
861
+
862
+ user_location = tool.get("user_location")
863
+ if user_location is None:
864
+ return True
865
+ if not isinstance(user_location, dict) or not set(user_location) <= _ACCOUNT_NEUTRAL_WEB_SEARCH_LOCATION_FIELDS:
866
+ return False
867
+ location_type = user_location.get("type")
868
+ if location_type not in (None, "approximate") or (
869
+ tool_type == "web_search_preview" and location_type != "approximate"
870
+ ):
871
+ return False
872
+ return all(value is None or isinstance(value, str) for key, value in user_location.items() if key != "type")
873
+
874
+
875
+ def _tool_choice_is_account_neutral(tool_choice: JsonValue | None) -> bool:
876
+ if tool_choice is None:
877
+ return True
878
+ if isinstance(tool_choice, str):
879
+ return tool_choice in _ACCOUNT_NEUTRAL_TOOL_CHOICE_STRINGS
880
+ if not isinstance(tool_choice, dict) or _contains_account_scoped_tool_state(tool_choice):
881
+ return False
882
+ choice_type = tool_choice.get("type")
883
+ if _is_one_of(choice_type, {"custom", "function"}):
884
+ return set(tool_choice) <= {"name", "type"} and _is_nonblank_string(tool_choice.get("name"))
885
+ if _is_one_of(choice_type, {"web_search", "web_search_preview"}):
886
+ return set(tool_choice) == {"type"}
887
+ if choice_type != "allowed_tools" or set(tool_choice) > {"mode", "tools", "type"}:
888
+ return False
889
+ mode = tool_choice.get("mode")
890
+ allowed = tool_choice.get("tools")
891
+ return (
892
+ _is_one_of(mode, {"auto", "required"})
893
+ and isinstance(allowed, list)
894
+ and bool(allowed)
895
+ and all(isinstance(tool, dict) and _tool_choice_reference_is_account_neutral(tool) for tool in allowed)
896
+ )
897
+
898
+
899
+ def _tool_choice_reference_is_account_neutral(tool: Mapping[str, JsonValue]) -> bool:
900
+ tool_type = tool.get("type")
901
+ if _is_one_of(tool_type, {"custom", "function"}):
902
+ return set(tool) <= {"name", "type"} and _is_nonblank_string(tool.get("name"))
903
+ return _is_one_of(tool_type, {"web_search", "web_search_preview"}) and set(tool) == {"type"}
904
+
905
+
906
+ def _custom_tool_format_is_account_neutral(format_value: JsonValue | None) -> bool:
907
+ if format_value is None:
908
+ return True
909
+ if not isinstance(format_value, dict):
910
+ return False
911
+ format_type = format_value.get("type")
912
+ if format_type == "text":
913
+ return set(format_value) == {"type"}
914
+ return (
915
+ format_type == "grammar"
916
+ and set(format_value) == {"definition", "syntax", "type"}
917
+ and _is_one_of(format_value.get("syntax"), {"lark", "regex"})
918
+ and isinstance(format_value.get("definition"), str)
919
+ )
920
+
921
+
922
+ def _contains_account_scoped_tool_state(value: JsonValue) -> bool:
923
+ pending = [(value, True)]
924
+ while pending:
925
+ current, is_root = pending.pop()
926
+ if isinstance(current, dict):
927
+ if _mapping_has_account_scoped_reference(current):
928
+ return True
929
+ pending.extend(
930
+ (nested, False)
931
+ for key, nested in current.items()
932
+ if not (is_root and current.get("type") == "function" and key == "parameters")
933
+ )
934
+ elif isinstance(current, list):
935
+ pending.extend((nested, False) for nested in current)
936
+ return False
937
+
938
+
939
+ def _input_items_have_valid_account_neutral_shape(input_items: list[JsonValue]) -> bool:
940
+ for item in input_items:
941
+ if not isinstance(item, dict):
942
+ return False
943
+ item_type = item.get("type")
944
+ if _is_one_of(item_type, {"input_file", "input_image", "input_text"}):
945
+ if not _input_content_part_is_self_contained(item, allow_output=False):
946
+ return False
947
+ continue
948
+ if item_type == "additional_tools":
949
+ if item.get("role") != "developer" or not _tools_are_account_neutral(item.get("tools")):
950
+ return False
951
+ continue
952
+ if item_type not in (None, "message"):
953
+ continue
954
+ if not _message_has_valid_account_neutral_content(item):
955
+ return False
956
+ return True
957
+
958
+
959
+ def _message_has_valid_account_neutral_content(item: Mapping[str, JsonValue]) -> bool:
960
+ role = item.get("role")
961
+ if not _is_one_of(role, _ACCOUNT_NEUTRAL_MESSAGE_ROLES):
962
+ return False
963
+ phase = item.get("phase")
964
+ if phase is not None and not _is_one_of(phase, {"commentary", "final_answer"}):
965
+ return False
966
+ content = item.get("content")
967
+ if role != "assistant" and isinstance(content, str):
968
+ return _is_nonblank_string(content)
969
+ if not isinstance(content, list) or not content:
970
+ return False
971
+ if role == "assistant":
972
+ return all(
973
+ isinstance(part, dict)
974
+ and _is_one_of(part.get("type"), {"output_text", "refusal"})
975
+ and _input_content_part_is_self_contained(part, allow_output=True)
976
+ for part in content
977
+ )
978
+ return all(
979
+ isinstance(part, dict) and _input_content_part_is_self_contained(part, allow_output=False) for part in content
980
+ )
981
+
982
+
983
+ def _input_content_part_is_self_contained(
984
+ part: Mapping[str, JsonValue],
985
+ *,
986
+ allow_output: bool,
987
+ ) -> bool:
988
+ part_type = part.get("type")
989
+ if not _is_one_of(part_type, _ACCOUNT_NEUTRAL_MESSAGE_CONTENT_TYPES):
990
+ return False
991
+ if any(key not in _ACCOUNT_NEUTRAL_CONTENT_FIELDS[cast(str, part_type)] for key in part):
992
+ return False
993
+ if part_type in {"input_text", "text"} or (allow_output and part_type == "output_text"):
994
+ return _is_nonblank_string(part.get("text"))
995
+ if allow_output and part_type == "refusal":
996
+ return _is_nonblank_string(part.get("refusal"))
997
+ if part_type == "input_image":
998
+ return (part.get("detail") is None or isinstance(part.get("detail"), str)) and (
999
+ _url_is_account_neutral(part.get("image_url"), allow_data=True) or _is_nonblank_string(part.get("file_id"))
1000
+ )
1001
+ if part_type == "input_file":
1002
+ return (part.get("filename") is None or isinstance(part.get("filename"), str)) and (
1003
+ _is_nonblank_string(part.get("file_data"))
1004
+ or _is_nonblank_string(part.get("file_id"))
1005
+ or _url_is_account_neutral(part.get("file_url"), allow_data=False)
1006
+ )
1007
+ return False
1008
+
1009
+
1010
+ def _url_is_account_neutral(value: JsonValue | None, *, allow_data: bool) -> bool:
1011
+ if not isinstance(value, str) or not value.strip():
1012
+ return False
1013
+ try:
1014
+ scheme = urlsplit(value).scheme.lower()
1015
+ except ValueError:
1016
+ return False
1017
+ return scheme in ({"data", "http", "https"} if allow_data else {"http", "https"})
1018
+
1019
+
1020
+ def _contains_account_scoped_input_state(value: JsonValue) -> bool:
1021
+ pending = [value]
1022
+ while pending:
1023
+ current = pending.pop()
1024
+ if isinstance(current, dict):
1025
+ item_type = current.get("type")
1026
+ if isinstance(item_type, str) and item_type in _ACCOUNT_SCOPED_HOSTED_INPUT_TYPES:
1027
+ return True
1028
+ if isinstance(item_type, str) and item_type.startswith("mcp_"):
1029
+ return True
1030
+ if item_type == "additional_tools" and not _tools_are_account_neutral(current.get("tools")):
1031
+ return True
1032
+ if (
1033
+ isinstance(item_type, str)
1034
+ and (item_type.endswith("_call") or item_type.endswith("_call_output"))
1035
+ and item_type not in _TOOL_CALL_TYPES
1036
+ and item_type not in _TOOL_CALL_TYPE_BY_OUTPUT_TYPE
1037
+ ):
1038
+ return True
1039
+ if _mapping_has_account_scoped_reference(current):
1040
+ return True
1041
+ pending.extend(
1042
+ nested for key, nested in current.items() if not (item_type == "additional_tools" and key == "tools")
1043
+ )
1044
+ elif isinstance(current, list):
1045
+ pending.extend(current)
1046
+ return False
1047
+
1048
+
1049
+ def _mapping_has_account_scoped_reference(value: Mapping[str, JsonValue]) -> bool:
1050
+ for key in ("file_id", "container_id", "vector_store_id"):
1051
+ if value.get(key) not in (None, ""):
1052
+ return True
1053
+ if value.get("encrypted_content") not in (None, ""):
1054
+ return True
1055
+ for url_field, allow_data in (("image_url", True), ("file_url", False)):
1056
+ url_value = value.get(url_field)
1057
+ if url_value not in (None, "") and not _url_is_account_neutral(url_value, allow_data=allow_data):
1058
+ return True
1059
+ for key in ("file_ids", "vector_store_ids"):
1060
+ identifiers = value.get(key)
1061
+ if identifiers is not None and identifiers != []:
1062
+ return True
1063
+ return False