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
|
File without changes
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Operator endpoints for the cross-account prompt cache isolation probe.
|
|
2
|
+
|
|
3
|
+
Both routes sit behind ``ops:write`` -- the same gate as the other
|
|
4
|
+
operational actions that change what the fleet does -- because running the
|
|
5
|
+
probe bills the account pool. ``GET`` is the cost preview the operator reads
|
|
6
|
+
before deciding; ``POST`` is the spend, and it refuses to act without an
|
|
7
|
+
explicit confirmation, a shared rate-limit budget, and a pool that is not
|
|
8
|
+
already under pressure.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from fastapi import APIRouter, Body, Depends, Request
|
|
14
|
+
|
|
15
|
+
from app.core.audit.service import AuditActor, AuditService, AuditTarget
|
|
16
|
+
from app.core.auth.dashboard_access import DashboardPrincipal, Permission
|
|
17
|
+
from app.core.auth.dependencies import (
|
|
18
|
+
require_dashboard_permission,
|
|
19
|
+
set_dashboard_error_format,
|
|
20
|
+
validate_dashboard_session,
|
|
21
|
+
)
|
|
22
|
+
from app.core.exceptions import DashboardBadRequestError, DashboardConflictError
|
|
23
|
+
from app.core.rate_limiter.db_rate_limiter import DatabaseRateLimiter
|
|
24
|
+
from app.db.session import get_background_session
|
|
25
|
+
from app.modules.cache_isolation_probe.schemas import (
|
|
26
|
+
CacheProbeAccountResponse,
|
|
27
|
+
CacheProbeCallResponse,
|
|
28
|
+
CacheProbePlanResponse,
|
|
29
|
+
CacheProbePressureResponse,
|
|
30
|
+
CacheProbeRunRequest,
|
|
31
|
+
CacheProbeRunResponse,
|
|
32
|
+
)
|
|
33
|
+
from app.modules.cache_isolation_probe.service import (
|
|
34
|
+
CacheIsolationProbeService,
|
|
35
|
+
CacheProbeRefused,
|
|
36
|
+
ProbeAccount,
|
|
37
|
+
ProbePlan,
|
|
38
|
+
ProbeResult,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
router = APIRouter(
|
|
42
|
+
prefix="/api/diagnostics/cache-isolation-probe",
|
|
43
|
+
tags=["dashboard"],
|
|
44
|
+
dependencies=[Depends(validate_dashboard_session), Depends(set_dashboard_error_format)],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
#: Deliberately a *shared* budget, not a per-principal one: the resource being
|
|
48
|
+
#: protected is the account pool's quota, which two operators drain just as
|
|
49
|
+
#: fast as one. Three runs an hour leaves room for a before/after comparison
|
|
50
|
+
#: plus a retry without turning the diagnostic into a traffic source.
|
|
51
|
+
_RATE_LIMIT_KEY = "cache-isolation-probe"
|
|
52
|
+
_probe_rate_limiter = DatabaseRateLimiter(max_attempts=3, window_seconds=3600, type="cache_isolation_probe")
|
|
53
|
+
|
|
54
|
+
_service = CacheIsolationProbeService()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_cache_isolation_probe_service() -> CacheIsolationProbeService:
|
|
58
|
+
return _service
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_cache_isolation_probe_rate_limiter() -> DatabaseRateLimiter:
|
|
62
|
+
return _probe_rate_limiter
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _account_response(account: ProbeAccount) -> CacheProbeAccountResponse:
|
|
66
|
+
return CacheProbeAccountResponse(account_id=account.account_id, label=account.label)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _plan_response(plan: ProbePlan) -> CacheProbePlanResponse:
|
|
70
|
+
return CacheProbePlanResponse(
|
|
71
|
+
model=plan.model,
|
|
72
|
+
seed_account=_account_response(plan.seed_account) if plan.seed_account is not None else None,
|
|
73
|
+
available_other_accounts=[_account_response(account) for account in plan.available_other_accounts],
|
|
74
|
+
seed_repetitions=plan.seed_repetitions,
|
|
75
|
+
total_calls=plan.total_calls,
|
|
76
|
+
estimated_input_tokens_per_call=plan.estimated_input_tokens_per_call,
|
|
77
|
+
estimated_total_input_tokens=plan.estimated_total_input_tokens,
|
|
78
|
+
max_seed_repetitions=plan.max_seed_repetitions,
|
|
79
|
+
max_other_accounts=plan.max_other_accounts,
|
|
80
|
+
pressure=CacheProbePressureResponse(
|
|
81
|
+
under_pressure=plan.pressure.under_pressure,
|
|
82
|
+
reason=plan.pressure.reason,
|
|
83
|
+
detail=plan.pressure.detail,
|
|
84
|
+
selectable_account_count=plan.pressure.selectable_account_count,
|
|
85
|
+
eligible_account_count=plan.pressure.eligible_account_count,
|
|
86
|
+
pressured_account_count=plan.pressure.pressured_account_count,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _run_response(result: ProbeResult) -> CacheProbeRunResponse:
|
|
92
|
+
return CacheProbeRunResponse(
|
|
93
|
+
run_id=result.run_id,
|
|
94
|
+
model=result.model,
|
|
95
|
+
started_at=result.started_at,
|
|
96
|
+
completed_at=result.completed_at,
|
|
97
|
+
seed_account=_account_response(result.seed_account),
|
|
98
|
+
seed_repetitions=result.seed_repetitions,
|
|
99
|
+
calls=[
|
|
100
|
+
CacheProbeCallResponse(
|
|
101
|
+
sequence=call.sequence,
|
|
102
|
+
account_id=call.account_id,
|
|
103
|
+
account_label=call.account_label,
|
|
104
|
+
role=call.role,
|
|
105
|
+
status=call.status,
|
|
106
|
+
cache_hit=call.cache_hit,
|
|
107
|
+
input_tokens=call.input_tokens,
|
|
108
|
+
cached_tokens=call.cached_tokens,
|
|
109
|
+
latency_ms=call.latency_ms,
|
|
110
|
+
error_code=call.error_code,
|
|
111
|
+
)
|
|
112
|
+
for call in result.calls
|
|
113
|
+
],
|
|
114
|
+
seed_hit_count=result.seed_hit_count,
|
|
115
|
+
seed_call_count=result.seed_call_count,
|
|
116
|
+
other_hit_count=result.other_hit_count,
|
|
117
|
+
other_call_count=result.other_call_count,
|
|
118
|
+
cross_account_hit=result.cross_account_hit,
|
|
119
|
+
verdict=result.verdict,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@router.get(
|
|
124
|
+
"",
|
|
125
|
+
response_model=CacheProbePlanResponse,
|
|
126
|
+
dependencies=[Depends(require_dashboard_permission(Permission.OPS_WRITE))],
|
|
127
|
+
)
|
|
128
|
+
async def get_cache_isolation_probe_plan(
|
|
129
|
+
service: CacheIsolationProbeService = Depends(get_cache_isolation_probe_service),
|
|
130
|
+
) -> CacheProbePlanResponse:
|
|
131
|
+
return _plan_response(await service.plan())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@router.post(
|
|
135
|
+
"/run",
|
|
136
|
+
response_model=CacheProbeRunResponse,
|
|
137
|
+
dependencies=[Depends(require_dashboard_permission(Permission.OPS_WRITE))],
|
|
138
|
+
)
|
|
139
|
+
async def run_cache_isolation_probe(
|
|
140
|
+
request: Request,
|
|
141
|
+
payload: CacheProbeRunRequest = Body(default_factory=CacheProbeRunRequest),
|
|
142
|
+
principal: DashboardPrincipal = Depends(validate_dashboard_session),
|
|
143
|
+
service: CacheIsolationProbeService = Depends(get_cache_isolation_probe_service),
|
|
144
|
+
) -> CacheProbeRunResponse:
|
|
145
|
+
if payload.confirm is not True:
|
|
146
|
+
raise DashboardBadRequestError(
|
|
147
|
+
"Running the cache isolation probe spends real account quota and must be confirmed explicitly.",
|
|
148
|
+
code="cache_probe_confirmation_required",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Charged before the run, in its own short-lived session, so the shared
|
|
152
|
+
# budget is spent even if the run then fails -- and so no DB session is
|
|
153
|
+
# held open across minutes of upstream calls.
|
|
154
|
+
async with get_background_session() as session:
|
|
155
|
+
await get_cache_isolation_probe_rate_limiter().check_and_increment(_RATE_LIMIT_KEY, session)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
result = await service.run(
|
|
159
|
+
model=payload.model,
|
|
160
|
+
seed_repetitions=payload.seed_repetitions,
|
|
161
|
+
other_account_count=payload.other_account_count,
|
|
162
|
+
)
|
|
163
|
+
except CacheProbeRefused as exc:
|
|
164
|
+
raise DashboardConflictError(exc.message, code=exc.code) from exc
|
|
165
|
+
|
|
166
|
+
# Numeric results only: the generated prefix, the run nonce and every byte
|
|
167
|
+
# of upstream response content are never persisted anywhere.
|
|
168
|
+
AuditService.log_async(
|
|
169
|
+
"cache_isolation_probe_run",
|
|
170
|
+
actor_ip=request.client.host if request.client else None,
|
|
171
|
+
actor=AuditActor.from_principal(principal),
|
|
172
|
+
target=AuditTarget("account", result.seed_account.account_id),
|
|
173
|
+
details={
|
|
174
|
+
"run_id": result.run_id,
|
|
175
|
+
"model": result.model,
|
|
176
|
+
"verdict": result.verdict,
|
|
177
|
+
"seed_account_id": result.seed_account.account_id,
|
|
178
|
+
"seed_repetitions": result.seed_repetitions,
|
|
179
|
+
"seed_hit_count": result.seed_hit_count,
|
|
180
|
+
"seed_call_count": result.seed_call_count,
|
|
181
|
+
"other_hit_count": result.other_hit_count,
|
|
182
|
+
"other_call_count": result.other_call_count,
|
|
183
|
+
"cross_account_hit": result.cross_account_hit,
|
|
184
|
+
"total_input_tokens": sum(call.input_tokens or 0 for call in result.calls),
|
|
185
|
+
"total_cached_tokens": sum(call.cached_tokens or 0 for call in result.calls),
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
return _run_response(result)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Deterministic, single-use filler prefix for the cross-account cache probe.
|
|
2
|
+
|
|
3
|
+
The probe asks one question: does a large prompt prefix that only one account
|
|
4
|
+
has ever sent still register as cached when a *sibling* account sends the
|
|
5
|
+
identical bytes? Answering it needs a prefix with two properties that pull in
|
|
6
|
+
opposite directions:
|
|
7
|
+
|
|
8
|
+
- **Fresh.** No earlier request, by any account, may have seeded it. Upstream
|
|
9
|
+
matches on a *prefix*, so a run-unique marker has to sit in the very first
|
|
10
|
+
tokens; a nonce appended at the end would leave the preceding ~28k tokens
|
|
11
|
+
identical to the previous run and silently measure the wrong thing. Every
|
|
12
|
+
filler word is therefore derived from the run nonce as well, so no two runs
|
|
13
|
+
share so much as a line.
|
|
14
|
+
- **Deterministic inside one run.** Every call of a run must send byte-identical
|
|
15
|
+
content, or a miss proves nothing. ``build_probe_prefix`` is a pure function
|
|
16
|
+
of ``(nonce, target_tokens)``: the filler words come from a keyed BLAKE2b
|
|
17
|
+
stream rather than ``random``, so the bytes do not depend on interpreter
|
|
18
|
+
version, hash seed, or iteration order.
|
|
19
|
+
|
|
20
|
+
The corpus is inert by construction and says so in both the header and the
|
|
21
|
+
footer, so a model that reads it has nothing to do but ignore it.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
import math
|
|
28
|
+
import secrets
|
|
29
|
+
from collections.abc import Iterator
|
|
30
|
+
from itertools import islice
|
|
31
|
+
|
|
32
|
+
#: Target size of the generated prefix. Large enough that a hit is
|
|
33
|
+
#: unmistakable against the handful of tokens the instruction costs, and it is
|
|
34
|
+
#: the size the hand-run experiment used (~28k), so results stay comparable.
|
|
35
|
+
DEFAULT_TARGET_PREFIX_TOKENS = 28_000
|
|
36
|
+
|
|
37
|
+
#: Bounds an operator (or a test) may ask for. The floor keeps the signal above
|
|
38
|
+
#: incidental caching of the instruction preamble; the ceiling bounds the spend.
|
|
39
|
+
MIN_TARGET_PREFIX_TOKENS = 1_000
|
|
40
|
+
MAX_TARGET_PREFIX_TOKENS = 32_000
|
|
41
|
+
|
|
42
|
+
#: What the probe asks the model to do once it has ignored the corpus. Short on
|
|
43
|
+
#: purpose: the probe measures *input* caching, so output tokens are waste.
|
|
44
|
+
PROBE_INSTRUCTIONS = "Ignore the reference corpus entirely. Reply with the single word OK."
|
|
45
|
+
PROBE_QUESTION = "Reply with OK."
|
|
46
|
+
|
|
47
|
+
_HEADER_TEMPLATE = (
|
|
48
|
+
"BEGIN CODEX-LB CACHE ISOLATION PROBE REFERENCE CORPUS run={nonce}\n"
|
|
49
|
+
"This block is inert filler emitted by an operator diagnostic that measures\n"
|
|
50
|
+
"upstream prompt cache behaviour. It carries no instruction, no question and\n"
|
|
51
|
+
"no data. Do not read it, do not summarise it, do not act on it. Skip to the\n"
|
|
52
|
+
"line after the END marker and follow only that.\n"
|
|
53
|
+
)
|
|
54
|
+
_FOOTER_TEMPLATE = "END CODEX-LB CACHE ISOLATION PROBE REFERENCE CORPUS run={nonce}\n"
|
|
55
|
+
|
|
56
|
+
#: Short, common, lowercase ASCII words. Each is a single BPE token for the
|
|
57
|
+
#: tokenizers upstream uses, which keeps ``estimate_prefix_tokens`` close to the
|
|
58
|
+
#: ``input_tokens`` the response actually reports.
|
|
59
|
+
_FILLER_VOCABULARY: tuple[str, ...] = tuple(
|
|
60
|
+
"""able acid aim air arc arm ash bad bag bar bay bed bee bell belt bird bit blue boat bone
|
|
61
|
+
book box boy bread brick bus cake calm cap car cat cell chair chin city clay clock cloud coal coat
|
|
62
|
+
code coin cold cook cord corn cost cow crop cup dark day deep desk dish dog door dot down draw
|
|
63
|
+
dry dust ear east""".split()
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
#: Filler words per line. Sixteen keeps lines readable in a log without making
|
|
67
|
+
#: the newline overhead a meaningful share of the token count.
|
|
68
|
+
_WORDS_PER_LINE = 16
|
|
69
|
+
#: One token per word plus one for the newline.
|
|
70
|
+
_TOKENS_PER_LINE = _WORDS_PER_LINE + 1
|
|
71
|
+
|
|
72
|
+
_NONCE_BYTES = 16
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def new_probe_nonce() -> str:
|
|
76
|
+
"""Fresh, unguessable run identifier. One per probe run, never reused."""
|
|
77
|
+
|
|
78
|
+
return secrets.token_hex(_NONCE_BYTES)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _text_tokens(text: str) -> int:
|
|
82
|
+
"""Rough token count of a literal: one per whitespace-separated word, one per line."""
|
|
83
|
+
|
|
84
|
+
return len(text.split()) + text.count("\n")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _clamp_target_tokens(target_tokens: int) -> int:
|
|
88
|
+
return max(MIN_TARGET_PREFIX_TOKENS, min(MAX_TARGET_PREFIX_TOKENS, target_tokens))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _filler_line_count(nonce: str, target_tokens: int) -> int:
|
|
92
|
+
fixed = _text_tokens(_HEADER_TEMPLATE.format(nonce=nonce)) + _text_tokens(_FOOTER_TEMPLATE.format(nonce=nonce))
|
|
93
|
+
remaining = max(0, _clamp_target_tokens(target_tokens) - fixed)
|
|
94
|
+
return max(1, math.ceil(remaining / _TOKENS_PER_LINE))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _filler_words(nonce: str, count: int) -> Iterator[str]:
|
|
98
|
+
"""Deterministic nonce-keyed word stream.
|
|
99
|
+
|
|
100
|
+
A keyed hash chain rather than ``random.Random``: the output must be
|
|
101
|
+
identical on every replica and every interpreter that serves the same run.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
produced = 0
|
|
105
|
+
block = 0
|
|
106
|
+
while produced < count:
|
|
107
|
+
digest = hashlib.blake2b(block.to_bytes(8, "big"), key=nonce.encode("ascii"), digest_size=64).digest()
|
|
108
|
+
for byte in digest:
|
|
109
|
+
if produced >= count:
|
|
110
|
+
return
|
|
111
|
+
yield _FILLER_VOCABULARY[byte % len(_FILLER_VOCABULARY)]
|
|
112
|
+
produced += 1
|
|
113
|
+
block += 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def estimate_prefix_tokens(target_tokens: int = DEFAULT_TARGET_PREFIX_TOKENS, *, nonce: str | None = None) -> int:
|
|
117
|
+
"""Estimated input tokens one probe call spends, before the model answers.
|
|
118
|
+
|
|
119
|
+
Used for the pre-run cost preview. The nonce only affects the count through
|
|
120
|
+
its fixed-width hex length, so the estimate is stable across runs.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
reference_nonce = nonce if nonce is not None else "0" * (_NONCE_BYTES * 2)
|
|
124
|
+
header = _HEADER_TEMPLATE.format(nonce=reference_nonce)
|
|
125
|
+
footer = _FOOTER_TEMPLATE.format(nonce=reference_nonce)
|
|
126
|
+
lines = _filler_line_count(reference_nonce, target_tokens)
|
|
127
|
+
return _text_tokens(header) + _text_tokens(footer) + lines * _TOKENS_PER_LINE + _text_tokens(PROBE_QUESTION)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_probe_prefix(nonce: str, *, target_tokens: int = DEFAULT_TARGET_PREFIX_TOKENS) -> str:
|
|
131
|
+
"""The run's filler corpus. Pure in ``(nonce, target_tokens)``.
|
|
132
|
+
|
|
133
|
+
Two calls with the same nonce return identical text; two nonces share no
|
|
134
|
+
line, including the first one.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
if not nonce:
|
|
138
|
+
raise ValueError("probe nonce must be a non-empty string")
|
|
139
|
+
header = _HEADER_TEMPLATE.format(nonce=nonce)
|
|
140
|
+
footer = _FOOTER_TEMPLATE.format(nonce=nonce)
|
|
141
|
+
lines = _filler_line_count(nonce, target_tokens)
|
|
142
|
+
words = _filler_words(nonce, lines * _WORDS_PER_LINE)
|
|
143
|
+
body = "\n".join(" ".join(islice(words, _WORDS_PER_LINE)) for _ in range(lines))
|
|
144
|
+
return f"{header}{body}\n{footer}"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Wire contract for the cross-account prompt cache isolation probe.
|
|
2
|
+
|
|
3
|
+
Only numbers, identifiers and enumerated statuses cross this boundary. The
|
|
4
|
+
generated prefix, the run nonce and every byte of upstream response content
|
|
5
|
+
stay inside the service.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from app.modules.cache_isolation_probe.service import (
|
|
15
|
+
DEFAULT_OTHER_ACCOUNTS,
|
|
16
|
+
DEFAULT_SEED_REPETITIONS,
|
|
17
|
+
MAX_OTHER_ACCOUNTS,
|
|
18
|
+
MAX_SEED_REPETITIONS,
|
|
19
|
+
ProbeCallRole,
|
|
20
|
+
ProbeCallStatus,
|
|
21
|
+
ProbeVerdict,
|
|
22
|
+
)
|
|
23
|
+
from app.modules.shared.schemas import DashboardModel
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CacheProbeAccountResponse(DashboardModel):
|
|
27
|
+
account_id: str
|
|
28
|
+
label: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CacheProbePressureResponse(DashboardModel):
|
|
32
|
+
under_pressure: bool
|
|
33
|
+
reason: str | None = None
|
|
34
|
+
detail: str | None = None
|
|
35
|
+
selectable_account_count: int
|
|
36
|
+
eligible_account_count: int
|
|
37
|
+
pressured_account_count: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CacheProbePlanResponse(DashboardModel):
|
|
41
|
+
model: str | None = None
|
|
42
|
+
seed_account: CacheProbeAccountResponse | None = None
|
|
43
|
+
#: Every sibling the pool can offer, capped; the client picks how many of
|
|
44
|
+
#: them a run should use.
|
|
45
|
+
available_other_accounts: list[CacheProbeAccountResponse] = Field(default_factory=list)
|
|
46
|
+
seed_repetitions: int
|
|
47
|
+
total_calls: int
|
|
48
|
+
estimated_input_tokens_per_call: int
|
|
49
|
+
estimated_total_input_tokens: int
|
|
50
|
+
max_seed_repetitions: int
|
|
51
|
+
max_other_accounts: int
|
|
52
|
+
pressure: CacheProbePressureResponse
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CacheProbeRunRequest(DashboardModel):
|
|
56
|
+
#: Explicit, typed acknowledgement that the run spends real quota. The
|
|
57
|
+
#: endpoint refuses anything but ``true`` so a bare POST cannot bill the
|
|
58
|
+
#: pool by accident.
|
|
59
|
+
confirm: bool = False
|
|
60
|
+
model: str | None = None
|
|
61
|
+
seed_repetitions: int = Field(default=DEFAULT_SEED_REPETITIONS, ge=1, le=MAX_SEED_REPETITIONS)
|
|
62
|
+
#: Upper bound on sibling accounts, not a demand: a smaller pool still
|
|
63
|
+
#: runs, with fewer rows. The plan response lists what will actually be
|
|
64
|
+
#: called.
|
|
65
|
+
other_account_count: int = Field(default=DEFAULT_OTHER_ACCOUNTS, ge=1, le=MAX_OTHER_ACCOUNTS)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CacheProbeCallResponse(DashboardModel):
|
|
69
|
+
sequence: int
|
|
70
|
+
account_id: str
|
|
71
|
+
account_label: str
|
|
72
|
+
role: ProbeCallRole
|
|
73
|
+
status: ProbeCallStatus
|
|
74
|
+
cache_hit: bool
|
|
75
|
+
input_tokens: int | None = None
|
|
76
|
+
cached_tokens: int | None = None
|
|
77
|
+
latency_ms: int
|
|
78
|
+
error_code: str | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CacheProbeRunResponse(DashboardModel):
|
|
82
|
+
run_id: str
|
|
83
|
+
model: str
|
|
84
|
+
started_at: datetime
|
|
85
|
+
completed_at: datetime
|
|
86
|
+
seed_account: CacheProbeAccountResponse
|
|
87
|
+
seed_repetitions: int
|
|
88
|
+
calls: list[CacheProbeCallResponse] = Field(default_factory=list)
|
|
89
|
+
seed_hit_count: int
|
|
90
|
+
seed_call_count: int
|
|
91
|
+
other_hit_count: int
|
|
92
|
+
other_call_count: int
|
|
93
|
+
cross_account_hit: bool
|
|
94
|
+
verdict: ProbeVerdict
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""One probe call: the routed upstream send, reported as numbers only.
|
|
2
|
+
|
|
3
|
+
This deliberately reuses the same primitives the limit warm-up sender uses --
|
|
4
|
+
``AuthManager.ensure_fresh`` for credentials, ``resolve_upstream_route`` for
|
|
5
|
+
proxy routing, ``stream_responses`` for the upstream client -- instead of
|
|
6
|
+
opening its own transport. The probe therefore inherits the account's
|
|
7
|
+
credentials, its proxy binding (including fail-closed behaviour) and the
|
|
8
|
+
configured stream timeouts without restating any of them.
|
|
9
|
+
|
|
10
|
+
Nothing here returns or stores response content. The only values that leave
|
|
11
|
+
this module are token counts, a latency, and a sanitized error code.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import contextlib
|
|
17
|
+
import time
|
|
18
|
+
import uuid
|
|
19
|
+
from collections.abc import AsyncGenerator
|
|
20
|
+
from contextlib import AbstractAsyncContextManager
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Callable, Protocol, cast
|
|
23
|
+
|
|
24
|
+
from app.core.auth.refresh import RefreshError
|
|
25
|
+
from app.core.clients.codex import CodexTransportError
|
|
26
|
+
from app.core.clients.proxy import UpstreamProxyRouteTrace, stream_responses
|
|
27
|
+
from app.core.crypto import TokenEncryptor
|
|
28
|
+
from app.core.openai.models import ResponseUsage
|
|
29
|
+
from app.core.openai.parsing import parse_sse_event
|
|
30
|
+
from app.core.openai.requests import ResponsesRequest
|
|
31
|
+
from app.core.upstream_proxy import ResolvedUpstreamRoute, UpstreamProxyRouteError, resolve_upstream_route
|
|
32
|
+
from app.db.models import Account, AccountStatus
|
|
33
|
+
from app.modules.accounts.auth_manager import AuthManager
|
|
34
|
+
from app.modules.accounts.repository import AccountsRepository
|
|
35
|
+
from app.modules.cache_isolation_probe.prefix import PROBE_INSTRUCTIONS, PROBE_QUESTION
|
|
36
|
+
|
|
37
|
+
CACHE_PROBE_HEADER = "x-codex-lb-cache-isolation-probe"
|
|
38
|
+
CACHE_PROBE_USER_AGENT = "codex-lb-cache-isolation-probe"
|
|
39
|
+
#: The probe measures input caching; every output token is pure waste.
|
|
40
|
+
CACHE_PROBE_MAX_OUTPUT_TOKENS = 16
|
|
41
|
+
|
|
42
|
+
_TERMINAL_ERROR_EVENTS = frozenset({"response.failed", "response.incomplete", "error"})
|
|
43
|
+
|
|
44
|
+
AccountsRepoFactory = Callable[[], AbstractAsyncContextManager[AccountsRepository]]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CacheProbeSenderPort(Protocol):
|
|
48
|
+
"""What the orchestrator needs from a sender, so a test can substitute one."""
|
|
49
|
+
|
|
50
|
+
async def send(self, account_id: str, *, model: str, prefix: str) -> ProbeSendResult: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class ProbeSendResult:
|
|
55
|
+
"""Numeric outcome of one upstream call. Never carries response content."""
|
|
56
|
+
|
|
57
|
+
ok: bool
|
|
58
|
+
latency_ms: int
|
|
59
|
+
input_tokens: int | None = None
|
|
60
|
+
cached_tokens: int | None = None
|
|
61
|
+
error_code: str | None = None
|
|
62
|
+
error_message: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _elapsed_ms(started: float) -> int:
|
|
66
|
+
return int((time.monotonic() - started) * 1000)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _usage_tokens(usage: ResponseUsage | None) -> tuple[int | None, int | None]:
|
|
70
|
+
if usage is None:
|
|
71
|
+
return None, None
|
|
72
|
+
details = usage.input_tokens_details
|
|
73
|
+
cached = details.cached_tokens if details is not None else None
|
|
74
|
+
return usage.input_tokens, cached
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CacheProbeSender:
|
|
78
|
+
"""Sends one probe call on one named account."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, accounts_repo_factory: AccountsRepoFactory) -> None:
|
|
81
|
+
self._accounts_repo_factory = accounts_repo_factory
|
|
82
|
+
self._encryptor = TokenEncryptor()
|
|
83
|
+
|
|
84
|
+
async def send(self, account_id: str, *, model: str, prefix: str) -> ProbeSendResult:
|
|
85
|
+
started = time.monotonic()
|
|
86
|
+
request_id = f"cache-probe-{uuid.uuid4().hex}"
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
account = await self._refreshed_account(account_id)
|
|
90
|
+
except RefreshError as exc:
|
|
91
|
+
return ProbeSendResult(
|
|
92
|
+
ok=False,
|
|
93
|
+
latency_ms=_elapsed_ms(started),
|
|
94
|
+
error_code=f"auth_refresh_{exc.code}",
|
|
95
|
+
error_message=exc.message,
|
|
96
|
+
)
|
|
97
|
+
if account is None or account.status != AccountStatus.ACTIVE:
|
|
98
|
+
status = "missing" if account is None else account.status.value
|
|
99
|
+
return ProbeSendResult(
|
|
100
|
+
ok=False,
|
|
101
|
+
latency_ms=_elapsed_ms(started),
|
|
102
|
+
error_code="account_not_active",
|
|
103
|
+
error_message=f"Account is not active ({status})",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
route = await self._resolve_route(account)
|
|
108
|
+
except UpstreamProxyRouteError as exc:
|
|
109
|
+
return ProbeSendResult(
|
|
110
|
+
ok=False,
|
|
111
|
+
latency_ms=_elapsed_ms(started),
|
|
112
|
+
error_code="upstream_proxy_unavailable",
|
|
113
|
+
error_message=f"Upstream proxy route unavailable: {exc.reason}",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
access_token = self._encryptor.decrypt(account.access_token_encrypted)
|
|
117
|
+
payload = ResponsesRequest.model_validate(
|
|
118
|
+
{
|
|
119
|
+
"model": model,
|
|
120
|
+
"instructions": PROBE_INSTRUCTIONS,
|
|
121
|
+
"input": f"{prefix}\n{PROBE_QUESTION}",
|
|
122
|
+
"tools": [],
|
|
123
|
+
"parallel_tool_calls": False,
|
|
124
|
+
"stream": True,
|
|
125
|
+
"store": False,
|
|
126
|
+
# `max_output_tokens` is stripped by `_UNSUPPORTED_UPSTREAM_FIELDS`
|
|
127
|
+
# before egress, so it cannot bound this call. It is kept because
|
|
128
|
+
# it states the intent for any model source that does honour it,
|
|
129
|
+
# and the real bound comes from the two fields below: the probe
|
|
130
|
+
# only needs `usage.input_tokens_details.cached_tokens` off the
|
|
131
|
+
# terminal frame, so every output token is waste. Without them a
|
|
132
|
+
# 28k-token prompt runs an uncapped generation, up to ten per run.
|
|
133
|
+
"max_output_tokens": CACHE_PROBE_MAX_OUTPUT_TOKENS,
|
|
134
|
+
"reasoning": {"effort": "low"},
|
|
135
|
+
"text": {"verbosity": "low"},
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
headers = {
|
|
139
|
+
"x-request-id": request_id,
|
|
140
|
+
CACHE_PROBE_HEADER: "1",
|
|
141
|
+
"user-agent": CACHE_PROBE_USER_AGENT,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
usage: ResponseUsage | None = None
|
|
145
|
+
try:
|
|
146
|
+
# aclosing(), not a bare `async for`: returning mid-stream on
|
|
147
|
+
# `response.completed` would otherwise leave the upstream response
|
|
148
|
+
# and its session to async-generator finalization, overlapping the
|
|
149
|
+
# next sequential call's connection.
|
|
150
|
+
stream = stream_responses(
|
|
151
|
+
payload,
|
|
152
|
+
headers,
|
|
153
|
+
access_token,
|
|
154
|
+
account.chatgpt_account_id,
|
|
155
|
+
upstream_stream_transport_override="http",
|
|
156
|
+
route=route,
|
|
157
|
+
route_trace=UpstreamProxyRouteTrace(),
|
|
158
|
+
allow_direct_egress=route is None,
|
|
159
|
+
codex_lb_account_id=account.id,
|
|
160
|
+
)
|
|
161
|
+
# ``stream_responses`` is an async generator function; its
|
|
162
|
+
# annotation widens to ``AsyncIterator``, which does not
|
|
163
|
+
# advertise ``aclose``.
|
|
164
|
+
async with contextlib.aclosing(cast("AsyncGenerator[str, None]", stream)):
|
|
165
|
+
async for event_block in stream:
|
|
166
|
+
event = parse_sse_event(event_block)
|
|
167
|
+
if event is None:
|
|
168
|
+
continue
|
|
169
|
+
if event.response is not None and event.response.usage is not None:
|
|
170
|
+
usage = event.response.usage
|
|
171
|
+
if event.type == "response.completed":
|
|
172
|
+
input_tokens, cached_tokens = _usage_tokens(usage)
|
|
173
|
+
return ProbeSendResult(
|
|
174
|
+
ok=True,
|
|
175
|
+
latency_ms=_elapsed_ms(started),
|
|
176
|
+
input_tokens=input_tokens,
|
|
177
|
+
cached_tokens=cached_tokens,
|
|
178
|
+
)
|
|
179
|
+
if event.type in _TERMINAL_ERROR_EVENTS:
|
|
180
|
+
error = event.error or (event.response.error if event.response is not None else None)
|
|
181
|
+
input_tokens, cached_tokens = _usage_tokens(usage)
|
|
182
|
+
return ProbeSendResult(
|
|
183
|
+
ok=False,
|
|
184
|
+
latency_ms=_elapsed_ms(started),
|
|
185
|
+
input_tokens=input_tokens,
|
|
186
|
+
cached_tokens=cached_tokens,
|
|
187
|
+
error_code=(error.code if error is not None else None) or event.type,
|
|
188
|
+
error_message=(error.message if error is not None else None) or event.type,
|
|
189
|
+
)
|
|
190
|
+
except CodexTransportError as exc:
|
|
191
|
+
# Routed-transport failures carry a classified code and a message
|
|
192
|
+
# that is credential-safe by construction, so they become a typed
|
|
193
|
+
# failed row rather than an exception the orchestrator has to
|
|
194
|
+
# sanitize blindly.
|
|
195
|
+
return ProbeSendResult(
|
|
196
|
+
ok=False,
|
|
197
|
+
latency_ms=_elapsed_ms(started),
|
|
198
|
+
error_code=exc.error_code or "upstream_transport_error",
|
|
199
|
+
error_message=str(exc),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
input_tokens, cached_tokens = _usage_tokens(usage)
|
|
203
|
+
return ProbeSendResult(
|
|
204
|
+
ok=False,
|
|
205
|
+
latency_ms=_elapsed_ms(started),
|
|
206
|
+
input_tokens=input_tokens,
|
|
207
|
+
cached_tokens=cached_tokens,
|
|
208
|
+
error_code="stream_incomplete",
|
|
209
|
+
error_message="Probe stream ended without a terminal event",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
async def _refreshed_account(self, account_id: str) -> Account | None:
|
|
213
|
+
async with self._accounts_repo_factory() as accounts_repo:
|
|
214
|
+
current = await accounts_repo.get_by_id_fresh(account_id)
|
|
215
|
+
if current is None or current.status != AccountStatus.ACTIVE:
|
|
216
|
+
return current
|
|
217
|
+
await AuthManager(accounts_repo, refresh_repo_factory=self._accounts_repo_factory).ensure_fresh(current)
|
|
218
|
+
async with self._accounts_repo_factory() as accounts_repo:
|
|
219
|
+
return await accounts_repo.get_by_id_fresh(account_id)
|
|
220
|
+
|
|
221
|
+
async def _resolve_route(self, account: Account) -> ResolvedUpstreamRoute | None:
|
|
222
|
+
async with self._accounts_repo_factory() as accounts_repo:
|
|
223
|
+
return await resolve_upstream_route(
|
|
224
|
+
accounts_repo.session,
|
|
225
|
+
account_id=account.id,
|
|
226
|
+
operation="cache_isolation_probe",
|
|
227
|
+
scope="account",
|
|
228
|
+
encryptor=self._encryptor,
|
|
229
|
+
)
|