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,55 @@
|
|
|
1
|
+
"""Responses body projection for OpenAI-compatible model sources.
|
|
2
|
+
|
|
3
|
+
``strip_source_telemetry`` removes the Codex client telemetry a source must
|
|
4
|
+
never see -- whole fields where the field itself is Codex-only, a single key
|
|
5
|
+
inside the standard ``stream_options`` object -- and forwards everything else
|
|
6
|
+
verbatim: direct source routing never fails closed on an unknown field.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from app.core.types import JsonValue
|
|
12
|
+
|
|
13
|
+
type MutableJsonObject = dict[str, JsonValue]
|
|
14
|
+
|
|
15
|
+
# Codex client telemetry that an OpenAI-compatible source rejects or must not see.
|
|
16
|
+
# Whole top-level fields, because neither is a Responses API field:
|
|
17
|
+
# ``client_metadata`` (installation/session/thread/turn ids and turn metadata)
|
|
18
|
+
# and ``access_programs``.
|
|
19
|
+
STRIPPED_TELEMETRY_FIELDS: frozenset[str] = frozenset({"client_metadata", "access_programs"})
|
|
20
|
+
|
|
21
|
+
# ``stream_options`` is a standard Responses field (``include_obfuscation``)
|
|
22
|
+
# that Codex extends with ``reasoning_summary_delivery`` (its reasoning-summary
|
|
23
|
+
# delivery mode), so only that key is telemetry: it is removed from the object
|
|
24
|
+
# and the object is dropped once the removal leaves it empty -- the shape every
|
|
25
|
+
# Codex body has -- while an SDK client's ``include_obfuscation`` is forwarded
|
|
26
|
+
# unchanged.
|
|
27
|
+
STREAM_OPTIONS_FIELD = "stream_options"
|
|
28
|
+
STRIPPED_STREAM_OPTIONS_KEYS: frozenset[str] = frozenset({"reasoning_summary_delivery"})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def strip_source_telemetry(payload: MutableJsonObject) -> MutableJsonObject:
|
|
32
|
+
"""Remove exactly the Codex telemetry in place; returns ``payload``.
|
|
33
|
+
|
|
34
|
+
``STRIPPED_TELEMETRY_FIELDS`` are removed whole; from a ``stream_options``
|
|
35
|
+
object only ``STRIPPED_STREAM_OPTIONS_KEYS`` are removed, and the object
|
|
36
|
+
itself is dropped when that removal leaves nothing behind. Everything else
|
|
37
|
+
-- ``prompt_cache_key``, ``tools``, ``include``, ``max_output_tokens``,
|
|
38
|
+
``prompt_cache_retention``, ``background``, ``max_tool_calls``, a
|
|
39
|
+
``stream_options.include_obfuscation``, a ``stream_options`` that is not an
|
|
40
|
+
object, and any field this proxy has never seen -- is forwarded untouched,
|
|
41
|
+
so direct source routing never fails closed on an unknown field.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
for field in STRIPPED_TELEMETRY_FIELDS:
|
|
45
|
+
payload.pop(field, None)
|
|
46
|
+
stream_options = payload.get(STREAM_OPTIONS_FIELD)
|
|
47
|
+
if isinstance(stream_options, dict):
|
|
48
|
+
removed = False
|
|
49
|
+
for key in STRIPPED_STREAM_OPTIONS_KEYS:
|
|
50
|
+
if key in stream_options:
|
|
51
|
+
del stream_options[key]
|
|
52
|
+
removed = True
|
|
53
|
+
if removed and not stream_options:
|
|
54
|
+
del payload[STREAM_OPTIONS_FIELD]
|
|
55
|
+
return payload
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import ColumnElement, and_, delete, or_, select
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
from sqlalchemy.orm import selectinload
|
|
6
|
+
|
|
7
|
+
from app.db.models import ModelSource, ModelSourceModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _enablement_filter(only_disabled: bool) -> ColumnElement[bool]:
|
|
11
|
+
"""Enabled-state predicate for a per-capability source lookup.
|
|
12
|
+
|
|
13
|
+
``only_disabled`` selects the exact complement of the routable set: rows
|
|
14
|
+
that match the model and the route shape but that an operator switched off,
|
|
15
|
+
at the source or at the individual model. Routing needs that complement to
|
|
16
|
+
tell "no source serves this model" apart from "the source that serves this
|
|
17
|
+
model is switched off" -- the latter must not fall through to a
|
|
18
|
+
subscription account, which rejects the model outright.
|
|
19
|
+
"""
|
|
20
|
+
if only_disabled:
|
|
21
|
+
return or_(ModelSource.is_enabled.is_(False), ModelSourceModel.is_enabled.is_(False))
|
|
22
|
+
return and_(ModelSource.is_enabled.is_(True), ModelSourceModel.is_enabled.is_(True))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ModelSourcesRepository:
|
|
26
|
+
def __init__(self, session: AsyncSession) -> None:
|
|
27
|
+
self._session = session
|
|
28
|
+
|
|
29
|
+
async def list_sources(self) -> list[ModelSource]:
|
|
30
|
+
result = await self._session.execute(
|
|
31
|
+
select(ModelSource).options(selectinload(ModelSource.models)).order_by(ModelSource.name)
|
|
32
|
+
)
|
|
33
|
+
return list(result.scalars().unique().all())
|
|
34
|
+
|
|
35
|
+
async def list_enabled_sources(self) -> list[ModelSource]:
|
|
36
|
+
result = await self._session.execute(
|
|
37
|
+
select(ModelSource)
|
|
38
|
+
.options(selectinload(ModelSource.models))
|
|
39
|
+
.where(ModelSource.is_enabled.is_(True))
|
|
40
|
+
.order_by(ModelSource.name)
|
|
41
|
+
)
|
|
42
|
+
return list(result.scalars().unique().all())
|
|
43
|
+
|
|
44
|
+
async def get_by_id(self, source_id: str) -> ModelSource | None:
|
|
45
|
+
result = await self._session.execute(
|
|
46
|
+
select(ModelSource).options(selectinload(ModelSource.models)).where(ModelSource.id == source_id)
|
|
47
|
+
)
|
|
48
|
+
return result.scalar_one_or_none()
|
|
49
|
+
|
|
50
|
+
async def find_chat_source_for_model(
|
|
51
|
+
self,
|
|
52
|
+
model: str,
|
|
53
|
+
*,
|
|
54
|
+
allowed_source_ids: set[str] | None = None,
|
|
55
|
+
require_streaming: bool = False,
|
|
56
|
+
only_disabled: bool = False,
|
|
57
|
+
) -> ModelSource | None:
|
|
58
|
+
stmt = (
|
|
59
|
+
select(ModelSource)
|
|
60
|
+
.options(selectinload(ModelSource.models))
|
|
61
|
+
.join(ModelSourceModel, ModelSourceModel.source_id == ModelSource.id)
|
|
62
|
+
.where(ModelSource.kind == "openai_compatible")
|
|
63
|
+
.where(ModelSource.supports_chat_completions.is_(True))
|
|
64
|
+
.where(ModelSourceModel.model == model)
|
|
65
|
+
.where(_enablement_filter(only_disabled))
|
|
66
|
+
.order_by(ModelSource.name, ModelSource.id)
|
|
67
|
+
.limit(1)
|
|
68
|
+
)
|
|
69
|
+
if require_streaming:
|
|
70
|
+
stmt = stmt.where(ModelSourceModel.supports_streaming.is_(True))
|
|
71
|
+
if allowed_source_ids is not None:
|
|
72
|
+
if not allowed_source_ids:
|
|
73
|
+
return None
|
|
74
|
+
stmt = stmt.where(ModelSource.id.in_(allowed_source_ids))
|
|
75
|
+
result = await self._session.execute(stmt)
|
|
76
|
+
return result.scalar_one_or_none()
|
|
77
|
+
|
|
78
|
+
async def find_responses_source_for_model(
|
|
79
|
+
self,
|
|
80
|
+
model: str,
|
|
81
|
+
*,
|
|
82
|
+
allowed_source_ids: set[str] | None = None,
|
|
83
|
+
require_streaming: bool = False,
|
|
84
|
+
only_disabled: bool = False,
|
|
85
|
+
) -> ModelSource | None:
|
|
86
|
+
stmt = (
|
|
87
|
+
select(ModelSource)
|
|
88
|
+
.options(selectinload(ModelSource.models))
|
|
89
|
+
.join(ModelSourceModel, ModelSourceModel.source_id == ModelSource.id)
|
|
90
|
+
.where(ModelSource.kind == "openai_compatible")
|
|
91
|
+
.where(ModelSource.supports_responses.is_(True))
|
|
92
|
+
.where(ModelSourceModel.model == model)
|
|
93
|
+
.where(_enablement_filter(only_disabled))
|
|
94
|
+
.order_by(ModelSource.name, ModelSource.id)
|
|
95
|
+
.limit(1)
|
|
96
|
+
)
|
|
97
|
+
if require_streaming:
|
|
98
|
+
stmt = stmt.where(ModelSourceModel.supports_streaming.is_(True))
|
|
99
|
+
if allowed_source_ids is not None:
|
|
100
|
+
if not allowed_source_ids:
|
|
101
|
+
return None
|
|
102
|
+
stmt = stmt.where(ModelSource.id.in_(allowed_source_ids))
|
|
103
|
+
result = await self._session.execute(stmt)
|
|
104
|
+
return result.scalar_one_or_none()
|
|
105
|
+
|
|
106
|
+
async def find_audio_transcriptions_source_for_model(
|
|
107
|
+
self,
|
|
108
|
+
model: str,
|
|
109
|
+
*,
|
|
110
|
+
allowed_source_ids: set[str] | None = None,
|
|
111
|
+
) -> ModelSource | None:
|
|
112
|
+
stmt = (
|
|
113
|
+
select(ModelSource)
|
|
114
|
+
.options(selectinload(ModelSource.models))
|
|
115
|
+
.join(ModelSourceModel, ModelSourceModel.source_id == ModelSource.id)
|
|
116
|
+
.where(ModelSource.kind == "openai_compatible")
|
|
117
|
+
.where(ModelSource.is_enabled.is_(True))
|
|
118
|
+
.where(ModelSource.supports_audio_transcriptions.is_(True))
|
|
119
|
+
.where(ModelSourceModel.model == model)
|
|
120
|
+
.where(ModelSourceModel.is_enabled.is_(True))
|
|
121
|
+
.order_by(ModelSource.name, ModelSource.id)
|
|
122
|
+
.limit(1)
|
|
123
|
+
)
|
|
124
|
+
if allowed_source_ids is not None:
|
|
125
|
+
if not allowed_source_ids:
|
|
126
|
+
return None
|
|
127
|
+
stmt = stmt.where(ModelSource.id.in_(allowed_source_ids))
|
|
128
|
+
result = await self._session.execute(stmt)
|
|
129
|
+
return result.scalar_one_or_none()
|
|
130
|
+
|
|
131
|
+
async def find_embeddings_source_for_model(
|
|
132
|
+
self,
|
|
133
|
+
model: str,
|
|
134
|
+
*,
|
|
135
|
+
allowed_source_ids: set[str] | None = None,
|
|
136
|
+
) -> ModelSource | None:
|
|
137
|
+
stmt = (
|
|
138
|
+
select(ModelSource)
|
|
139
|
+
.options(selectinload(ModelSource.models))
|
|
140
|
+
.join(ModelSourceModel, ModelSourceModel.source_id == ModelSource.id)
|
|
141
|
+
.where(ModelSource.kind == "openai_compatible")
|
|
142
|
+
.where(ModelSource.is_enabled.is_(True))
|
|
143
|
+
.where(ModelSource.supports_embeddings.is_(True))
|
|
144
|
+
.where(ModelSourceModel.model == model)
|
|
145
|
+
.where(ModelSourceModel.is_enabled.is_(True))
|
|
146
|
+
.order_by(ModelSource.name, ModelSource.id)
|
|
147
|
+
.limit(1)
|
|
148
|
+
)
|
|
149
|
+
if allowed_source_ids is not None:
|
|
150
|
+
if not allowed_source_ids:
|
|
151
|
+
return None
|
|
152
|
+
stmt = stmt.where(ModelSource.id.in_(allowed_source_ids))
|
|
153
|
+
result = await self._session.execute(stmt)
|
|
154
|
+
return result.scalar_one_or_none()
|
|
155
|
+
|
|
156
|
+
async def create(self, row: ModelSource, *, commit: bool = True) -> ModelSource:
|
|
157
|
+
self._session.add(row)
|
|
158
|
+
if commit:
|
|
159
|
+
await self._session.commit()
|
|
160
|
+
await self._session.refresh(row, attribute_names=["models"])
|
|
161
|
+
return row
|
|
162
|
+
|
|
163
|
+
async def delete(self, source_id: str) -> bool:
|
|
164
|
+
result = await self._session.execute(
|
|
165
|
+
select(ModelSource)
|
|
166
|
+
.options(
|
|
167
|
+
selectinload(ModelSource.models),
|
|
168
|
+
selectinload(ModelSource.api_key_assignments),
|
|
169
|
+
)
|
|
170
|
+
.where(ModelSource.id == source_id)
|
|
171
|
+
)
|
|
172
|
+
row = result.scalar_one_or_none()
|
|
173
|
+
if row is None:
|
|
174
|
+
return False
|
|
175
|
+
await self._session.delete(row)
|
|
176
|
+
await self._session.commit()
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
async def replace_models(
|
|
180
|
+
self,
|
|
181
|
+
source: ModelSource,
|
|
182
|
+
models: list[ModelSourceModel],
|
|
183
|
+
*,
|
|
184
|
+
commit: bool = True,
|
|
185
|
+
) -> None:
|
|
186
|
+
await self._session.execute(delete(ModelSourceModel).where(ModelSourceModel.source_id == source.id))
|
|
187
|
+
for model in models:
|
|
188
|
+
model.source_id = source.id
|
|
189
|
+
self._session.add(model)
|
|
190
|
+
if commit:
|
|
191
|
+
await self._session.commit()
|
|
192
|
+
await self._session.refresh(source, attribute_names=["models"])
|
|
193
|
+
|
|
194
|
+
async def refresh_models(self, source: ModelSource) -> None:
|
|
195
|
+
await self._session.refresh(source, attribute_names=["models"])
|
|
196
|
+
|
|
197
|
+
async def commit(self) -> None:
|
|
198
|
+
await self._session.commit()
|
|
199
|
+
|
|
200
|
+
async def rollback(self) -> None:
|
|
201
|
+
await self._session.rollback()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from app.modules.shared.schemas import DashboardModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelSourceModelInput(DashboardModel):
|
|
11
|
+
model: str = Field(min_length=1, max_length=255)
|
|
12
|
+
display_name: str | None = Field(default=None, max_length=255)
|
|
13
|
+
context_window: int | None = Field(default=None, ge=1)
|
|
14
|
+
max_output_tokens: int | None = Field(default=None, ge=1)
|
|
15
|
+
supports_streaming: bool = True
|
|
16
|
+
supports_tools: bool = False
|
|
17
|
+
supports_vision: bool = False
|
|
18
|
+
input_per_1m: float | None = Field(default=None, ge=0)
|
|
19
|
+
cached_input_per_1m: float | None = Field(default=None, ge=0)
|
|
20
|
+
output_per_1m: float | None = Field(default=None, ge=0)
|
|
21
|
+
audio_per_minute: float | None = Field(default=None, ge=0)
|
|
22
|
+
raw_metadata_json: str | None = None
|
|
23
|
+
is_enabled: bool = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ModelSourceModelResponse(ModelSourceModelInput):
|
|
27
|
+
id: int
|
|
28
|
+
source_id: str
|
|
29
|
+
created_at: datetime
|
|
30
|
+
updated_at: datetime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ModelSourceCreateRequest(DashboardModel):
|
|
34
|
+
name: str = Field(min_length=1, max_length=128)
|
|
35
|
+
base_url: str = Field(min_length=1, max_length=2048)
|
|
36
|
+
api_key: str | None = Field(default=None, min_length=1)
|
|
37
|
+
supports_chat_completions: bool = True
|
|
38
|
+
supports_responses: bool = False
|
|
39
|
+
supports_audio_transcriptions: bool = False
|
|
40
|
+
supports_embeddings: bool = False
|
|
41
|
+
timeout_seconds: int | None = Field(default=None, ge=1)
|
|
42
|
+
max_concurrency: int | None = Field(default=None, ge=1)
|
|
43
|
+
models: list[ModelSourceModelInput] = Field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ModelSourceUpdateRequest(DashboardModel):
|
|
47
|
+
name: str | None = Field(default=None, min_length=1, max_length=128)
|
|
48
|
+
base_url: str | None = Field(default=None, min_length=1, max_length=2048)
|
|
49
|
+
api_key: str | None = Field(default=None, min_length=1)
|
|
50
|
+
is_enabled: bool | None = None
|
|
51
|
+
supports_chat_completions: bool | None = None
|
|
52
|
+
supports_responses: bool | None = None
|
|
53
|
+
supports_audio_transcriptions: bool | None = None
|
|
54
|
+
supports_embeddings: bool | None = None
|
|
55
|
+
timeout_seconds: int | None = Field(default=None, ge=1)
|
|
56
|
+
max_concurrency: int | None = Field(default=None, ge=1)
|
|
57
|
+
models: list[ModelSourceModelInput] | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ModelSourceResponse(DashboardModel):
|
|
61
|
+
id: str
|
|
62
|
+
name: str
|
|
63
|
+
kind: str
|
|
64
|
+
base_url: str
|
|
65
|
+
is_enabled: bool
|
|
66
|
+
health_status: str
|
|
67
|
+
supports_chat_completions: bool
|
|
68
|
+
supports_responses: bool
|
|
69
|
+
supports_audio_transcriptions: bool
|
|
70
|
+
supports_embeddings: bool
|
|
71
|
+
timeout_seconds: int | None
|
|
72
|
+
max_concurrency: int | None
|
|
73
|
+
created_at: datetime
|
|
74
|
+
updated_at: datetime
|
|
75
|
+
models: list[ModelSourceModelResponse] = Field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ModelSourcesResponse(DashboardModel):
|
|
79
|
+
sources: list[ModelSourceResponse] = Field(default_factory=list)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Shared model-source selection helpers.
|
|
2
|
+
|
|
3
|
+
Both the HTTP request handlers and the WebSocket session path need to decide
|
|
4
|
+
whether a requested model is served by an OpenAI-compatible model source.
|
|
5
|
+
Keeping that decision in one module stops the two transports from drifting
|
|
6
|
+
apart: previously only the HTTP handlers consulted model sources, so a
|
|
7
|
+
source-owned model requested over WebSocket fell through to subscription
|
|
8
|
+
account selection and was rejected upstream.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
from app.core.openai.model_registry import get_model_registry
|
|
16
|
+
from app.db.models import ModelSource
|
|
17
|
+
from app.db.session import detach_session_objects, get_background_session
|
|
18
|
+
from app.modules.api_keys.service import ApiKeyData
|
|
19
|
+
from app.modules.model_sources.repository import ModelSourcesRepository
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def allowed_source_ids_for_api_key(api_key: ApiKeyData | None) -> set[str] | None:
|
|
25
|
+
"""Source ids an API key may use, or ``None`` when scoping is disabled."""
|
|
26
|
+
if api_key is None or not api_key.source_assignment_scope_enabled:
|
|
27
|
+
return None
|
|
28
|
+
return set(api_key.assigned_source_ids)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def select_responses_model_source(
|
|
32
|
+
model: str,
|
|
33
|
+
api_key: ApiKeyData | None,
|
|
34
|
+
*,
|
|
35
|
+
raw_model: str | None = None,
|
|
36
|
+
require_streaming: bool = False,
|
|
37
|
+
only_disabled: bool = False,
|
|
38
|
+
) -> tuple[ModelSource, str] | None:
|
|
39
|
+
"""Resolve ``model`` to a Responses-capable model source, if any.
|
|
40
|
+
|
|
41
|
+
``only_disabled`` inverts the enabled-state filter and leaves every other
|
|
42
|
+
rule -- candidate order, API key model allowlist, source assignment scope,
|
|
43
|
+
subscription-registry precedence, route shape, streaming -- untouched, so
|
|
44
|
+
the answer is exactly "the source this request would have used, had the
|
|
45
|
+
operator not switched it off". Callers use it after the ordinary lookup
|
|
46
|
+
misses, to refuse the request instead of handing a source-owned model to a
|
|
47
|
+
subscription account that cannot serve it. Sharing one function keeps the
|
|
48
|
+
two lookups from drifting apart the way the transports once did.
|
|
49
|
+
"""
|
|
50
|
+
assigned_source_ids = allowed_source_ids_for_api_key(api_key)
|
|
51
|
+
exact_allowed_models = set(api_key.allowed_models) if api_key and api_key.allowed_models else None
|
|
52
|
+
candidates = [candidate for candidate in (raw_model, model) if candidate]
|
|
53
|
+
if not candidates:
|
|
54
|
+
return None
|
|
55
|
+
deduped_candidates = list(dict.fromkeys(candidates))
|
|
56
|
+
registry_models = get_model_registry().get_models_with_fallback()
|
|
57
|
+
async with get_background_session() as session:
|
|
58
|
+
repository = ModelSourcesRepository(session)
|
|
59
|
+
for candidate in deduped_candidates:
|
|
60
|
+
if exact_allowed_models is not None and candidate not in exact_allowed_models:
|
|
61
|
+
continue
|
|
62
|
+
subscription_model = registry_models.get(candidate)
|
|
63
|
+
if assigned_source_ids is None and subscription_model is not None:
|
|
64
|
+
continue
|
|
65
|
+
source = await repository.find_responses_source_for_model(
|
|
66
|
+
candidate,
|
|
67
|
+
allowed_source_ids=assigned_source_ids,
|
|
68
|
+
require_streaming=require_streaming,
|
|
69
|
+
only_disabled=only_disabled,
|
|
70
|
+
)
|
|
71
|
+
if source is not None:
|
|
72
|
+
break
|
|
73
|
+
else:
|
|
74
|
+
source = None
|
|
75
|
+
# ``close_session`` rolls back the read transaction, which would
|
|
76
|
+
# expire the loaded row; detach it so the forwarding path can read
|
|
77
|
+
# its attributes after this session boundary.
|
|
78
|
+
detach_session_objects(session)
|
|
79
|
+
return (source, candidate) if source is not None else None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def effective_model_for_api_key(api_key: ApiKeyData | None, requested_model: str | None) -> str | None:
|
|
83
|
+
"""The model an API key forces, falling back to the requested one."""
|
|
84
|
+
if api_key is None or api_key.enforced_model is None:
|
|
85
|
+
return requested_model
|
|
86
|
+
return api_key.enforced_model
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def responses_model_is_source_owned(
|
|
90
|
+
model: str | None,
|
|
91
|
+
api_key: ApiKeyData | None,
|
|
92
|
+
*,
|
|
93
|
+
raw_model: str | None = None,
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""True when ``model`` is served by a Responses-capable source, even a disabled one.
|
|
96
|
+
|
|
97
|
+
Used by the WebSocket path, which cannot forward to a model source and must
|
|
98
|
+
fail the session so the client falls back to the HTTP transport.
|
|
99
|
+
|
|
100
|
+
Disabled-source ownership counts as ownership here: the WebSocket transport
|
|
101
|
+
cannot serve a source-owned model regardless of the source's enabled state,
|
|
102
|
+
and dispatching the turn to a subscription account instead produces the
|
|
103
|
+
opaque upstream 400 (``The '<model>' model is not supported when using
|
|
104
|
+
Codex with a ChatGPT account.``). Bouncing the turn to HTTP lands it on the
|
|
105
|
+
disabled-source denial there, which answers 503 ``model_source_disabled``
|
|
106
|
+
and names the actual condition. The second, ``only_disabled`` probe runs
|
|
107
|
+
only after the ordinary lookup misses, so enabled sources keep resolving in
|
|
108
|
+
a single query.
|
|
109
|
+
|
|
110
|
+
The API key's ``enforced_model`` is considered alongside the requested
|
|
111
|
+
model, matching how the HTTP handlers build their candidate list: an
|
|
112
|
+
enforced model that resolves to a source must not slip through to
|
|
113
|
+
subscription-account selection.
|
|
114
|
+
|
|
115
|
+
``raw_model`` is the client's requested model captured before request
|
|
116
|
+
preparation normalized aliases (``gpt-5-high`` -> ``gpt-5``), mirroring the
|
|
117
|
+
HTTP path's ``raw_source_model``: the caller has already substituted the
|
|
118
|
+
API key's ``enforced_model`` and applied the fast-mode correction, so it is
|
|
119
|
+
used verbatim as the leading source candidate. When omitted (request states
|
|
120
|
+
that predate preparation, e.g. replayed turns), the raw candidate is
|
|
121
|
+
derived from ``enforced_model``/``model`` as before.
|
|
122
|
+
|
|
123
|
+
Resolution failures fail open to ``False``. This helper only gates the
|
|
124
|
+
WebSocket transport, where the alternative is worse: the lookup runs after
|
|
125
|
+
the turn's usage reservation is acquired but before it is registered for
|
|
126
|
+
cleanup, so a propagating database error tears the whole session down and
|
|
127
|
+
strands the reservation until the stale reaper runs. Failing open degrades
|
|
128
|
+
to the pre-guard behaviour (the subscription upstream rejects the model),
|
|
129
|
+
and source forwarding could not have worked anyway — it needs the same
|
|
130
|
+
database for the source's credentials. The HTTP handlers deliberately do
|
|
131
|
+
not use this helper: they call ``select_responses_model_source`` directly
|
|
132
|
+
and must keep surfacing resolution errors rather than silently routing
|
|
133
|
+
source traffic to a subscription account.
|
|
134
|
+
"""
|
|
135
|
+
raw = raw_model if raw_model is not None else effective_model_for_api_key(api_key, model)
|
|
136
|
+
if not model and not raw:
|
|
137
|
+
return False
|
|
138
|
+
try:
|
|
139
|
+
if (
|
|
140
|
+
await select_responses_model_source(
|
|
141
|
+
model or raw or "",
|
|
142
|
+
api_key,
|
|
143
|
+
raw_model=raw,
|
|
144
|
+
require_streaming=True,
|
|
145
|
+
)
|
|
146
|
+
is not None
|
|
147
|
+
):
|
|
148
|
+
return True
|
|
149
|
+
return (
|
|
150
|
+
await select_responses_model_source(
|
|
151
|
+
model or raw or "",
|
|
152
|
+
api_key,
|
|
153
|
+
raw_model=raw,
|
|
154
|
+
require_streaming=True,
|
|
155
|
+
only_disabled=True,
|
|
156
|
+
)
|
|
157
|
+
is not None
|
|
158
|
+
)
|
|
159
|
+
except Exception:
|
|
160
|
+
logger.warning(
|
|
161
|
+
"model_source_resolution_failed_open model=%s raw_model=%s",
|
|
162
|
+
model,
|
|
163
|
+
raw,
|
|
164
|
+
exc_info=True,
|
|
165
|
+
)
|
|
166
|
+
return False
|