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.
- app/__init__.py +10 -0
- app/admin_cli.py +494 -0
- app/cli.py +339 -0
- app/codex_sessions_retag.py +487 -0
- app/core/__init__.py +0 -0
- app/core/audit/__init__.py +0 -0
- app/core/audit/service.py +216 -0
- app/core/audit/types.py +94 -0
- app/core/auth/__init__.py +236 -0
- app/core/auth/api_key_cache.py +65 -0
- app/core/auth/dashboard_access.py +429 -0
- app/core/auth/dashboard_mode.py +171 -0
- app/core/auth/dashboard_session_ttl.py +74 -0
- app/core/auth/dashboard_users_cache.py +120 -0
- app/core/auth/dependencies.py +710 -0
- app/core/auth/external_identity.py +78 -0
- app/core/auth/guardian.py +374 -0
- app/core/auth/models.py +49 -0
- app/core/auth/providers/__init__.py +129 -0
- app/core/auth/providers/oidc.py +838 -0
- app/core/auth/providers/registry.py +118 -0
- app/core/auth/refresh.py +394 -0
- app/core/auth/step_up.py +117 -0
- app/core/auth/totp.py +81 -0
- app/core/balancer/__init__.py +71 -0
- app/core/balancer/logic.py +1612 -0
- app/core/balancer/rendezvous_hash.py +21 -0
- app/core/balancer/types.py +21 -0
- app/core/bootstrap.py +172 -0
- app/core/cache/invalidation.py +426 -0
- app/core/clients/__init__.py +0 -0
- app/core/clients/codex.py +996 -0
- app/core/clients/codex_version.py +187 -0
- app/core/clients/codex_version_snapshot.py +2 -0
- app/core/clients/files.py +534 -0
- app/core/clients/headers.py +29 -0
- app/core/clients/http.py +511 -0
- app/core/clients/model_fetcher.py +276 -0
- app/core/clients/native_egress.py +1304 -0
- app/core/clients/oauth.py +477 -0
- app/core/clients/proxy.py +6089 -0
- app/core/clients/proxy_websocket.py +1411 -0
- app/core/clients/rate_limit_reset_credits.py +412 -0
- app/core/clients/stream_errors.py +12 -0
- app/core/clients/thread_cache_identity.py +347 -0
- app/core/clients/usage.py +467 -0
- app/core/clock.py +170 -0
- app/core/config/__init__.py +0 -0
- app/core/config/background_jobs.py +73 -0
- app/core/config/context_window_overrides.py +166 -0
- app/core/config/dashboard_overrides.py +145 -0
- app/core/config/inheritable.py +47 -0
- app/core/config/key_fingerprint.py +115 -0
- app/core/config/settings.py +872 -0
- app/core/config/settings_cache.py +100 -0
- app/core/config/spool_retention.py +217 -0
- app/core/config/tiers.py +174 -0
- app/core/conversation_archive.py +568 -0
- app/core/crypto.py +37 -0
- app/core/draining.py +11 -0
- app/core/errors.py +365 -0
- app/core/exceptions.py +155 -0
- app/core/handlers/__init__.py +3 -0
- app/core/handlers/exceptions.py +478 -0
- app/core/http_protocol.py +176 -0
- app/core/http_protocol_httptools.py +204 -0
- app/core/ingress_limits.py +17 -0
- app/core/metadata_store.py +18 -0
- app/core/metrics/__init__.py +0 -0
- app/core/metrics/middleware.py +74 -0
- app/core/metrics/prometheus.py +604 -0
- app/core/middleware/__init__.py +25 -0
- app/core/middleware/api_firewall.py +111 -0
- app/core/middleware/app_version.py +37 -0
- app/core/middleware/dashboard_auth_proxy.py +55 -0
- app/core/middleware/dashboard_csrf.py +177 -0
- app/core/middleware/dashboard_gzip.py +41 -0
- app/core/middleware/dashboard_overrides.py +59 -0
- app/core/middleware/firewall_cache.py +91 -0
- app/core/middleware/inflight.py +126 -0
- app/core/middleware/multipart_content_encoding.py +129 -0
- app/core/middleware/path_rewrite.py +148 -0
- app/core/middleware/request_body_limit.py +196 -0
- app/core/middleware/request_decompression.py +209 -0
- app/core/middleware/request_id.py +72 -0
- app/core/middleware/required_capability_http.py +116 -0
- app/core/middleware/trusted_proxy_headers.py +36 -0
- app/core/multipart.py +237 -0
- app/core/multipart_fields.py +91 -0
- app/core/openai/__init__.py +0 -0
- app/core/openai/chat_requests.py +493 -0
- app/core/openai/chat_responses.py +931 -0
- app/core/openai/contracts.py +46 -0
- app/core/openai/exceptions.py +24 -0
- app/core/openai/host_models.py +14 -0
- app/core/openai/images.py +413 -0
- app/core/openai/message_coercion.py +376 -0
- app/core/openai/model_refresh_scheduler.py +423 -0
- app/core/openai/model_registry.py +997 -0
- app/core/openai/model_registry_store.py +548 -0
- app/core/openai/models.py +172 -0
- app/core/openai/parsing.py +97 -0
- app/core/openai/requests.py +1980 -0
- app/core/openai/strict_schema.py +199 -0
- app/core/openai/tool_call_safety.py +99 -0
- app/core/openai/v1_requests.py +187 -0
- app/core/plan_types.py +90 -0
- app/core/prestop.py +202 -0
- app/core/rate_limiter/__init__.py +0 -0
- app/core/rate_limiter/db_rate_limiter.py +215 -0
- app/core/request_locality.py +473 -0
- app/core/resilience/__init__.py +0 -0
- app/core/resilience/backpressure.py +57 -0
- app/core/resilience/bulkhead.py +202 -0
- app/core/resilience/circuit_breaker.py +150 -0
- app/core/resilience/degradation.py +55 -0
- app/core/resilience/loop_lag_monitor.py +60 -0
- app/core/resilience/memory_monitor.py +137 -0
- app/core/resilience/network_recovery.py +256 -0
- app/core/resilience/overload.py +98 -0
- app/core/resilience/toggles.py +114 -0
- app/core/retention/__init__.py +0 -0
- app/core/retention/job.py +240 -0
- app/core/retention/scheduler.py +69 -0
- app/core/runtime_logging.py +601 -0
- app/core/scheduling/__init__.py +0 -0
- app/core/scheduling/leader_election.py +993 -0
- app/core/scheduling/leader_election_handle.py +25 -0
- app/core/server.py +140 -0
- app/core/shutdown.py +326 -0
- app/core/socket_peer.py +22 -0
- app/core/startup.py +41 -0
- app/core/timeout_invariants.py +347 -0
- app/core/tracing/__init__.py +0 -0
- app/core/tracing/otel.py +207 -0
- app/core/types.py +6 -0
- app/core/upstream_proxy/__init__.py +13 -0
- app/core/upstream_proxy/cache.py +130 -0
- app/core/upstream_proxy/resolver.py +189 -0
- app/core/upstream_proxy/types.py +104 -0
- app/core/usage/__init__.py +407 -0
- app/core/usage/depletion.py +109 -0
- app/core/usage/live_hub.py +41 -0
- app/core/usage/live_snapshots.py +174 -0
- app/core/usage/logs.py +212 -0
- app/core/usage/metadata_scheduler.py +118 -0
- app/core/usage/models.py +54 -0
- app/core/usage/pricing.py +576 -0
- app/core/usage/pricing_catalog.py +241 -0
- app/core/usage/pricing_snapshot.json +821 -0
- app/core/usage/quota.py +133 -0
- app/core/usage/refresh_policy.py +21 -0
- app/core/usage/refresh_scheduler.py +713 -0
- app/core/usage/reset_credits_refresh_scheduler.py +435 -0
- app/core/usage/types.py +168 -0
- app/core/utils/__init__.py +0 -0
- app/core/utils/json_guards.py +18 -0
- app/core/utils/locks.py +33 -0
- app/core/utils/masking.py +20 -0
- app/core/utils/proxy_env.py +104 -0
- app/core/utils/request_id.py +65 -0
- app/core/utils/retry.py +77 -0
- app/core/utils/shared_future.py +172 -0
- app/core/utils/sse.py +286 -0
- app/core/utils/time.py +24 -0
- app/db/__init__.py +0 -0
- app/db/account_identity_lock.py +56 -0
- app/db/alembic/__init__.py +1 -0
- app/db/alembic/env.py +70 -0
- app/db/alembic/revision_ids.py +43 -0
- app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
- app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
- app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
- app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
- app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
- app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
- app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
- app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
- app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
- app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
- app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
- app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
- app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
- app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
- app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
- app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
- app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
- app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
- app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
- app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
- app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
- app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
- app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
- app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
- app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
- app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
- app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
- app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
- app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
- app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
- app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
- app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
- app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
- app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
- app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
- app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
- app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
- app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
- app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
- app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
- app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
- app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
- app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
- app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
- app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
- app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
- app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
- app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
- app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
- app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
- app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
- app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
- app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
- app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
- app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
- app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
- app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
- app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
- app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
- app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
- app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
- app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
- app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
- app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
- app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
- app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
- app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
- app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
- app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
- app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
- app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
- app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
- app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
- app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
- app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
- app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
- app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
- app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
- app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
- app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
- app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
- app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
- app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
- app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
- app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
- app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
- app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
- app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
- app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
- app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
- app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
- app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
- app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
- app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
- app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
- app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
- app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
- app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
- app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
- app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
- app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
- app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
- app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
- app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
- app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
- app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
- app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
- app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
- app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
- app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
- app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
- app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
- app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
- app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
- app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
- app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
- app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
- app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
- app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
- app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
- app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
- app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
- app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
- app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
- app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
- app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
- app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
- app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
- app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
- app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
- app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
- app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
- app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
- app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
- app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
- app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
- app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
- app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
- app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
- app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
- app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
- app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
- app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
- app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
- app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
- app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
- app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
- app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
- app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
- app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
- app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
- app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
- app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
- app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
- app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
- app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
- app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
- app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
- app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
- app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
- app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
- app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
- app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
- app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
- app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
- app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
- app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
- app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
- app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
- app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
- app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
- app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
- app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
- app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
- app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
- app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
- app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
- app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
- app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
- app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
- app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
- app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
- app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
- app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
- app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
- app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
- app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
- app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
- app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
- app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
- app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
- app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
- app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
- app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
- app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
- app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
- app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
- app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
- app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
- app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
- app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
- app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
- app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
- app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
- app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
- app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
- app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
- app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
- app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
- app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
- app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
- app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
- app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
- app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
- app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
- app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
- app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
- app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
- app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
- app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
- app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
- app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
- app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
- app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
- app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
- app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
- app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
- app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
- app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
- app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
- app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
- app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
- app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
- app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
- app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
- app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
- app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
- app/db/alembic/versions/__init__.py +1 -0
- app/db/backup.py +64 -0
- app/db/migrate.py +1249 -0
- app/db/migration_lock.py +160 -0
- app/db/migration_url.py +18 -0
- app/db/models.py +3100 -0
- app/db/recover.py +244 -0
- app/db/session.py +1295 -0
- app/db/snapshot.py +46 -0
- app/db/sqlite_lock_retry.py +190 -0
- app/db/sqlite_utils.py +549 -0
- app/dependencies.py +462 -0
- app/main.py +1240 -0
- app/modules/__init__.py +0 -0
- app/modules/accounts/__init__.py +0 -0
- app/modules/accounts/api.py +426 -0
- app/modules/accounts/auth_manager.py +1439 -0
- app/modules/accounts/background_repository.py +160 -0
- app/modules/accounts/deletion.py +540 -0
- app/modules/accounts/mappers.py +654 -0
- app/modules/accounts/refresh_claims.py +352 -0
- app/modules/accounts/repository.py +1679 -0
- app/modules/accounts/schemas.py +308 -0
- app/modules/accounts/service.py +809 -0
- app/modules/accounts/usage_rollup.py +486 -0
- app/modules/accounts/usage_rollup_scheduler.py +75 -0
- app/modules/accounts/usage_time_rollup.py +1037 -0
- app/modules/accounts/usage_time_rollup_read.py +522 -0
- app/modules/api_keys/__init__.py +1 -0
- app/modules/api_keys/api.py +344 -0
- app/modules/api_keys/last_used_coalescer.py +216 -0
- app/modules/api_keys/limit_windows.py +41 -0
- app/modules/api_keys/repository.py +1200 -0
- app/modules/api_keys/reset_scheduler.py +85 -0
- app/modules/api_keys/schemas.py +135 -0
- app/modules/api_keys/service.py +2123 -0
- app/modules/audit/__init__.py +0 -0
- app/modules/audit/api.py +88 -0
- app/modules/audit/repository.py +74 -0
- app/modules/audit/schemas.py +33 -0
- app/modules/audit/service.py +71 -0
- app/modules/auth_providers/__init__.py +0 -0
- app/modules/auth_providers/api.py +127 -0
- app/modules/auth_providers/config.py +343 -0
- app/modules/auth_providers/repository.py +101 -0
- app/modules/auth_providers/schemas.py +88 -0
- app/modules/auth_providers/seed.py +105 -0
- app/modules/auth_providers/service.py +289 -0
- app/modules/automations/__init__.py +1 -0
- app/modules/automations/api.py +369 -0
- app/modules/automations/repository.py +1896 -0
- app/modules/automations/scheduler.py +107 -0
- app/modules/automations/schemas.py +158 -0
- app/modules/automations/service.py +2548 -0
- app/modules/cache_isolation_probe/__init__.py +0 -0
- app/modules/cache_isolation_probe/api.py +188 -0
- app/modules/cache_isolation_probe/prefix.py +144 -0
- app/modules/cache_isolation_probe/schemas.py +94 -0
- app/modules/cache_isolation_probe/sender.py +229 -0
- app/modules/cache_isolation_probe/service.py +525 -0
- app/modules/conversation_archive/__init__.py +1 -0
- app/modules/conversation_archive/api.py +101 -0
- app/modules/conversation_archive/schemas.py +28 -0
- app/modules/conversation_archive/service.py +165 -0
- app/modules/dashboard/__init__.py +1 -0
- app/modules/dashboard/api.py +106 -0
- app/modules/dashboard/builders.py +107 -0
- app/modules/dashboard/repository.py +304 -0
- app/modules/dashboard/schemas.py +148 -0
- app/modules/dashboard/service.py +534 -0
- app/modules/dashboard/timeframes.py +19 -0
- app/modules/dashboard/weekly_pace.py +617 -0
- app/modules/dashboard_auth/__init__.py +1 -0
- app/modules/dashboard_auth/api.py +1344 -0
- app/modules/dashboard_auth/oidc_api.py +753 -0
- app/modules/dashboard_auth/oidc_flows.py +330 -0
- app/modules/dashboard_auth/repository.py +452 -0
- app/modules/dashboard_auth/schemas.py +232 -0
- app/modules/dashboard_auth/service.py +1384 -0
- app/modules/dashboard_roles/__init__.py +0 -0
- app/modules/dashboard_roles/api.py +64 -0
- app/modules/dashboard_roles/repository.py +54 -0
- app/modules/dashboard_roles/schemas.py +29 -0
- app/modules/dashboard_roles/seed.py +80 -0
- app/modules/dashboard_roles/service.py +142 -0
- app/modules/dashboard_users/__init__.py +0 -0
- app/modules/dashboard_users/api.py +278 -0
- app/modules/dashboard_users/break_glass.py +169 -0
- app/modules/dashboard_users/credentials.py +18 -0
- app/modules/dashboard_users/identity_resolver.py +665 -0
- app/modules/dashboard_users/repository.py +725 -0
- app/modules/dashboard_users/schemas.py +117 -0
- app/modules/dashboard_users/service.py +1054 -0
- app/modules/firewall/__init__.py +1 -0
- app/modules/firewall/api.py +78 -0
- app/modules/firewall/repository.py +51 -0
- app/modules/firewall/schemas.py +28 -0
- app/modules/firewall/service.py +93 -0
- app/modules/fleet/__init__.py +1 -0
- app/modules/fleet/api.py +177 -0
- app/modules/fleet/mappers.py +56 -0
- app/modules/fleet/observability.py +372 -0
- app/modules/fleet/schemas.py +119 -0
- app/modules/health/__init__.py +0 -0
- app/modules/health/api.py +246 -0
- app/modules/health/schemas.py +27 -0
- app/modules/limit_warmup/__init__.py +1 -0
- app/modules/limit_warmup/repository.py +159 -0
- app/modules/limit_warmup/service.py +1001 -0
- app/modules/model_sources/__init__.py +1 -0
- app/modules/model_sources/api.py +99 -0
- app/modules/model_sources/catalog.py +307 -0
- app/modules/model_sources/forwarding.py +1435 -0
- app/modules/model_sources/projection.py +55 -0
- app/modules/model_sources/repository.py +201 -0
- app/modules/model_sources/schemas.py +79 -0
- app/modules/model_sources/selection.py +166 -0
- app/modules/model_sources/service.py +236 -0
- app/modules/oauth/__init__.py +0 -0
- app/modules/oauth/api.py +101 -0
- app/modules/oauth/repository.py +238 -0
- app/modules/oauth/schemas.py +49 -0
- app/modules/oauth/service.py +1196 -0
- app/modules/oauth/templates/oauth_success.html +122 -0
- app/modules/proxy/__init__.py +0 -0
- app/modules/proxy/_load_balancer/__init__.py +1 -0
- app/modules/proxy/_load_balancer/error_rate.py +116 -0
- app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
- app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
- app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
- app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
- app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
- app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
- app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
- app/modules/proxy/_load_balancer/tunables.py +97 -0
- app/modules/proxy/_load_balancer/types.py +96 -0
- app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
- app/modules/proxy/_service/__init__.py +6 -0
- app/modules/proxy/_service/api_key_usage.py +745 -0
- app/modules/proxy/_service/clock_budget.py +38 -0
- app/modules/proxy/_service/codex_control.py +611 -0
- app/modules/proxy/_service/compact.py +2211 -0
- app/modules/proxy/_service/file_ops.py +749 -0
- app/modules/proxy/_service/http_bridge/__init__.py +3 -0
- app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
- app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
- app/modules/proxy/_service/http_bridge/activity.py +178 -0
- app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
- app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
- app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
- app/modules/proxy/_service/http_bridge/protocol.py +167 -0
- app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
- app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
- app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
- app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
- app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
- app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
- app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
- app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
- app/modules/proxy/_service/observability.py +410 -0
- app/modules/proxy/_service/rate_limit.py +361 -0
- app/modules/proxy/_service/realtime_live.py +655 -0
- app/modules/proxy/_service/refresh.py +154 -0
- app/modules/proxy/_service/request_log.py +585 -0
- app/modules/proxy/_service/response_create.py +930 -0
- app/modules/proxy/_service/streaming/__init__.py +3 -0
- app/modules/proxy/_service/streaming/helpers.py +1317 -0
- app/modules/proxy/_service/streaming/mixin.py +1100 -0
- app/modules/proxy/_service/streaming/protocol.py +32 -0
- app/modules/proxy/_service/streaming/retry.py +3762 -0
- app/modules/proxy/_service/support.py +2122 -0
- app/modules/proxy/_service/transcribe.py +434 -0
- app/modules/proxy/_service/warmup.py +483 -0
- app/modules/proxy/_service/websocket/__init__.py +3 -0
- app/modules/proxy/_service/websocket/helpers.py +2360 -0
- app/modules/proxy/_service/websocket/mixin.py +7285 -0
- app/modules/proxy/_service/websocket/protocol.py +74 -0
- app/modules/proxy/account_cache.py +229 -0
- app/modules/proxy/account_eligibility.py +87 -0
- app/modules/proxy/additional_model_limits.py +40 -0
- app/modules/proxy/affinity.py +830 -0
- app/modules/proxy/affinity_observation.py +101 -0
- app/modules/proxy/api.py +10254 -0
- app/modules/proxy/api_key_usage.py +113 -0
- app/modules/proxy/cap_partitioning.py +280 -0
- app/modules/proxy/capability_lineage.py +65 -0
- app/modules/proxy/capability_lineage_repository.py +82 -0
- app/modules/proxy/capability_routing.py +253 -0
- app/modules/proxy/continuity.py +112 -0
- app/modules/proxy/downstream_delivery.py +277 -0
- app/modules/proxy/durable_bridge_coordinator.py +1217 -0
- app/modules/proxy/durable_bridge_repository.py +4516 -0
- app/modules/proxy/durable_bridge_runtime.py +9 -0
- app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
- app/modules/proxy/fair_share.py +131 -0
- app/modules/proxy/file_pin_repository.py +210 -0
- app/modules/proxy/helpers.py +439 -0
- app/modules/proxy/http_bridge_event_batcher.py +613 -0
- app/modules/proxy/http_bridge_forwarding.py +797 -0
- app/modules/proxy/http_continuation.py +76 -0
- app/modules/proxy/images_observability.py +62 -0
- app/modules/proxy/images_service.py +1054 -0
- app/modules/proxy/load_balancer.py +3016 -0
- app/modules/proxy/rate_limit_cache.py +46 -0
- app/modules/proxy/replay_safety.py +1063 -0
- app/modules/proxy/repo_bundle.py +31 -0
- app/modules/proxy/request_policy.py +1054 -0
- app/modules/proxy/ring_membership.py +251 -0
- app/modules/proxy/schemas.py +345 -0
- app/modules/proxy/selection_errors.py +41 -0
- app/modules/proxy/service.py +2600 -0
- app/modules/proxy/source_admission.py +125 -0
- app/modules/proxy/source_dispatch.py +999 -0
- app/modules/proxy/sticky_repository.py +831 -0
- app/modules/proxy/thread_anchors.py +491 -0
- app/modules/proxy/tool_call_dedupe.py +802 -0
- app/modules/proxy/types.py +54 -0
- app/modules/proxy/work_admission.py +121 -0
- app/modules/quota_planner/__init__.py +1 -0
- app/modules/quota_planner/api.py +276 -0
- app/modules/quota_planner/logic.py +743 -0
- app/modules/quota_planner/repository.py +722 -0
- app/modules/quota_planner/scheduler.py +212 -0
- app/modules/quota_planner/schemas.py +102 -0
- app/modules/quota_planner/warmup.py +693 -0
- app/modules/rate_limit_reset_credits/__init__.py +0 -0
- app/modules/rate_limit_reset_credits/api.py +601 -0
- app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
- app/modules/rate_limit_reset_credits/store.py +114 -0
- app/modules/reports/__init__.py +1 -0
- app/modules/reports/api.py +134 -0
- app/modules/reports/cache.py +74 -0
- app/modules/reports/filters.py +27 -0
- app/modules/reports/repository.py +525 -0
- app/modules/reports/rollup.py +150 -0
- app/modules/reports/rollup_read.py +148 -0
- app/modules/reports/schemas.py +123 -0
- app/modules/reports/service.py +287 -0
- app/modules/reports/thread_identity.py +256 -0
- app/modules/request_logs/__init__.py +0 -0
- app/modules/request_logs/api.py +224 -0
- app/modules/request_logs/cost_backfill.py +154 -0
- app/modules/request_logs/mappers.py +97 -0
- app/modules/request_logs/repository.py +1765 -0
- app/modules/request_logs/schemas.py +145 -0
- app/modules/request_logs/service.py +348 -0
- app/modules/role_mappings/__init__.py +0 -0
- app/modules/role_mappings/api.py +206 -0
- app/modules/role_mappings/matching.py +55 -0
- app/modules/role_mappings/repository.py +106 -0
- app/modules/role_mappings/schemas.py +67 -0
- app/modules/role_mappings/service.py +334 -0
- app/modules/runtime/__init__.py +1 -0
- app/modules/runtime/api.py +18 -0
- app/modules/runtime/schemas.py +14 -0
- app/modules/runtime/service.py +218 -0
- app/modules/scim/__init__.py +1 -0
- app/modules/scim/api.py +194 -0
- app/modules/scim/dependencies.py +112 -0
- app/modules/scim/errors.py +77 -0
- app/modules/scim/management_api.py +156 -0
- app/modules/scim/repository.py +212 -0
- app/modules/scim/schemas.py +145 -0
- app/modules/scim/service.py +786 -0
- app/modules/scim/tokens.py +43 -0
- app/modules/settings/__init__.py +1 -0
- app/modules/settings/api.py +1677 -0
- app/modules/settings/repository.py +606 -0
- app/modules/settings/schemas.py +496 -0
- app/modules/settings/service.py +769 -0
- app/modules/shared/__init__.py +0 -0
- app/modules/shared/schemas.py +22 -0
- app/modules/sticky_sessions/__init__.py +1 -0
- app/modules/sticky_sessions/api.py +118 -0
- app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
- app/modules/sticky_sessions/schemas.py +78 -0
- app/modules/sticky_sessions/service.py +196 -0
- app/modules/telemetry/__init__.py +5 -0
- app/modules/telemetry/api.py +115 -0
- app/modules/telemetry/clients.py +62 -0
- app/modules/telemetry/consent.py +115 -0
- app/modules/telemetry/scheduler.py +85 -0
- app/modules/telemetry/schemas.py +163 -0
- app/modules/telemetry/sender.py +221 -0
- app/modules/telemetry/snapshot.py +490 -0
- app/modules/usage/__init__.py +0 -0
- app/modules/usage/additional_quota_keys.py +379 -0
- app/modules/usage/api.py +45 -0
- app/modules/usage/background_repository.py +147 -0
- app/modules/usage/builders.py +559 -0
- app/modules/usage/depletion_service.py +277 -0
- app/modules/usage/live_ingest.py +356 -0
- app/modules/usage/mappers.py +29 -0
- app/modules/usage/plan_downgrade_observations.py +488 -0
- app/modules/usage/repository.py +1890 -0
- app/modules/usage/schemas.py +68 -0
- app/modules/usage/service.py +102 -0
- app/modules/usage/updater.py +1488 -0
- app/static/assets/Trans-Cj-J9mVA.js +1 -0
- app/static/assets/access-page-DHzZiXZi.js +1 -0
- app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
- app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
- app/static/assets/account-status-DIsU5sUS.js +1 -0
- app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
- app/static/assets/accounts-page-Bbai8ift.js +3 -0
- app/static/assets/api-Dwa7XxPe.js +1 -0
- app/static/assets/api-client-dugX1Pti.js +55 -0
- app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
- app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
- app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
- app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
- app/static/assets/apis-page-BvXz9c6i.js +2 -0
- app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
- app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
- app/static/assets/automations-page-DQgNAJOR.js +3 -0
- app/static/assets/badge-DqMb9es0.js +1 -0
- app/static/assets/blur-email-HEvKagG2.js +1 -0
- app/static/assets/button-BjVmmiGW.js +1 -0
- app/static/assets/checkbox-apsl9vzk.js +1 -0
- app/static/assets/chevron-left-1d_pHVa5.js +1 -0
- app/static/assets/circle-alert-BS0xd_o1.js +1 -0
- app/static/assets/clsx-DB0hHKMi.js +1 -0
- app/static/assets/colors-BBy8GEtr.js +1 -0
- app/static/assets/copy-button-D7nAvOKR.js +1 -0
- app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
- app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
- app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
- app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
- app/static/assets/donut-chart-Bxc69cra.js +1 -0
- app/static/assets/download-kQteIcsB.js +1 -0
- app/static/assets/ellipsis-B_5OgIre.js +1 -0
- app/static/assets/empty-state-JcA7tc8m.js +1 -0
- app/static/assets/es2015-bMH8YKZT.js +41 -0
- app/static/assets/es6-B264ni6W.js +69 -0
- app/static/assets/external-link-UVAOgMQ0.js +1 -0
- app/static/assets/i18n-BWqZ9Ua2.js +1 -0
- app/static/assets/inbox-B_RM2s1J.js +1 -0
- app/static/assets/index-DIpd264Z.js +23 -0
- app/static/assets/index-DMeLKRJx.css +2 -0
- app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
- app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
- app/static/assets/iso-Bpe5Ihbb.js +1 -0
- app/static/assets/label-CGaLmLNf.js +1 -0
- app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
- app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
- app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
- app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
- app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
- app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
- app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
- app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
- app/static/assets/payload-CIYnGZz9.js +1 -0
- app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
- app/static/assets/report-chart-card-BXDliwcc.js +1 -0
- app/static/assets/reports-page-CGCJeMHG.js +3 -0
- app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
- app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- app/static/assets/search-HfhqwERt.js +1 -0
- app/static/assets/select-Cl33bz-g.js +1 -0
- app/static/assets/settings-page-mZj01HYl.js +2 -0
- app/static/assets/shim-CZwyM4Q7.js +1 -0
- app/static/assets/skeleton-C_EUc4Rp.js +1 -0
- app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
- app/static/assets/status-badge-CHHA5X6c.js +1 -0
- app/static/assets/switch-CzxgokcM.js +1 -0
- app/static/assets/table-DXaNSQ0G.js +1 -0
- app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
- app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
- app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
- app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
- app/static/assets/trash-D3KyWr1h.js +1 -0
- app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
- app/static/assets/use-accounts-PWlVGw3z.js +1 -0
- app/static/assets/use-auth-CuDlW3iC.js +1 -0
- app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
- app/static/assets/use-model-sources-Bul24JBu.js +1 -0
- app/static/assets/use-models-DUCPUuZg.js +1 -0
- app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
- app/static/assets/useTranslation--56nHhlH.js +1 -0
- app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
- app/static/assets/users-uIC3TIlE.js +1 -0
- app/static/assets/vendor-query-_bHZuxEx.js +1 -0
- app/static/assets/vendor-react-BVL0YZPW.js +9 -0
- app/static/favicon.svg +10 -0
- app/static/fonts/GeistSans-Variable.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
- app/static/index.html +29 -0
- codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
- codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
- codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
- codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
- codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
- 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
|