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,997 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass, field, replace
|
|
8
|
+
from fnmatch import fnmatchcase
|
|
9
|
+
|
|
10
|
+
import anyio
|
|
11
|
+
|
|
12
|
+
from app.core.types import JsonValue
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
MODEL_SOURCE_KIND_SUBSCRIPTION = "subscription"
|
|
17
|
+
MODEL_SOURCE_KIND_OPENAI_COMPATIBLE = "openai_compatible"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ReasoningLevel:
|
|
22
|
+
effort: str
|
|
23
|
+
description: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class UpstreamModel:
|
|
28
|
+
slug: str
|
|
29
|
+
display_name: str
|
|
30
|
+
description: str
|
|
31
|
+
context_window: int
|
|
32
|
+
input_modalities: tuple[str, ...]
|
|
33
|
+
supported_reasoning_levels: tuple[ReasoningLevel, ...]
|
|
34
|
+
default_reasoning_level: str | None
|
|
35
|
+
supports_reasoning_summaries: bool
|
|
36
|
+
support_verbosity: bool
|
|
37
|
+
default_verbosity: str | None
|
|
38
|
+
prefer_websockets: bool
|
|
39
|
+
supports_parallel_tool_calls: bool
|
|
40
|
+
supported_in_api: bool
|
|
41
|
+
minimal_client_version: str | None
|
|
42
|
+
priority: int
|
|
43
|
+
available_in_plans: frozenset[str]
|
|
44
|
+
base_instructions: str = ""
|
|
45
|
+
source_kind: str = MODEL_SOURCE_KIND_SUBSCRIPTION
|
|
46
|
+
source_id: str | None = None
|
|
47
|
+
raw: dict[str, JsonValue] = field(default_factory=dict, hash=False, compare=False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ModelRegistrySnapshot:
|
|
52
|
+
models: dict[str, UpstreamModel]
|
|
53
|
+
model_plans: dict[str, frozenset[str]]
|
|
54
|
+
plan_models: dict[str, frozenset[str]]
|
|
55
|
+
model_service_tier_plans: dict[str, dict[str, frozenset[str]]]
|
|
56
|
+
model_service_tier_accounts: dict[str, dict[str, frozenset[str]]]
|
|
57
|
+
account_plans: dict[str, str]
|
|
58
|
+
fetched_at: float
|
|
59
|
+
model_accounts: dict[str, frozenset[str]] = field(default_factory=dict)
|
|
60
|
+
account_catalogs_authoritative: bool = False
|
|
61
|
+
bootstrap_floor_active: bool = False
|
|
62
|
+
suppressed_model_slugs: frozenset[str] = field(default_factory=frozenset)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(slots=True)
|
|
66
|
+
class ModelRegistryExport:
|
|
67
|
+
"""Complete registry state as exchanged with the persisted snapshot store.
|
|
68
|
+
|
|
69
|
+
``snapshot is None`` represents the cleared/bootstrap-floor state.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
snapshot: ModelRegistrySnapshot | None
|
|
73
|
+
metadata_models: dict[str, UpstreamModel] | None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_BOOTSTRAP_WEBSOCKET_PREFERRED_MODEL_PATTERNS = (
|
|
77
|
+
"gpt-5.6-*",
|
|
78
|
+
"gpt-5.5",
|
|
79
|
+
"gpt-5.5-*",
|
|
80
|
+
"gpt-5.4",
|
|
81
|
+
"gpt-5.4-*",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
_REASONING_LEVELS_STANDARD = (
|
|
85
|
+
ReasoningLevel(effort="low", description="Low reasoning effort"),
|
|
86
|
+
ReasoningLevel(effort="medium", description="Medium reasoning effort"),
|
|
87
|
+
ReasoningLevel(effort="high", description="High reasoning effort"),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
_REASONING_LEVELS_EXTENDED = (
|
|
91
|
+
ReasoningLevel(effort="low", description="Low reasoning effort"),
|
|
92
|
+
ReasoningLevel(effort="medium", description="Medium reasoning effort"),
|
|
93
|
+
ReasoningLevel(effort="high", description="High reasoning effort"),
|
|
94
|
+
ReasoningLevel(effort="xhigh", description="Extra high reasoning effort"),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
_REASONING_LEVELS_MAX = (
|
|
98
|
+
ReasoningLevel(effort="low", description="Fast responses with lighter reasoning"),
|
|
99
|
+
ReasoningLevel(effort="medium", description="Balances speed and reasoning depth for everyday tasks"),
|
|
100
|
+
ReasoningLevel(effort="high", description="Greater reasoning depth for complex problems"),
|
|
101
|
+
ReasoningLevel(effort="xhigh", description="Extra high reasoning depth for complex problems"),
|
|
102
|
+
ReasoningLevel(effort="max", description="Maximum reasoning depth for the hardest problems"),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
_REASONING_LEVELS_ULTRA = (
|
|
106
|
+
*_REASONING_LEVELS_MAX,
|
|
107
|
+
ReasoningLevel(effort="ultra", description="Maximum reasoning with automatic task delegation"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
_BOOTSTRAP_FAST_SERVICE_TIERS: list[JsonValue] = [
|
|
111
|
+
{
|
|
112
|
+
"id": "priority",
|
|
113
|
+
"name": "Fast",
|
|
114
|
+
"description": "1.5x speed, increased usage",
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
_BOOTSTRAP_AVAILABLE_IN_PLANS = frozenset(
|
|
119
|
+
{
|
|
120
|
+
"plus",
|
|
121
|
+
"pro",
|
|
122
|
+
"prolite",
|
|
123
|
+
"team",
|
|
124
|
+
"business",
|
|
125
|
+
"enterprise",
|
|
126
|
+
"edu",
|
|
127
|
+
"education",
|
|
128
|
+
"k12",
|
|
129
|
+
"go",
|
|
130
|
+
"hc",
|
|
131
|
+
"finserv",
|
|
132
|
+
"free",
|
|
133
|
+
"free_workspace",
|
|
134
|
+
"quorum",
|
|
135
|
+
"self_serve_business_usage_based",
|
|
136
|
+
"enterprise_cbp_usage_based",
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
_BOOTSTRAP_CORE_AVAILABLE_IN_PLANS = frozenset(
|
|
141
|
+
plan for plan in _BOOTSTRAP_AVAILABLE_IN_PLANS if plan not in {"free", "free_workspace", "k12"}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# GPT-5.6 ships to four additional plan tiers upstream
|
|
145
|
+
# (codex-rs/models-manager/models.json at rust-v0.145.0).
|
|
146
|
+
_BOOTSTRAP_GPT56_AVAILABLE_IN_PLANS = frozenset(
|
|
147
|
+
{
|
|
148
|
+
*_BOOTSTRAP_AVAILABLE_IN_PLANS,
|
|
149
|
+
"edu_plus",
|
|
150
|
+
"edu_pro",
|
|
151
|
+
"enterprise_cbp_automation",
|
|
152
|
+
"sci",
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
_GPT56_SOL_AVAILABILITY_NUX: dict[str, JsonValue] = {
|
|
157
|
+
"message": (
|
|
158
|
+
"Our most capable model yet. GPT-5.6 Sol can tackle complex code changes, "
|
|
159
|
+
"dig into research, produce polished documents, and take on your most "
|
|
160
|
+
"ambitious work. Sol is highly capable at lower reasoning efforts—try "
|
|
161
|
+
"starting lower, then turn it up for harder jobs."
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _bootstrap_model(
|
|
167
|
+
slug: str,
|
|
168
|
+
display_name: str,
|
|
169
|
+
*,
|
|
170
|
+
prefer_websockets: bool,
|
|
171
|
+
minimal_client_version: str | None,
|
|
172
|
+
description: str | None = None,
|
|
173
|
+
reasoning_levels: tuple[ReasoningLevel, ...] = _REASONING_LEVELS_EXTENDED,
|
|
174
|
+
context_window: int = 272_000,
|
|
175
|
+
input_modalities: tuple[str, ...] = ("text", "image"),
|
|
176
|
+
default_reasoning_level: str | None = "medium",
|
|
177
|
+
default_verbosity: str | None = "low",
|
|
178
|
+
supported_in_api: bool = True,
|
|
179
|
+
available_in_plans: frozenset[str] = _BOOTSTRAP_AVAILABLE_IN_PLANS,
|
|
180
|
+
visibility: str = "list",
|
|
181
|
+
shell_type: str = "shell_command",
|
|
182
|
+
priority: int = 0,
|
|
183
|
+
raw: dict[str, JsonValue] | None = None,
|
|
184
|
+
) -> UpstreamModel:
|
|
185
|
+
raw_fields: dict[str, JsonValue] = {
|
|
186
|
+
"shell_type": shell_type,
|
|
187
|
+
"visibility": visibility,
|
|
188
|
+
"availability_nux": None,
|
|
189
|
+
"max_context_window": context_window,
|
|
190
|
+
"truncation_policy": {"mode": "tokens", "limit": 10_000},
|
|
191
|
+
"experimental_supported_tools": [],
|
|
192
|
+
}
|
|
193
|
+
if raw:
|
|
194
|
+
raw_fields.update(raw)
|
|
195
|
+
return UpstreamModel(
|
|
196
|
+
slug=slug,
|
|
197
|
+
display_name=display_name,
|
|
198
|
+
description=description or display_name,
|
|
199
|
+
context_window=context_window,
|
|
200
|
+
input_modalities=input_modalities,
|
|
201
|
+
supported_reasoning_levels=reasoning_levels,
|
|
202
|
+
default_reasoning_level=default_reasoning_level,
|
|
203
|
+
supports_reasoning_summaries=True,
|
|
204
|
+
support_verbosity=True,
|
|
205
|
+
default_verbosity=default_verbosity,
|
|
206
|
+
prefer_websockets=prefer_websockets,
|
|
207
|
+
supports_parallel_tool_calls=True,
|
|
208
|
+
supported_in_api=supported_in_api,
|
|
209
|
+
minimal_client_version=minimal_client_version,
|
|
210
|
+
priority=priority,
|
|
211
|
+
available_in_plans=available_in_plans,
|
|
212
|
+
raw=raw_fields,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _gpt56_raw(
|
|
217
|
+
*,
|
|
218
|
+
multi_agent_version: str,
|
|
219
|
+
availability_nux: dict[str, JsonValue] | None = None,
|
|
220
|
+
) -> dict[str, JsonValue]:
|
|
221
|
+
"""Raw catalog fields for the GPT-5.6 family, mirroring the upstream
|
|
222
|
+
bundled catalog (codex-rs/models-manager/models.json at rust-v0.145.0)
|
|
223
|
+
field-for-field, with one tracked exception: ``max_context_window``, which
|
|
224
|
+
upstream later raised from 272,000 to 872,000 (see the field comment
|
|
225
|
+
below). The ~16.5 KB ``base_instructions`` string and the
|
|
226
|
+
personality-templated ``model_messages`` object are deliberately not
|
|
227
|
+
bundled; the live upstream registry supplies them on the first refresh.
|
|
228
|
+
"""
|
|
229
|
+
return {
|
|
230
|
+
"apply_patch_tool_type": "freeform",
|
|
231
|
+
"web_search_tool_type": "text_and_image",
|
|
232
|
+
"supports_image_detail_original": True,
|
|
233
|
+
"truncation_policy": {"mode": "tokens", "limit": 10_000},
|
|
234
|
+
# ``tool_mode`` / ``multi_agent_version`` drive Codex client tool
|
|
235
|
+
# assembly and the ``ultra`` proactive multi-agent mode; omitting them
|
|
236
|
+
# would make a bootstrap-served catalog change client behavior.
|
|
237
|
+
"tool_mode": "code_mode_only",
|
|
238
|
+
"multi_agent_version": multi_agent_version,
|
|
239
|
+
"use_responses_lite": True,
|
|
240
|
+
"include_skills_usage_instructions": False,
|
|
241
|
+
"auto_review_model_override": None,
|
|
242
|
+
# Upstream raised only the ceiling: ``max_context_window`` 272000 ->
|
|
243
|
+
# 872000 with ``context_window`` unchanged at 272000. ``_bootstrap_model``
|
|
244
|
+
# synthesizes ``max_context_window == context_window``, so the family
|
|
245
|
+
# ceiling has to override it here, the same decoupling ``gpt-5.4`` uses.
|
|
246
|
+
# Pinned evidence: openai/codex commit
|
|
247
|
+
# 2eee483e49f88b868f67364134a658b3298e6c14 -- "Raise the GPT-5.6 maximum
|
|
248
|
+
# context window" (openai/codex#39102). Not yet in a ``rust-v*`` release
|
|
249
|
+
# tag; ``rust-v0.148.0-alpha.21`` still ships 272000. Re-pin to the tag
|
|
250
|
+
# once one carries it.
|
|
251
|
+
"max_context_window": 872_000,
|
|
252
|
+
"auto_compact_token_limit": None,
|
|
253
|
+
"comp_hash": "3000",
|
|
254
|
+
"reasoning_summary_format": "experimental",
|
|
255
|
+
"default_reasoning_summary": "none",
|
|
256
|
+
"availability_nux": availability_nux,
|
|
257
|
+
"upgrade": None,
|
|
258
|
+
# Required (no serde default) by the Rust ``ModelInfo`` deserializer;
|
|
259
|
+
# a Codex client rejects catalog entries that omit it.
|
|
260
|
+
"experimental_supported_tools": [],
|
|
261
|
+
"supports_search_tool": True,
|
|
262
|
+
"default_service_tier": None,
|
|
263
|
+
"service_tiers": _BOOTSTRAP_FAST_SERVICE_TIERS,
|
|
264
|
+
"additional_speed_tiers": ["fast"],
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Static bundled fallback models used before the first upstream registry refresh.
|
|
269
|
+
# This mirrors Codex's model-manager pattern: ship a conservative catalog so
|
|
270
|
+
# startup/offline paths have usable metadata, then treat the live upstream
|
|
271
|
+
# registry as authoritative once a refresh succeeds. Keep compatibility fields
|
|
272
|
+
# explicit rather than inherited from helper defaults; every slug must be a
|
|
273
|
+
# real upstream slug (never invented), and live upstream data always takes
|
|
274
|
+
# precedence once available. ``gpt-5.3-codex`` / ``gpt-5.3-codex-spark`` were
|
|
275
|
+
# dropped from upstream's bundled catalog at rust-v0.144.x but are retained
|
|
276
|
+
# here for older pinned clients; the upstream backend still serves them.
|
|
277
|
+
_BOOTSTRAP_STATIC_MODELS: tuple[UpstreamModel, ...] = (
|
|
278
|
+
_bootstrap_model(
|
|
279
|
+
"gpt-5.6-sol",
|
|
280
|
+
"GPT-5.6-Sol",
|
|
281
|
+
description="Latest frontier agentic coding model.",
|
|
282
|
+
prefer_websockets=True,
|
|
283
|
+
minimal_client_version="0.144.0",
|
|
284
|
+
reasoning_levels=_REASONING_LEVELS_ULTRA,
|
|
285
|
+
context_window=272_000,
|
|
286
|
+
default_reasoning_level="low",
|
|
287
|
+
priority=1,
|
|
288
|
+
available_in_plans=_BOOTSTRAP_GPT56_AVAILABLE_IN_PLANS,
|
|
289
|
+
raw=_gpt56_raw(multi_agent_version="v2", availability_nux=_GPT56_SOL_AVAILABILITY_NUX),
|
|
290
|
+
),
|
|
291
|
+
_bootstrap_model(
|
|
292
|
+
"gpt-5.6-terra",
|
|
293
|
+
"GPT-5.6-Terra",
|
|
294
|
+
description="Balanced agentic coding model for everyday work.",
|
|
295
|
+
prefer_websockets=True,
|
|
296
|
+
minimal_client_version="0.144.0",
|
|
297
|
+
reasoning_levels=_REASONING_LEVELS_ULTRA,
|
|
298
|
+
context_window=272_000,
|
|
299
|
+
default_reasoning_level="medium",
|
|
300
|
+
priority=2,
|
|
301
|
+
available_in_plans=_BOOTSTRAP_GPT56_AVAILABLE_IN_PLANS,
|
|
302
|
+
raw=_gpt56_raw(multi_agent_version="v2"),
|
|
303
|
+
),
|
|
304
|
+
_bootstrap_model(
|
|
305
|
+
"gpt-5.6-luna",
|
|
306
|
+
"GPT-5.6-Luna",
|
|
307
|
+
description="Fast and affordable agentic coding model.",
|
|
308
|
+
prefer_websockets=True,
|
|
309
|
+
minimal_client_version="0.144.0",
|
|
310
|
+
reasoning_levels=_REASONING_LEVELS_MAX,
|
|
311
|
+
context_window=272_000,
|
|
312
|
+
default_reasoning_level="medium",
|
|
313
|
+
priority=3,
|
|
314
|
+
available_in_plans=_BOOTSTRAP_GPT56_AVAILABLE_IN_PLANS,
|
|
315
|
+
raw=_gpt56_raw(multi_agent_version="v1"),
|
|
316
|
+
),
|
|
317
|
+
_bootstrap_model(
|
|
318
|
+
"gpt-5.5",
|
|
319
|
+
"GPT-5.5",
|
|
320
|
+
prefer_websockets=True,
|
|
321
|
+
minimal_client_version="0.124.0",
|
|
322
|
+
),
|
|
323
|
+
_bootstrap_model(
|
|
324
|
+
"gpt-5.4",
|
|
325
|
+
"GPT-5.4",
|
|
326
|
+
prefer_websockets=True,
|
|
327
|
+
minimal_client_version="0.98.0",
|
|
328
|
+
available_in_plans=_BOOTSTRAP_CORE_AVAILABLE_IN_PLANS,
|
|
329
|
+
raw={"max_context_window": 1_000_000},
|
|
330
|
+
),
|
|
331
|
+
_bootstrap_model(
|
|
332
|
+
"gpt-5.4-mini",
|
|
333
|
+
"GPT-5.4 Mini",
|
|
334
|
+
prefer_websockets=True,
|
|
335
|
+
default_verbosity="medium",
|
|
336
|
+
minimal_client_version="0.98.0",
|
|
337
|
+
),
|
|
338
|
+
_bootstrap_model(
|
|
339
|
+
"gpt-5.3-codex",
|
|
340
|
+
"GPT-5.3 Codex",
|
|
341
|
+
prefer_websockets=True,
|
|
342
|
+
minimal_client_version="0.98.0",
|
|
343
|
+
available_in_plans=_BOOTSTRAP_CORE_AVAILABLE_IN_PLANS,
|
|
344
|
+
),
|
|
345
|
+
_bootstrap_model(
|
|
346
|
+
"gpt-5.3-codex-spark",
|
|
347
|
+
"GPT-5.3 Codex Spark",
|
|
348
|
+
prefer_websockets=True,
|
|
349
|
+
context_window=128_000,
|
|
350
|
+
input_modalities=("text",),
|
|
351
|
+
default_reasoning_level="high",
|
|
352
|
+
minimal_client_version="0.100.0",
|
|
353
|
+
),
|
|
354
|
+
_bootstrap_model(
|
|
355
|
+
"gpt-5.2",
|
|
356
|
+
"GPT-5.2",
|
|
357
|
+
prefer_websockets=True,
|
|
358
|
+
minimal_client_version="0.0.1",
|
|
359
|
+
raw={"truncation_policy": {"mode": "bytes", "limit": 10_000}},
|
|
360
|
+
),
|
|
361
|
+
_bootstrap_model(
|
|
362
|
+
"codex-auto-review",
|
|
363
|
+
"Codex Auto Review",
|
|
364
|
+
prefer_websockets=True,
|
|
365
|
+
minimal_client_version="0.98.0",
|
|
366
|
+
available_in_plans=_BOOTSTRAP_CORE_AVAILABLE_IN_PLANS,
|
|
367
|
+
visibility="hide",
|
|
368
|
+
raw={"max_context_window": 1_000_000},
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
# Speed/service-tier metadata must aggregate (union) when the same slug is
|
|
374
|
+
# fetched from multiple accounts/plans, rather than be overwritten
|
|
375
|
+
# last-writer-wins. Otherwise a single account without Fast entitlement returns
|
|
376
|
+
# an empty tier list and erases Fast from the shared catalog for every account
|
|
377
|
+
# (issue #1100).
|
|
378
|
+
_SERVICE_TIER_OBJECT_KEY_FIELDS = ("slug", "name", "id", "tier")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _union_string_tiers(primary: JsonValue, secondary: JsonValue) -> list[JsonValue] | None:
|
|
382
|
+
merged: list[JsonValue] = []
|
|
383
|
+
seen: set[str] = set()
|
|
384
|
+
for source in (primary, secondary):
|
|
385
|
+
if isinstance(source, list):
|
|
386
|
+
for item in source:
|
|
387
|
+
if not isinstance(item, str):
|
|
388
|
+
continue
|
|
389
|
+
identity = canonical_service_tier_value(item)
|
|
390
|
+
if identity not in seen:
|
|
391
|
+
seen.add(identity)
|
|
392
|
+
merged.append(item)
|
|
393
|
+
return merged or None
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _service_tier_identity(entry: JsonValue) -> str:
|
|
397
|
+
if isinstance(entry, dict):
|
|
398
|
+
identities: list[str] = []
|
|
399
|
+
for key in _SERVICE_TIER_OBJECT_KEY_FIELDS:
|
|
400
|
+
value = entry.get(key)
|
|
401
|
+
if isinstance(value, str) and value:
|
|
402
|
+
canonical = canonical_service_tier_value(value)
|
|
403
|
+
if canonical:
|
|
404
|
+
identities.append(canonical)
|
|
405
|
+
if "priority" in identities:
|
|
406
|
+
return "tier:priority"
|
|
407
|
+
if identities:
|
|
408
|
+
return f"tier:{identities[0]}"
|
|
409
|
+
return json.dumps(entry, sort_keys=True, default=str)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def canonical_service_tier_value(value: str) -> str:
|
|
413
|
+
canonical = value.strip().lower()
|
|
414
|
+
if canonical in {"fast", "priority"}:
|
|
415
|
+
return "priority"
|
|
416
|
+
return canonical
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _union_object_tiers(primary: JsonValue, secondary: JsonValue) -> list[JsonValue] | None:
|
|
420
|
+
merged: list[JsonValue] = []
|
|
421
|
+
seen: set[str] = set()
|
|
422
|
+
for source in (primary, secondary):
|
|
423
|
+
if isinstance(source, list):
|
|
424
|
+
for item in source:
|
|
425
|
+
identity = _service_tier_identity(item)
|
|
426
|
+
if identity not in seen:
|
|
427
|
+
seen.add(identity)
|
|
428
|
+
merged.append(item)
|
|
429
|
+
return merged or None
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _merge_default_service_tier(primary: JsonValue, secondary: JsonValue) -> str | None:
|
|
433
|
+
if not isinstance(primary, str) or not primary:
|
|
434
|
+
return secondary if isinstance(secondary, str) and secondary else None
|
|
435
|
+
if not isinstance(secondary, str) or not secondary:
|
|
436
|
+
return primary
|
|
437
|
+
if primary == secondary:
|
|
438
|
+
return primary
|
|
439
|
+
|
|
440
|
+
plain_defaults = {"auto", "default"}
|
|
441
|
+
if primary in plain_defaults and secondary not in plain_defaults:
|
|
442
|
+
return secondary
|
|
443
|
+
return primary
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _model_service_tier_keys(model: UpstreamModel) -> frozenset[str]:
|
|
447
|
+
tiers: set[str] = set()
|
|
448
|
+
|
|
449
|
+
def add_tier(value: str) -> None:
|
|
450
|
+
tiers.add(value)
|
|
451
|
+
canonical = canonical_service_tier_value(value)
|
|
452
|
+
if canonical:
|
|
453
|
+
tiers.add(canonical)
|
|
454
|
+
|
|
455
|
+
additional_speed_tiers = model.raw.get("additional_speed_tiers")
|
|
456
|
+
if isinstance(additional_speed_tiers, list):
|
|
457
|
+
for item in additional_speed_tiers:
|
|
458
|
+
if isinstance(item, str) and item:
|
|
459
|
+
add_tier(item)
|
|
460
|
+
|
|
461
|
+
service_tiers = model.raw.get("service_tiers")
|
|
462
|
+
if isinstance(service_tiers, list):
|
|
463
|
+
for item in service_tiers:
|
|
464
|
+
if not isinstance(item, dict):
|
|
465
|
+
continue
|
|
466
|
+
for key in _SERVICE_TIER_OBJECT_KEY_FIELDS:
|
|
467
|
+
value = item.get(key)
|
|
468
|
+
if isinstance(value, str) and value:
|
|
469
|
+
add_tier(value)
|
|
470
|
+
|
|
471
|
+
default_service_tier = model.raw.get("default_service_tier")
|
|
472
|
+
if isinstance(default_service_tier, str) and default_service_tier:
|
|
473
|
+
add_tier(default_service_tier)
|
|
474
|
+
|
|
475
|
+
return frozenset(tiers)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _merge_service_tier_metadata(existing: UpstreamModel, incoming: UpstreamModel) -> UpstreamModel:
|
|
479
|
+
"""Combine two same-slug models so the speed/service-tier metadata is the
|
|
480
|
+
union of both. ``incoming`` stays the base (last-writer-wins for every other
|
|
481
|
+
field); only the tier fields aggregate, so an account without Fast cannot
|
|
482
|
+
strip Fast contributed by another account (issue #1100)."""
|
|
483
|
+
merged_raw = dict(incoming.raw)
|
|
484
|
+
|
|
485
|
+
speed_tiers = _union_string_tiers(
|
|
486
|
+
incoming.raw.get("additional_speed_tiers"),
|
|
487
|
+
existing.raw.get("additional_speed_tiers"),
|
|
488
|
+
)
|
|
489
|
+
if speed_tiers is not None:
|
|
490
|
+
merged_raw["additional_speed_tiers"] = speed_tiers
|
|
491
|
+
|
|
492
|
+
service_tiers = _union_object_tiers(
|
|
493
|
+
incoming.raw.get("service_tiers"),
|
|
494
|
+
existing.raw.get("service_tiers"),
|
|
495
|
+
)
|
|
496
|
+
if service_tiers is not None:
|
|
497
|
+
merged_raw["service_tiers"] = service_tiers
|
|
498
|
+
|
|
499
|
+
default_service_tier = _merge_default_service_tier(
|
|
500
|
+
incoming.raw.get("default_service_tier"),
|
|
501
|
+
existing.raw.get("default_service_tier"),
|
|
502
|
+
)
|
|
503
|
+
if default_service_tier is not None:
|
|
504
|
+
merged_raw["default_service_tier"] = default_service_tier
|
|
505
|
+
|
|
506
|
+
if merged_raw == incoming.raw:
|
|
507
|
+
return incoming
|
|
508
|
+
return replace(incoming, raw=merged_raw)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class ModelRegistry:
|
|
512
|
+
def __init__(self, *, ttl_seconds: float = 300.0) -> None:
|
|
513
|
+
if ttl_seconds <= 0:
|
|
514
|
+
raise ValueError("ttl_seconds must be positive")
|
|
515
|
+
self._ttl_seconds = ttl_seconds
|
|
516
|
+
self._snapshot: ModelRegistrySnapshot | None = None
|
|
517
|
+
self._bootstrap_models: dict[str, UpstreamModel] = {m.slug: m for m in _BOOTSTRAP_STATIC_MODELS}
|
|
518
|
+
self._metadata_models: dict[str, UpstreamModel] | None = None
|
|
519
|
+
self._applied_content_hash: str | None = None
|
|
520
|
+
self._lock = anyio.Lock()
|
|
521
|
+
|
|
522
|
+
@property
|
|
523
|
+
def applied_content_hash(self) -> str | None:
|
|
524
|
+
"""Content hash of the persisted snapshot this registry last applied or persisted."""
|
|
525
|
+
return self._applied_content_hash
|
|
526
|
+
|
|
527
|
+
def note_applied_content_hash(self, content_hash: str | None) -> None:
|
|
528
|
+
self._applied_content_hash = content_hash
|
|
529
|
+
|
|
530
|
+
async def export_state(self) -> ModelRegistryExport:
|
|
531
|
+
async with self._lock:
|
|
532
|
+
return ModelRegistryExport(snapshot=self._snapshot, metadata_models=self._metadata_models)
|
|
533
|
+
|
|
534
|
+
async def import_state(self, state: ModelRegistryExport, *, content_hash: str) -> None:
|
|
535
|
+
"""Replace the registry state with a decoded persisted snapshot."""
|
|
536
|
+
async with self._lock:
|
|
537
|
+
self._snapshot = state.snapshot
|
|
538
|
+
self._metadata_models = state.metadata_models
|
|
539
|
+
self._applied_content_hash = content_hash
|
|
540
|
+
|
|
541
|
+
def get_snapshot(self) -> ModelRegistrySnapshot | None:
|
|
542
|
+
return self._snapshot
|
|
543
|
+
|
|
544
|
+
def get_models_with_fallback(self) -> dict[str, UpstreamModel]:
|
|
545
|
+
snapshot = self._snapshot
|
|
546
|
+
if snapshot is not None:
|
|
547
|
+
if snapshot.bootstrap_floor_active:
|
|
548
|
+
models = dict(self._bootstrap_models)
|
|
549
|
+
for slug in snapshot.suppressed_model_slugs:
|
|
550
|
+
models.pop(slug, None)
|
|
551
|
+
models.update(snapshot.models)
|
|
552
|
+
return models
|
|
553
|
+
return snapshot.models
|
|
554
|
+
return self._bootstrap_models
|
|
555
|
+
|
|
556
|
+
def get_models_for_metadata(self) -> dict[str, UpstreamModel]:
|
|
557
|
+
if self._metadata_models is not None:
|
|
558
|
+
return self._metadata_models
|
|
559
|
+
return self._bootstrap_models
|
|
560
|
+
|
|
561
|
+
def plan_types_for_model(self, slug: str) -> frozenset[str] | None:
|
|
562
|
+
normalized_slug = slug.strip().lower()
|
|
563
|
+
bootstrap_model = self._bootstrap_models.get(slug) or self._bootstrap_models.get(normalized_slug)
|
|
564
|
+
if self._snapshot is None:
|
|
565
|
+
return bootstrap_model.available_in_plans if bootstrap_model is not None else None
|
|
566
|
+
if slug in self._snapshot.model_plans:
|
|
567
|
+
return self._snapshot.model_plans[slug]
|
|
568
|
+
if normalized_slug in self._snapshot.model_plans:
|
|
569
|
+
return self._snapshot.model_plans[normalized_slug]
|
|
570
|
+
if (
|
|
571
|
+
self._snapshot.bootstrap_floor_active
|
|
572
|
+
and bootstrap_model is not None
|
|
573
|
+
and normalized_slug not in self._snapshot.suppressed_model_slugs
|
|
574
|
+
):
|
|
575
|
+
return bootstrap_model.available_in_plans
|
|
576
|
+
snapshot_plans = frozenset()
|
|
577
|
+
return snapshot_plans
|
|
578
|
+
|
|
579
|
+
def is_suppressed_model(self, slug: str) -> bool:
|
|
580
|
+
"""Whether catalog evidence explicitly withdrew a previously known slug.
|
|
581
|
+
|
|
582
|
+
This differs from an operator-mapped slug simply absent from catalog
|
|
583
|
+
discovery: that slug keeps the existing unfiltered fallback, whereas a
|
|
584
|
+
suppressed model must not select any account.
|
|
585
|
+
"""
|
|
586
|
+
snapshot = self._snapshot
|
|
587
|
+
if snapshot is None:
|
|
588
|
+
return False
|
|
589
|
+
return slug.strip().lower() in snapshot.suppressed_model_slugs
|
|
590
|
+
|
|
591
|
+
def plan_types_for_model_service_tier(self, slug: str, service_tier: str | None) -> frozenset[str] | None:
|
|
592
|
+
if service_tier is None:
|
|
593
|
+
return self.plan_types_for_model(slug)
|
|
594
|
+
normalized_slug = slug.strip().lower()
|
|
595
|
+
normalized_service_tier = canonical_service_tier_value(service_tier)
|
|
596
|
+
if not normalized_slug or not normalized_service_tier:
|
|
597
|
+
return self.plan_types_for_model(slug)
|
|
598
|
+
|
|
599
|
+
if self._snapshot is None:
|
|
600
|
+
return self.plan_types_for_model(slug)
|
|
601
|
+
if not self._snapshot.account_catalogs_authoritative and self._snapshot.account_plans:
|
|
602
|
+
return self.plan_types_for_model(slug)
|
|
603
|
+
|
|
604
|
+
tier_plans = self._snapshot.model_service_tier_plans.get(slug) or self._snapshot.model_service_tier_plans.get(
|
|
605
|
+
normalized_slug
|
|
606
|
+
)
|
|
607
|
+
if tier_plans is None:
|
|
608
|
+
return frozenset() if self._snapshot.account_catalogs_authoritative else self.plan_types_for_model(slug)
|
|
609
|
+
return tier_plans.get(normalized_service_tier, frozenset())
|
|
610
|
+
|
|
611
|
+
def account_ids_for_model_service_tier(self, slug: str, service_tier: str | None) -> frozenset[str] | None:
|
|
612
|
+
if service_tier is None or self._snapshot is None:
|
|
613
|
+
return None
|
|
614
|
+
if not self._snapshot.account_catalogs_authoritative:
|
|
615
|
+
return None
|
|
616
|
+
normalized_slug = slug.strip().lower()
|
|
617
|
+
normalized_service_tier = canonical_service_tier_value(service_tier)
|
|
618
|
+
if not normalized_slug or not normalized_service_tier:
|
|
619
|
+
return None
|
|
620
|
+
|
|
621
|
+
tier_accounts = self._snapshot.model_service_tier_accounts.get(
|
|
622
|
+
slug
|
|
623
|
+
) or self._snapshot.model_service_tier_accounts.get(normalized_slug)
|
|
624
|
+
if tier_accounts is None:
|
|
625
|
+
if self._snapshot.account_catalogs_authoritative:
|
|
626
|
+
return frozenset()
|
|
627
|
+
return None
|
|
628
|
+
return tier_accounts.get(normalized_service_tier, frozenset())
|
|
629
|
+
|
|
630
|
+
def model_advertises_service_tier(self, slug: str, service_tier: str | None) -> bool:
|
|
631
|
+
"""Report whether the catalog lists ``service_tier`` for ``slug`` at all.
|
|
632
|
+
|
|
633
|
+
This is deliberately distinct from "no account carries the tier": a model
|
|
634
|
+
that never advertises the tier cannot be routed at it by any account, so
|
|
635
|
+
an enforced tier must not be treated as an account-eligibility filter for
|
|
636
|
+
that model. Returns ``True`` whenever the answer is unknown (no snapshot,
|
|
637
|
+
non-authoritative catalogs, unusable slug/tier) so callers keep their
|
|
638
|
+
existing behavior until the catalog can actually answer.
|
|
639
|
+
"""
|
|
640
|
+
if service_tier is None or self._snapshot is None:
|
|
641
|
+
return True
|
|
642
|
+
if not self._snapshot.account_catalogs_authoritative:
|
|
643
|
+
return True
|
|
644
|
+
normalized_slug = slug.strip().lower()
|
|
645
|
+
normalized_service_tier = canonical_service_tier_value(service_tier)
|
|
646
|
+
if not normalized_slug or not normalized_service_tier:
|
|
647
|
+
return True
|
|
648
|
+
|
|
649
|
+
model_is_known = (
|
|
650
|
+
slug in self._snapshot.model_plans
|
|
651
|
+
or normalized_slug in self._snapshot.model_plans
|
|
652
|
+
or slug in self._snapshot.model_accounts
|
|
653
|
+
or normalized_slug in self._snapshot.model_accounts
|
|
654
|
+
)
|
|
655
|
+
if not model_is_known:
|
|
656
|
+
# An authoritative subscription snapshot cannot describe the tier
|
|
657
|
+
# capabilities of an operator-mapped or source-routed model that it
|
|
658
|
+
# does not contain. Keep the enforced tier when model identity is
|
|
659
|
+
# unknown instead of broadening fallback beyond catalog evidence.
|
|
660
|
+
return True
|
|
661
|
+
|
|
662
|
+
tier_accounts = self._snapshot.model_service_tier_accounts.get(
|
|
663
|
+
slug
|
|
664
|
+
) or self._snapshot.model_service_tier_accounts.get(normalized_slug)
|
|
665
|
+
tier_plans = self._snapshot.model_service_tier_plans.get(slug) or self._snapshot.model_service_tier_plans.get(
|
|
666
|
+
normalized_slug
|
|
667
|
+
)
|
|
668
|
+
if tier_accounts is None and tier_plans is None:
|
|
669
|
+
return False
|
|
670
|
+
return normalized_service_tier in (tier_accounts or {}) or normalized_service_tier in (tier_plans or {})
|
|
671
|
+
|
|
672
|
+
def account_ids_for_model(self, slug: str) -> frozenset[str] | None:
|
|
673
|
+
"""Return exact supporting accounts, or ``None`` while coverage is incomplete."""
|
|
674
|
+
if self._snapshot is None or not self._snapshot.account_catalogs_authoritative:
|
|
675
|
+
return None
|
|
676
|
+
normalized_slug = slug.strip().lower()
|
|
677
|
+
if not normalized_slug:
|
|
678
|
+
return None
|
|
679
|
+
return self._snapshot.model_accounts.get(slug) or self._snapshot.model_accounts.get(
|
|
680
|
+
normalized_slug, frozenset()
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
def prefers_websockets(self, slug: str | None) -> bool:
|
|
684
|
+
if not isinstance(slug, str):
|
|
685
|
+
return False
|
|
686
|
+
normalized_slug = slug.strip().lower()
|
|
687
|
+
if not normalized_slug:
|
|
688
|
+
return False
|
|
689
|
+
|
|
690
|
+
if self._snapshot is not None:
|
|
691
|
+
model = self._snapshot.models.get(slug) or self._snapshot.models.get(normalized_slug)
|
|
692
|
+
if model is not None:
|
|
693
|
+
return model.prefer_websockets
|
|
694
|
+
if self._snapshot.bootstrap_floor_active and normalized_slug not in self._snapshot.suppressed_model_slugs:
|
|
695
|
+
bootstrap_model = self._bootstrap_models.get(slug) or self._bootstrap_models.get(normalized_slug)
|
|
696
|
+
if bootstrap_model is not None:
|
|
697
|
+
return bootstrap_model.prefer_websockets
|
|
698
|
+
return False
|
|
699
|
+
|
|
700
|
+
bootstrap_model = self._bootstrap_models.get(slug) or self._bootstrap_models.get(normalized_slug)
|
|
701
|
+
if bootstrap_model is not None:
|
|
702
|
+
return bootstrap_model.prefer_websockets
|
|
703
|
+
|
|
704
|
+
return any(fnmatchcase(normalized_slug, pattern) for pattern in _BOOTSTRAP_WEBSOCKET_PREFERRED_MODEL_PATTERNS)
|
|
705
|
+
|
|
706
|
+
def needs_refresh(self) -> bool:
|
|
707
|
+
if self._snapshot is None:
|
|
708
|
+
return True
|
|
709
|
+
return (time.monotonic() - self._snapshot.fetched_at) >= self._ttl_seconds
|
|
710
|
+
|
|
711
|
+
async def clear(self) -> None:
|
|
712
|
+
"""Drop live capability state when no active accounts remain.
|
|
713
|
+
|
|
714
|
+
This resets to the bootstrap-floor state (equivalent to never having
|
|
715
|
+
refreshed): every reader falls back to the static bootstrap catalog. We
|
|
716
|
+
must NOT publish an authoritative-empty snapshot here. If we did, then in
|
|
717
|
+
the window after an account is added but before the next scheduled refresh
|
|
718
|
+
tick, ``plan_types_for_model`` / ``get_models_with_fallback`` would report
|
|
719
|
+
even canonical models as absent (because ``_snapshot`` is no longer
|
|
720
|
+
``None``), so ``_mapped_model_has_registry_entry`` would skip model/plan
|
|
721
|
+
filtering and let an unsupported plan be selected, and ``/v1/models`` would
|
|
722
|
+
stay empty until the timer fires. Bootstrap remains the discovery/gating
|
|
723
|
+
floor whenever there is no authoritative account coverage.
|
|
724
|
+
"""
|
|
725
|
+
async with self._lock:
|
|
726
|
+
self._snapshot = None
|
|
727
|
+
self._metadata_models = None
|
|
728
|
+
self._applied_content_hash = None
|
|
729
|
+
|
|
730
|
+
async def update(
|
|
731
|
+
self,
|
|
732
|
+
per_plan_results: dict[str, list[UpstreamModel]],
|
|
733
|
+
*,
|
|
734
|
+
per_account_results: dict[str, tuple[str, list[UpstreamModel]]] | None = None,
|
|
735
|
+
active_account_plans: dict[str, str] | None = None,
|
|
736
|
+
) -> None:
|
|
737
|
+
if not per_plan_results:
|
|
738
|
+
logger.warning("Model registry refresh produced no plan results; keeping cached snapshot")
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
async with self._lock:
|
|
742
|
+
previous = self._snapshot
|
|
743
|
+
try:
|
|
744
|
+
models: dict[str, UpstreamModel] = {}
|
|
745
|
+
model_plans: dict[str, set[str]] = {}
|
|
746
|
+
model_service_tier_plans: dict[str, dict[str, set[str]]] = {}
|
|
747
|
+
model_service_tier_accounts: dict[str, dict[str, set[str]]] = {}
|
|
748
|
+
model_accounts: dict[str, set[str]] = {}
|
|
749
|
+
account_plans: dict[str, str] = {}
|
|
750
|
+
suppressed_model_slugs: set[str] = (
|
|
751
|
+
set(previous.suppressed_model_slugs) if previous is not None else set()
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Carry over data from plans not present in per_plan_results
|
|
755
|
+
if previous is not None:
|
|
756
|
+
previous_plans = set(previous.plan_models.keys())
|
|
757
|
+
refreshed_plans = set(per_plan_results.keys())
|
|
758
|
+
stale_plans = previous_plans - refreshed_plans
|
|
759
|
+
if active_account_plans is not None:
|
|
760
|
+
stale_plans.intersection_update(active_account_plans.values())
|
|
761
|
+
stale_account_ids = {
|
|
762
|
+
account_id
|
|
763
|
+
for account_id, plan_type in previous.account_plans.items()
|
|
764
|
+
if account_id in active_account_plans
|
|
765
|
+
and plan_type in stale_plans
|
|
766
|
+
and active_account_plans[account_id] == plan_type
|
|
767
|
+
}
|
|
768
|
+
refreshed_account_ids = set(per_account_results or {})
|
|
769
|
+
stale_account_ids.update(
|
|
770
|
+
account_id
|
|
771
|
+
for account_id, previous_plan_type in previous.account_plans.items()
|
|
772
|
+
if account_id in active_account_plans
|
|
773
|
+
and account_id not in refreshed_account_ids
|
|
774
|
+
# A retained catalog proves capabilities only for the
|
|
775
|
+
# plan that produced it. A plan change without a fresh
|
|
776
|
+
# catalog leaves the active account unknown instead of
|
|
777
|
+
# re-labeling old entitlements as new-plan support.
|
|
778
|
+
and active_account_plans[account_id] == previous_plan_type
|
|
779
|
+
)
|
|
780
|
+
else:
|
|
781
|
+
stale_account_ids = {
|
|
782
|
+
account_id
|
|
783
|
+
for account_id, plan_type in previous.account_plans.items()
|
|
784
|
+
if plan_type in stale_plans
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
# Carryover invariant: never carry forward a stale-plan model
|
|
788
|
+
# that no currently-active account of that plan advertises, per
|
|
789
|
+
# the last-known per-account catalogs in ``previous.model_accounts``.
|
|
790
|
+
#
|
|
791
|
+
# This runs whenever we know the active account set
|
|
792
|
+
# (``active_account_plans`` is not None), REGARDLESS of whether the
|
|
793
|
+
# previous snapshot was authoritative. The authoritative flag governs
|
|
794
|
+
# whether per-account *routing* is trusted (see ``account_ids_for_model``),
|
|
795
|
+
# not whether a dead model is dropped from discovery: a first-refresh
|
|
796
|
+
# (non-authoritative) snapshot can still record that a private model
|
|
797
|
+
# was advertised only by an account that is now removed/paused, and
|
|
798
|
+
# that model must not linger in discovery once no active account serves it.
|
|
799
|
+
#
|
|
800
|
+
# An active account whose refresh transiently failed keeps its
|
|
801
|
+
# last-known models (it is in ``stale_account_ids`` and still appears
|
|
802
|
+
# in ``previous.model_accounts``). A model with NO per-account
|
|
803
|
+
# provenance at all (an older/plan-only snapshot that never captured
|
|
804
|
+
# per-account catalogs) is preserved rather than dropped, so we
|
|
805
|
+
# degrade safe when we cannot attribute a model to any account.
|
|
806
|
+
supported_stale_slugs: dict[str, set[str]] | None = None
|
|
807
|
+
if active_account_plans is not None:
|
|
808
|
+
supported_stale_slugs = {}
|
|
809
|
+
for slug, account_ids in previous.model_accounts.items():
|
|
810
|
+
for account_id in account_ids:
|
|
811
|
+
if account_id not in stale_account_ids:
|
|
812
|
+
continue
|
|
813
|
+
plan_of_account = active_account_plans.get(
|
|
814
|
+
account_id, previous.account_plans.get(account_id)
|
|
815
|
+
)
|
|
816
|
+
if plan_of_account in stale_plans:
|
|
817
|
+
supported_stale_slugs.setdefault(plan_of_account, set()).add(slug)
|
|
818
|
+
|
|
819
|
+
for plan_type in stale_plans:
|
|
820
|
+
stale_slugs: Iterable[str] = previous.plan_models.get(plan_type, frozenset())
|
|
821
|
+
if supported_stale_slugs is not None:
|
|
822
|
+
supported = supported_stale_slugs.get(plan_type, set())
|
|
823
|
+
stale_slugs = [
|
|
824
|
+
slug
|
|
825
|
+
for slug in stale_slugs
|
|
826
|
+
# Unknown per-account provenance -> keep (degrade safe).
|
|
827
|
+
if not previous.model_accounts.get(slug)
|
|
828
|
+
# Known provenance -> keep only with an active advertiser.
|
|
829
|
+
or slug in supported
|
|
830
|
+
]
|
|
831
|
+
for slug in stale_slugs:
|
|
832
|
+
if slug not in models and slug in previous.models:
|
|
833
|
+
models[slug] = previous.models[slug]
|
|
834
|
+
model_plans.setdefault(slug, set()).add(plan_type)
|
|
835
|
+
stale_tier_plans = previous.model_service_tier_plans.get(slug, {})
|
|
836
|
+
for service_tier, plans in stale_tier_plans.items():
|
|
837
|
+
if plan_type in plans:
|
|
838
|
+
model_service_tier_plans.setdefault(slug, {}).setdefault(service_tier, set()).add(
|
|
839
|
+
plan_type
|
|
840
|
+
)
|
|
841
|
+
for account_id in stale_account_ids:
|
|
842
|
+
plan_type = (
|
|
843
|
+
active_account_plans.get(account_id)
|
|
844
|
+
if active_account_plans is not None
|
|
845
|
+
else previous.account_plans.get(account_id)
|
|
846
|
+
)
|
|
847
|
+
if plan_type is not None:
|
|
848
|
+
account_plans[account_id] = plan_type
|
|
849
|
+
for slug, account_ids in previous.model_accounts.items():
|
|
850
|
+
if slug not in previous.models:
|
|
851
|
+
continue
|
|
852
|
+
stale_model_accounts = account_ids & stale_account_ids
|
|
853
|
+
if not stale_model_accounts:
|
|
854
|
+
continue
|
|
855
|
+
model_accounts.setdefault(slug, set()).update(stale_model_accounts)
|
|
856
|
+
if slug in previous.models:
|
|
857
|
+
models.setdefault(slug, previous.models[slug])
|
|
858
|
+
for account_id in stale_model_accounts:
|
|
859
|
+
plan_type = account_plans.get(account_id)
|
|
860
|
+
if plan_type is not None:
|
|
861
|
+
model_plans.setdefault(slug, set()).add(plan_type)
|
|
862
|
+
for slug, tier_accounts in previous.model_service_tier_accounts.items():
|
|
863
|
+
if slug not in previous.models:
|
|
864
|
+
continue
|
|
865
|
+
for service_tier, account_ids in tier_accounts.items():
|
|
866
|
+
stale_tier_accounts = account_ids & stale_account_ids
|
|
867
|
+
if stale_tier_accounts:
|
|
868
|
+
model_service_tier_accounts.setdefault(slug, {}).setdefault(service_tier, set()).update(
|
|
869
|
+
stale_tier_accounts
|
|
870
|
+
)
|
|
871
|
+
for account_id in stale_tier_accounts:
|
|
872
|
+
plan_type = account_plans.get(account_id)
|
|
873
|
+
if plan_type is not None:
|
|
874
|
+
model_service_tier_plans.setdefault(slug, {}).setdefault(
|
|
875
|
+
service_tier, set()
|
|
876
|
+
).add(plan_type)
|
|
877
|
+
|
|
878
|
+
# Merge newly fetched results, aggregating service-tier metadata
|
|
879
|
+
# across plans refreshed in this pass. Stale plan slugs still map
|
|
880
|
+
# to the model, but their previous global union must not re-add a
|
|
881
|
+
# tier that no current per-plan source has (issue #1106).
|
|
882
|
+
refreshed_model_slugs: set[str] = set()
|
|
883
|
+
for plan_type, plan_models_list in per_plan_results.items():
|
|
884
|
+
for model in plan_models_list:
|
|
885
|
+
existing = models.get(model.slug)
|
|
886
|
+
if existing is not None and model.slug in refreshed_model_slugs:
|
|
887
|
+
models[model.slug] = _merge_service_tier_metadata(existing, model)
|
|
888
|
+
else:
|
|
889
|
+
models[model.slug] = model
|
|
890
|
+
refreshed_model_slugs.add(model.slug)
|
|
891
|
+
model_plans.setdefault(model.slug, set()).add(plan_type)
|
|
892
|
+
for service_tier in _model_service_tier_keys(model):
|
|
893
|
+
model_service_tier_plans.setdefault(model.slug, {}).setdefault(service_tier, set()).add(
|
|
894
|
+
plan_type
|
|
895
|
+
)
|
|
896
|
+
if per_account_results is not None:
|
|
897
|
+
for account_id, (plan_type, account_models) in per_account_results.items():
|
|
898
|
+
account_plans[account_id] = plan_type
|
|
899
|
+
for model in account_models:
|
|
900
|
+
if model.slug not in models:
|
|
901
|
+
continue
|
|
902
|
+
model_accounts.setdefault(model.slug, set()).add(account_id)
|
|
903
|
+
for service_tier in _model_service_tier_keys(model):
|
|
904
|
+
model_service_tier_accounts.setdefault(model.slug, {}).setdefault(
|
|
905
|
+
service_tier, set()
|
|
906
|
+
).add(account_id)
|
|
907
|
+
|
|
908
|
+
frozen_model_plans: dict[str, frozenset[str]] = {
|
|
909
|
+
slug: frozenset(plans) for slug, plans in model_plans.items()
|
|
910
|
+
}
|
|
911
|
+
frozen_model_service_tier_plans: dict[str, dict[str, frozenset[str]]] = {
|
|
912
|
+
slug: {service_tier: frozenset(plans) for service_tier, plans in tier_plans.items()}
|
|
913
|
+
for slug, tier_plans in model_service_tier_plans.items()
|
|
914
|
+
}
|
|
915
|
+
frozen_model_service_tier_accounts: dict[str, dict[str, frozenset[str]]] = {
|
|
916
|
+
slug: {service_tier: frozenset(account_ids) for service_tier, account_ids in tier_accounts.items()}
|
|
917
|
+
for slug, tier_accounts in model_service_tier_accounts.items()
|
|
918
|
+
}
|
|
919
|
+
frozen_model_accounts = {slug: frozenset(account_ids) for slug, account_ids in model_accounts.items()}
|
|
920
|
+
authoritative_account_catalogs = per_account_results is not None and set(
|
|
921
|
+
active_account_plans or per_account_results
|
|
922
|
+
).issubset(account_plans)
|
|
923
|
+
|
|
924
|
+
if authoritative_account_catalogs:
|
|
925
|
+
for slug in self._bootstrap_models:
|
|
926
|
+
if slug not in models:
|
|
927
|
+
suppressed_model_slugs.add(slug)
|
|
928
|
+
for _plan_type, account_models in (per_account_results or {}).values():
|
|
929
|
+
for model in account_models:
|
|
930
|
+
if model.slug not in models:
|
|
931
|
+
suppressed_model_slugs.add(model.slug)
|
|
932
|
+
if previous is not None:
|
|
933
|
+
for slug, previous_account_ids in previous.model_accounts.items():
|
|
934
|
+
if slug in models or not previous_account_ids:
|
|
935
|
+
continue
|
|
936
|
+
suppressed_model_slugs.add(slug)
|
|
937
|
+
for slug in tuple(suppressed_model_slugs):
|
|
938
|
+
if slug in models:
|
|
939
|
+
suppressed_model_slugs.discard(slug)
|
|
940
|
+
|
|
941
|
+
# Build reverse index: plan_type -> set of slugs
|
|
942
|
+
plan_models_index: dict[str, set[str]] = {}
|
|
943
|
+
for slug, plans in frozen_model_plans.items():
|
|
944
|
+
for plan_type in plans:
|
|
945
|
+
plan_models_index.setdefault(plan_type, set()).add(slug)
|
|
946
|
+
|
|
947
|
+
frozen_plan_models: dict[str, frozenset[str]] = {
|
|
948
|
+
plan_type: frozenset(slugs) for plan_type, slugs in plan_models_index.items()
|
|
949
|
+
}
|
|
950
|
+
bootstrap_floor_active = per_account_results is not None and not authoritative_account_catalogs
|
|
951
|
+
|
|
952
|
+
metadata_models = {
|
|
953
|
+
slug: model
|
|
954
|
+
for slug, model in (self._metadata_models or self._bootstrap_models).items()
|
|
955
|
+
if slug in self._bootstrap_models
|
|
956
|
+
}
|
|
957
|
+
if per_account_results is not None:
|
|
958
|
+
account_metadata_models: dict[str, UpstreamModel] = {}
|
|
959
|
+
for _account_id, (_plan_type, account_models) in per_account_results.items():
|
|
960
|
+
for model in account_models:
|
|
961
|
+
if model.slug in self._bootstrap_models:
|
|
962
|
+
account_metadata_models.setdefault(model.slug, model)
|
|
963
|
+
metadata_models.update(account_metadata_models)
|
|
964
|
+
metadata_models.update(models)
|
|
965
|
+
self._snapshot = ModelRegistrySnapshot(
|
|
966
|
+
models=models,
|
|
967
|
+
model_plans=frozen_model_plans,
|
|
968
|
+
plan_models=frozen_plan_models,
|
|
969
|
+
model_service_tier_plans=frozen_model_service_tier_plans,
|
|
970
|
+
model_service_tier_accounts=frozen_model_service_tier_accounts,
|
|
971
|
+
account_plans=account_plans,
|
|
972
|
+
fetched_at=time.monotonic(),
|
|
973
|
+
model_accounts=frozen_model_accounts,
|
|
974
|
+
account_catalogs_authoritative=authoritative_account_catalogs,
|
|
975
|
+
bootstrap_floor_active=bootstrap_floor_active,
|
|
976
|
+
suppressed_model_slugs=frozenset(suppressed_model_slugs),
|
|
977
|
+
)
|
|
978
|
+
self._metadata_models = metadata_models
|
|
979
|
+
except Exception:
|
|
980
|
+
self._snapshot = previous
|
|
981
|
+
logger.warning("Model registry refresh failed; keeping cached snapshot", exc_info=True)
|
|
982
|
+
raise
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
_model_registry = ModelRegistry()
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def get_model_registry() -> ModelRegistry:
|
|
989
|
+
return _model_registry
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def is_public_model(model: UpstreamModel, allowed_models: set[str] | None) -> bool:
|
|
993
|
+
if not model.supported_in_api:
|
|
994
|
+
return False
|
|
995
|
+
if allowed_models is None:
|
|
996
|
+
return True
|
|
997
|
+
return model.slug in allowed_models
|