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,407 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Iterable, Mapping
|
|
6
|
+
|
|
7
|
+
from app.core.plan_types import normalize_account_plan_type
|
|
8
|
+
from app.core.usage.models import UsageWindow
|
|
9
|
+
from app.core.usage.types import (
|
|
10
|
+
UsageCostSummary,
|
|
11
|
+
UsageHistoryPayload,
|
|
12
|
+
UsageMetricsSummary,
|
|
13
|
+
UsageSummaryPayload,
|
|
14
|
+
UsageWindowRow,
|
|
15
|
+
UsageWindowSnapshot,
|
|
16
|
+
UsageWindowSummary,
|
|
17
|
+
)
|
|
18
|
+
from app.db.models import Account
|
|
19
|
+
|
|
20
|
+
PLAN_CAPACITY_CREDITS_PRIMARY = {
|
|
21
|
+
"free": 0.0,
|
|
22
|
+
"plus": 225.0,
|
|
23
|
+
"business": 225.0,
|
|
24
|
+
"team": 225.0,
|
|
25
|
+
"edu": 225.0,
|
|
26
|
+
"pro": 1500.0,
|
|
27
|
+
"promax": 1500.0,
|
|
28
|
+
"prolite": 1125.0,
|
|
29
|
+
"enterprise": 1500.0,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
PLAN_CAPACITY_CREDITS_SECONDARY = {
|
|
33
|
+
"free": 1134.0,
|
|
34
|
+
"plus": 7560.0,
|
|
35
|
+
"business": 7560.0,
|
|
36
|
+
"team": 7560.0,
|
|
37
|
+
"edu": 7560.0,
|
|
38
|
+
"pro": 50400.0,
|
|
39
|
+
"promax": 50400.0,
|
|
40
|
+
"prolite": 37800.0,
|
|
41
|
+
"enterprise": 50400.0,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
PLAN_CAPACITY_CREDITS_MONTHLY = {
|
|
45
|
+
"free": 1134.0,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Rows written by the same upstream fetch land within milliseconds of each
|
|
49
|
+
# other; a sibling row only proves a *later* fetch (one that no longer
|
|
50
|
+
# reported the stale window) when it is newer by more than this margin. This
|
|
51
|
+
# is the same-fetch threshold shared by the weekly-primary remap tiebreak and
|
|
52
|
+
# the usage updater's sibling-row freshness logic. (Other modules keep their
|
|
53
|
+
# own private 5.0 s constants for unrelated sibling logic; consolidating those
|
|
54
|
+
# is out of scope for the weekly-primary tiebreak fix.)
|
|
55
|
+
SIBLING_FETCH_MARGIN_SECONDS = 5.0
|
|
56
|
+
|
|
57
|
+
DEFAULT_WINDOW_MINUTES_PRIMARY = 300
|
|
58
|
+
DEFAULT_WINDOW_MINUTES_SECONDARY = 10080
|
|
59
|
+
DEFAULT_WINDOW_MINUTES_MONTHLY = 43200
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class NormalizedRateLimitWindows:
|
|
64
|
+
primary: UsageWindow | None
|
|
65
|
+
secondary: UsageWindow | None
|
|
66
|
+
monthly: UsageWindow | None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _normalize_window_key(window: str | None) -> str:
|
|
70
|
+
normalized = (window or "").lower()
|
|
71
|
+
if normalized in {"primary", "5h"}:
|
|
72
|
+
return "primary"
|
|
73
|
+
if normalized in {"secondary", "7d"}:
|
|
74
|
+
return "secondary"
|
|
75
|
+
if normalized in {"monthly", "30d"}:
|
|
76
|
+
return "monthly"
|
|
77
|
+
return normalized
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def normalize_rate_limit_windows(
|
|
81
|
+
primary_window: UsageWindow | None,
|
|
82
|
+
secondary_window: UsageWindow | None,
|
|
83
|
+
) -> NormalizedRateLimitWindows:
|
|
84
|
+
if (
|
|
85
|
+
primary_window is not None
|
|
86
|
+
and primary_window.limit_window_seconds == DEFAULT_WINDOW_MINUTES_MONTHLY * 60
|
|
87
|
+
and secondary_window is None
|
|
88
|
+
):
|
|
89
|
+
return NormalizedRateLimitWindows(primary=None, secondary=None, monthly=primary_window)
|
|
90
|
+
return NormalizedRateLimitWindows(primary=primary_window, secondary=secondary_window, monthly=None)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _empty_cost() -> UsageCostSummary:
|
|
94
|
+
return UsageCostSummary(currency="USD", total_usd_7d=0.0, by_model=[])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _empty_window(
|
|
98
|
+
reset_at: int | None = None,
|
|
99
|
+
window_minutes: int | None = None,
|
|
100
|
+
) -> UsageWindowSnapshot:
|
|
101
|
+
return UsageWindowSnapshot(
|
|
102
|
+
used_percent=0.0,
|
|
103
|
+
capacity_credits=0.0,
|
|
104
|
+
used_credits=0.0,
|
|
105
|
+
reset_at=reset_at,
|
|
106
|
+
window_minutes=window_minutes,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def used_credits_from_percent(used_percent: float | None, capacity_credits: float | None) -> float | None:
|
|
111
|
+
if used_percent is None or capacity_credits is None:
|
|
112
|
+
return None
|
|
113
|
+
return (capacity_credits * used_percent) / 100.0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def remaining_percent_from_used(used_percent: float | None) -> float | None:
|
|
117
|
+
if used_percent is None:
|
|
118
|
+
return None
|
|
119
|
+
return max(0.0, 100.0 - float(used_percent))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def remaining_credits_from_used(
|
|
123
|
+
used_credits: float | None,
|
|
124
|
+
capacity_credits: float | None,
|
|
125
|
+
) -> float | None:
|
|
126
|
+
if used_credits is None or capacity_credits is None:
|
|
127
|
+
return None
|
|
128
|
+
return max(0.0, float(capacity_credits) - float(used_credits))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def remaining_credits_from_percent(
|
|
132
|
+
used_percent: float | None,
|
|
133
|
+
capacity_credits: float | None,
|
|
134
|
+
) -> float | None:
|
|
135
|
+
used_credits = used_credits_from_percent(used_percent, capacity_credits)
|
|
136
|
+
return remaining_credits_from_used(used_credits, capacity_credits)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def normalize_usage_window(summary: UsageWindowSummary) -> UsageWindowSnapshot:
|
|
140
|
+
return UsageWindowSnapshot(
|
|
141
|
+
used_percent=float(summary.used_percent or 0.0),
|
|
142
|
+
capacity_credits=float(summary.capacity_credits),
|
|
143
|
+
used_credits=float(summary.used_credits),
|
|
144
|
+
reset_at=summary.reset_at,
|
|
145
|
+
window_minutes=summary.window_minutes,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def summarize_usage_window(
|
|
150
|
+
usage_rows: Iterable[UsageWindowRow],
|
|
151
|
+
account_map: Mapping[str, Account],
|
|
152
|
+
window: str,
|
|
153
|
+
) -> UsageWindowSummary:
|
|
154
|
+
total_capacity = 0.0
|
|
155
|
+
total_used = 0.0
|
|
156
|
+
reset_candidates: list[int] = []
|
|
157
|
+
window_minutes_values: set[int] = set()
|
|
158
|
+
|
|
159
|
+
for row in usage_rows:
|
|
160
|
+
if row.reset_at is not None:
|
|
161
|
+
reset_candidates.append(row.reset_at)
|
|
162
|
+
if row.window_minutes is not None and row.window_minutes > 0:
|
|
163
|
+
window_minutes_values.add(row.window_minutes)
|
|
164
|
+
account = account_map.get(row.account_id)
|
|
165
|
+
capacity = capacity_for_plan(account.plan_type if account else None, window)
|
|
166
|
+
if row.used_percent is None or capacity is None:
|
|
167
|
+
continue
|
|
168
|
+
total_capacity += capacity
|
|
169
|
+
total_used += (capacity * float(row.used_percent)) / 100.0
|
|
170
|
+
|
|
171
|
+
window_minutes = _resolve_window_minutes(window, window_minutes_values)
|
|
172
|
+
|
|
173
|
+
overall = None
|
|
174
|
+
if total_capacity > 0:
|
|
175
|
+
overall = (total_used / total_capacity) * 100.0
|
|
176
|
+
reset_at_value = min(reset_candidates) if reset_candidates else None
|
|
177
|
+
return UsageWindowSummary(
|
|
178
|
+
used_percent=float(overall) if overall is not None else None,
|
|
179
|
+
capacity_credits=float(total_capacity),
|
|
180
|
+
used_credits=float(total_used),
|
|
181
|
+
reset_at=reset_at_value,
|
|
182
|
+
window_minutes=window_minutes,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def capacity_for_plan(plan_type: str | None, window: str) -> float | None:
|
|
187
|
+
normalized = normalize_account_plan_type(plan_type)
|
|
188
|
+
if not normalized:
|
|
189
|
+
return None
|
|
190
|
+
window_key = _normalize_window_key(window)
|
|
191
|
+
if window_key == "primary":
|
|
192
|
+
return PLAN_CAPACITY_CREDITS_PRIMARY.get(normalized)
|
|
193
|
+
if window_key == "secondary":
|
|
194
|
+
return PLAN_CAPACITY_CREDITS_SECONDARY.get(normalized)
|
|
195
|
+
if window_key == "monthly":
|
|
196
|
+
return PLAN_CAPACITY_CREDITS_MONTHLY.get(normalized)
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def default_window_minutes(window: str) -> int | None:
|
|
201
|
+
window_key = _normalize_window_key(window)
|
|
202
|
+
if window_key == "primary":
|
|
203
|
+
return DEFAULT_WINDOW_MINUTES_PRIMARY
|
|
204
|
+
if window_key == "secondary":
|
|
205
|
+
return DEFAULT_WINDOW_MINUTES_SECONDARY
|
|
206
|
+
if window_key == "monthly":
|
|
207
|
+
return DEFAULT_WINDOW_MINUTES_MONTHLY
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def resolve_window_minutes(window: str, rows: Iterable[UsageWindowRow]) -> int | None:
|
|
212
|
+
values = {row.window_minutes for row in rows if row.window_minutes is not None and row.window_minutes > 0}
|
|
213
|
+
return _resolve_window_minutes(window, values)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def is_weekly_window_minutes(window_minutes: int | None) -> bool:
|
|
217
|
+
if window_minutes is None:
|
|
218
|
+
return False
|
|
219
|
+
secondary_default = default_window_minutes("secondary")
|
|
220
|
+
if secondary_default is None:
|
|
221
|
+
return False
|
|
222
|
+
return window_minutes == secondary_default
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def is_primary_window_minutes(window_minutes: int | None) -> bool:
|
|
226
|
+
if window_minutes is None:
|
|
227
|
+
return False
|
|
228
|
+
primary_default = default_window_minutes("primary")
|
|
229
|
+
if primary_default is None:
|
|
230
|
+
return False
|
|
231
|
+
return window_minutes == primary_default
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def should_use_weekly_primary(
|
|
235
|
+
primary_row: UsageWindowRow,
|
|
236
|
+
secondary_row: UsageWindowRow | None,
|
|
237
|
+
) -> bool:
|
|
238
|
+
if not is_weekly_window_minutes(primary_row.window_minutes):
|
|
239
|
+
return False
|
|
240
|
+
if secondary_row is None:
|
|
241
|
+
return True
|
|
242
|
+
return _should_prefer_primary_row(primary_row, secondary_row)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def expire_elapsed_window_rows(
|
|
246
|
+
rows: Iterable[UsageWindowRow],
|
|
247
|
+
*,
|
|
248
|
+
now_epoch: int,
|
|
249
|
+
) -> list[UsageWindowRow]:
|
|
250
|
+
# A row whose reset_at has elapsed describes an expired window: upstream
|
|
251
|
+
# may have stopped reporting that window entirely, in which case the row
|
|
252
|
+
# is never rewritten. Treat it as a reset window (0% used, no reset) so
|
|
253
|
+
# pooled summaries and availability never freeze on stale samples.
|
|
254
|
+
expired: list[UsageWindowRow] = []
|
|
255
|
+
for row in rows:
|
|
256
|
+
if row.reset_at is not None and row.reset_at <= now_epoch:
|
|
257
|
+
expired.append(replace(row, used_percent=0.0, reset_at=None))
|
|
258
|
+
else:
|
|
259
|
+
expired.append(row)
|
|
260
|
+
return expired
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def normalize_weekly_only_rows(
|
|
264
|
+
primary_rows: Iterable[UsageWindowRow],
|
|
265
|
+
secondary_rows: Iterable[UsageWindowRow],
|
|
266
|
+
) -> tuple[list[UsageWindowRow], list[UsageWindowRow]]:
|
|
267
|
+
# Some plans (notably free) can report only one weekly window in the
|
|
268
|
+
# primary slot. Re-map those rows into secondary so downstream 5h/7d
|
|
269
|
+
# consumers operate on consistent semantics.
|
|
270
|
+
primary_by_account = {row.account_id: row for row in primary_rows}
|
|
271
|
+
normalized_secondary_by_account = {row.account_id: row for row in secondary_rows}
|
|
272
|
+
|
|
273
|
+
normalized_primary: list[UsageWindowRow] = []
|
|
274
|
+
|
|
275
|
+
for account_id, primary_row in primary_by_account.items():
|
|
276
|
+
if is_weekly_window_minutes(primary_row.window_minutes):
|
|
277
|
+
secondary_row = normalized_secondary_by_account.get(account_id)
|
|
278
|
+
if should_use_weekly_primary(primary_row, secondary_row):
|
|
279
|
+
normalized_secondary_by_account[account_id] = primary_row
|
|
280
|
+
continue
|
|
281
|
+
normalized_primary.append(primary_row)
|
|
282
|
+
|
|
283
|
+
return normalized_primary, list(normalized_secondary_by_account.values())
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _has_real_quota_metadata(row: UsageWindowRow) -> bool:
|
|
287
|
+
"""A row carries real quota metadata when it has a positive window
|
|
288
|
+
duration AND a reset deadline. Used by the weekly-primary remap tiebreak to
|
|
289
|
+
distinguish a real weekly sample from a no-data placeholder."""
|
|
290
|
+
return row.window_minutes is not None and row.window_minutes > 0 and row.reset_at is not None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _is_no_data_placeholder(row: UsageWindowRow) -> bool:
|
|
294
|
+
"""A no-data placeholder is the absence of a measurement, not 0% used.
|
|
295
|
+
|
|
296
|
+
Such rows (no positive window duration AND no reset deadline) are written
|
|
297
|
+
when upstream omits a window slot entirely. Within the data-aware tiebreak
|
|
298
|
+
(same-fetch or indeterminate ordering), a placeholder must not displace a
|
|
299
|
+
row carrying real quota metadata. A newer cross-fetch placeholder can still
|
|
300
|
+
win via fetch ordering, which is intentional — the cross-fetch winner is
|
|
301
|
+
rendered per existing placeholder rules.
|
|
302
|
+
"""
|
|
303
|
+
has_window = row.window_minutes is not None and row.window_minutes > 0
|
|
304
|
+
return not has_window and row.reset_at is None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _should_prefer_primary_row(primary_row: UsageWindowRow, secondary_row: UsageWindowRow) -> bool:
|
|
308
|
+
primary_recorded_at = _normalize_recorded_at(primary_row.recorded_at)
|
|
309
|
+
secondary_recorded_at = _normalize_recorded_at(secondary_row.recorded_at)
|
|
310
|
+
|
|
311
|
+
# Fetch ordering decides ONLY when both timestamps are present and differ
|
|
312
|
+
# by more than the sibling margin. A genuinely later fetch is more
|
|
313
|
+
# authoritative about what upstream currently reports, so the newer row
|
|
314
|
+
# wins — this preserves the pre-fix cross-fetch behavior and avoids
|
|
315
|
+
# freezing a stale real weekly primary over a fresh placeholder from a
|
|
316
|
+
# later fetch. When only one (or neither) timestamp is present we cannot
|
|
317
|
+
# determine fetch ordering, so we fall through to the data-aware tiebreak
|
|
318
|
+
# rather than letting timestamp presence alone decide (a timestamped
|
|
319
|
+
# no-data placeholder must not beat an untimestamped real weekly row).
|
|
320
|
+
if primary_recorded_at is not None and secondary_recorded_at is not None:
|
|
321
|
+
if primary_recorded_at != secondary_recorded_at:
|
|
322
|
+
delta_seconds = abs((primary_recorded_at - secondary_recorded_at).total_seconds())
|
|
323
|
+
if delta_seconds > SIBLING_FETCH_MARGIN_SECONDS:
|
|
324
|
+
return primary_recorded_at > secondary_recorded_at
|
|
325
|
+
# Within the sibling margin: same fetch, fall through to the
|
|
326
|
+
# data-aware tiebreak below so sub-second write skew cannot flip
|
|
327
|
+
# the winner per refresh.
|
|
328
|
+
|
|
329
|
+
# Same-fetch, or one/both timestamps unavailable, or rows equidistant
|
|
330
|
+
# within the margin: a row carrying real quota metadata MUST win over a
|
|
331
|
+
# no-data placeholder. A placeholder is the absence of a measurement, not
|
|
332
|
+
# 0% used, so it must never displace a real weekly sample (otherwise the
|
|
333
|
+
# dashboard jumps to 100% remaining every refresh).
|
|
334
|
+
primary_has_real = _has_real_quota_metadata(primary_row)
|
|
335
|
+
if primary_has_real and _is_no_data_placeholder(secondary_row):
|
|
336
|
+
return True
|
|
337
|
+
if _has_real_quota_metadata(secondary_row) and _is_no_data_placeholder(primary_row):
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
# Both real or both placeholder (same fetch / indeterminate ordering):
|
|
341
|
+
# reset-at precedence, then the stable weekly-primary default.
|
|
342
|
+
if primary_row.reset_at is not None and secondary_row.reset_at is not None:
|
|
343
|
+
if primary_row.reset_at != secondary_row.reset_at:
|
|
344
|
+
return primary_row.reset_at > secondary_row.reset_at
|
|
345
|
+
elif primary_row.reset_at is not None:
|
|
346
|
+
return True
|
|
347
|
+
elif secondary_row.reset_at is not None:
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
# Keep weekly-only semantics stable when no discriminator is available.
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _normalize_recorded_at(value: datetime | None) -> datetime | None:
|
|
355
|
+
if value is None:
|
|
356
|
+
return None
|
|
357
|
+
if value.tzinfo is None:
|
|
358
|
+
return value
|
|
359
|
+
return value.astimezone(timezone.utc).replace(tzinfo=None)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _resolve_window_minutes(window: str, values: set[int]) -> int | None:
|
|
363
|
+
if not values:
|
|
364
|
+
return default_window_minutes(window)
|
|
365
|
+
if len(values) == 1:
|
|
366
|
+
return next(iter(values))
|
|
367
|
+
default = default_window_minutes(window)
|
|
368
|
+
if default is not None:
|
|
369
|
+
return default
|
|
370
|
+
return min(values)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def parse_usage_summary(
|
|
374
|
+
primary_window: UsageWindowSummary,
|
|
375
|
+
secondary_window: UsageWindowSummary | None,
|
|
376
|
+
monthly_window: UsageWindowSummary | None,
|
|
377
|
+
cost: UsageCostSummary,
|
|
378
|
+
metrics: UsageMetricsSummary | None = None,
|
|
379
|
+
) -> UsageSummaryPayload:
|
|
380
|
+
primary = normalize_usage_window(primary_window)
|
|
381
|
+
secondary = None
|
|
382
|
+
if secondary_window is not None:
|
|
383
|
+
secondary = normalize_usage_window(secondary_window)
|
|
384
|
+
monthly = None
|
|
385
|
+
if monthly_window is not None:
|
|
386
|
+
monthly = normalize_usage_window(monthly_window)
|
|
387
|
+
return UsageSummaryPayload(
|
|
388
|
+
primary_window=primary,
|
|
389
|
+
secondary_window=secondary,
|
|
390
|
+
monthly_window=monthly,
|
|
391
|
+
cost=cost,
|
|
392
|
+
metrics=metrics,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
async def usage_summary() -> UsageSummaryPayload:
|
|
397
|
+
return UsageSummaryPayload(
|
|
398
|
+
primary_window=_empty_window(window_minutes=None),
|
|
399
|
+
secondary_window=None,
|
|
400
|
+
monthly_window=None,
|
|
401
|
+
cost=_empty_cost(),
|
|
402
|
+
metrics=None,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
async def usage_history(hours: int) -> UsageHistoryPayload:
|
|
407
|
+
return UsageHistoryPayload(window_hours=hours, accounts=[])
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
RISK_WARNING = 0.60
|
|
7
|
+
RISK_DANGER = 0.80
|
|
8
|
+
RISK_CRITICAL = 0.95
|
|
9
|
+
DEFAULT_ALPHA = 0.4
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EWMAState:
|
|
14
|
+
rate: float | None
|
|
15
|
+
last_used_percent: float
|
|
16
|
+
last_timestamp: float
|
|
17
|
+
last_reset_at: int | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def ewma_update(
|
|
21
|
+
state: EWMAState | None,
|
|
22
|
+
used_percent: float,
|
|
23
|
+
timestamp: float,
|
|
24
|
+
alpha: float = DEFAULT_ALPHA,
|
|
25
|
+
reset_at: int | None = None,
|
|
26
|
+
) -> EWMAState:
|
|
27
|
+
if state is None:
|
|
28
|
+
return EWMAState(
|
|
29
|
+
rate=None,
|
|
30
|
+
last_used_percent=used_percent,
|
|
31
|
+
last_timestamp=timestamp,
|
|
32
|
+
last_reset_at=reset_at,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
dt = timestamp - state.last_timestamp
|
|
36
|
+
if dt == 0:
|
|
37
|
+
return state
|
|
38
|
+
|
|
39
|
+
window_changed = reset_at is not None and state.last_reset_at is not None and reset_at != state.last_reset_at
|
|
40
|
+
|
|
41
|
+
drop = state.last_used_percent - used_percent
|
|
42
|
+
if drop > 0 or window_changed:
|
|
43
|
+
return EWMAState(
|
|
44
|
+
rate=None,
|
|
45
|
+
last_used_percent=used_percent,
|
|
46
|
+
last_timestamp=timestamp,
|
|
47
|
+
last_reset_at=reset_at,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
delta_percent = used_percent - state.last_used_percent
|
|
51
|
+
raw_rate = max(delta_percent / dt, 0.0)
|
|
52
|
+
rate = raw_rate if state.rate is None else (alpha * raw_rate) + ((1 - alpha) * state.rate)
|
|
53
|
+
|
|
54
|
+
return EWMAState(
|
|
55
|
+
rate=rate,
|
|
56
|
+
last_used_percent=used_percent,
|
|
57
|
+
last_timestamp=timestamp,
|
|
58
|
+
last_reset_at=reset_at,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def compute_burn_rate(
|
|
63
|
+
current_rate: float,
|
|
64
|
+
remaining_percent: float,
|
|
65
|
+
seconds_until_reset: float,
|
|
66
|
+
) -> float:
|
|
67
|
+
if current_rate == 0 or seconds_until_reset == 0:
|
|
68
|
+
return 0.0
|
|
69
|
+
|
|
70
|
+
sustainable_rate = remaining_percent / seconds_until_reset
|
|
71
|
+
if sustainable_rate == 0:
|
|
72
|
+
return 0.0
|
|
73
|
+
return current_rate / sustainable_rate
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def compute_depletion_risk(
|
|
77
|
+
used_percent: float,
|
|
78
|
+
rate_per_second: float,
|
|
79
|
+
seconds_until_reset: float,
|
|
80
|
+
) -> float:
|
|
81
|
+
effective_rate = max(0.0, rate_per_second)
|
|
82
|
+
projected = used_percent + (effective_rate * seconds_until_reset)
|
|
83
|
+
return min(projected / 100.0, 1.0)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def compute_safe_usage_percent(
|
|
87
|
+
seconds_elapsed: float,
|
|
88
|
+
total_window_seconds: float,
|
|
89
|
+
) -> float:
|
|
90
|
+
if total_window_seconds == 0:
|
|
91
|
+
return 0.0
|
|
92
|
+
|
|
93
|
+
progress = seconds_elapsed / total_window_seconds
|
|
94
|
+
clamped_progress = min(max(progress, 0.0), 1.0)
|
|
95
|
+
return clamped_progress * 100.0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def classify_risk(risk: float) -> Literal["safe", "warning", "danger", "critical"]:
|
|
99
|
+
if risk >= RISK_CRITICAL:
|
|
100
|
+
return "critical"
|
|
101
|
+
if risk >= RISK_DANGER:
|
|
102
|
+
return "danger"
|
|
103
|
+
if risk >= RISK_WARNING:
|
|
104
|
+
return "warning"
|
|
105
|
+
return "safe"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def aggregate_risks(risks: list[float]) -> float:
|
|
109
|
+
return max(risks) if risks else 0.0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Protocol
|
|
4
|
+
|
|
5
|
+
from app.core.usage.live_snapshots import LiveRateLimitSnapshot
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LiveUsagePublisher(Protocol):
|
|
9
|
+
def __call__(
|
|
10
|
+
self,
|
|
11
|
+
snapshot: LiveRateLimitSnapshot,
|
|
12
|
+
*,
|
|
13
|
+
account_id: str | None = None,
|
|
14
|
+
chatgpt_account_id: str | None = None,
|
|
15
|
+
) -> None: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# The core client layer publishes through this hub so it never imports the
|
|
19
|
+
# module layer; until startup registers an ingestor every publish is a no-op.
|
|
20
|
+
_publisher: Callable[..., None] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register_live_usage_publisher(publisher: LiveUsagePublisher | None) -> None:
|
|
24
|
+
global _publisher
|
|
25
|
+
_publisher = publisher
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def publish_live_usage(
|
|
29
|
+
snapshot: LiveRateLimitSnapshot | None,
|
|
30
|
+
*,
|
|
31
|
+
account_id: str | None = None,
|
|
32
|
+
chatgpt_account_id: str | None = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
if snapshot is None or not snapshot.has_windows:
|
|
35
|
+
return
|
|
36
|
+
if not account_id and not chatgpt_account_id:
|
|
37
|
+
return
|
|
38
|
+
publisher = _publisher
|
|
39
|
+
if publisher is None:
|
|
40
|
+
return
|
|
41
|
+
publisher(snapshot, account_id=account_id, chatgpt_account_id=chatgpt_account_id)
|