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,522 @@
|
|
|
1
|
+
"""Read-side partition/merge primitives for the time-axis usage rollups.
|
|
2
|
+
|
|
3
|
+
The switched read paths (dashboard buckets/activity/top-error, quota-planner
|
|
4
|
+
demand bins, api-key trends) serve folded history from the rollup tables and
|
|
5
|
+
only the un-folded tail from raw ``request_logs``. This module owns the ONE
|
|
6
|
+
partitioning rule they all share, so the boundary arithmetic cannot drift
|
|
7
|
+
apart across consumers:
|
|
8
|
+
|
|
9
|
+
- The rollup segment covers whole grid buckets inside ``[since, until)`` that
|
|
10
|
+
lie below the hourly watermark: ``[ceil_grid(since), min(W, floor_grid(
|
|
11
|
+
until)))``. The watermark is always hour-aligned (and 3600 % 900 == 0), so
|
|
12
|
+
a folded bucket is never split between the rollup segment and the raw tail.
|
|
13
|
+
- The raw windows are the exact complement: a partial leading bucket
|
|
14
|
+
``[since, ceil_grid(since))`` and the tail ``[hi, until)`` (which for a
|
|
15
|
+
fully historical window is a partial trailing bucket). Both are always
|
|
16
|
+
served from raw — if retention has already pruned them, the loss is a
|
|
17
|
+
deterministic ≤1-bucket undercount per unaligned edge, documented as out
|
|
18
|
+
of parity scope (sub-bucket boundary contributions cannot be represented
|
|
19
|
+
in the rollup grid; serving the whole edge bucket instead would
|
|
20
|
+
over-count).
|
|
21
|
+
- Rollup rows and the watermark are read in ONE statement (state LEFT JOIN
|
|
22
|
+
rollup, inherited from ``RequestUsageTimeRollupRepository``): a fold slice
|
|
23
|
+
committing between the rollup read and the raw-tail read can never drop or
|
|
24
|
+
double-count the just-folded window, because raw rows are never deleted by
|
|
25
|
+
the fold and the tail window is derived from the watermark generation the
|
|
26
|
+
rollup rows came from.
|
|
27
|
+
- With no state row or an epoch watermark (pre-backfill, or after the
|
|
28
|
+
operator escape hatch reset) the rollup segment is empty and the raw
|
|
29
|
+
windows collapse to the full ``[since, until)`` — the readers degrade to
|
|
30
|
+
the exact legacy behaviour with no kill switch.
|
|
31
|
+
|
|
32
|
+
The conversation-presence primitives at the bottom follow the same
|
|
33
|
+
partitioning rule but resolve it INSIDE one statement (the watermark read
|
|
34
|
+
from the state row as a scalar subquery in both UNION branches) instead of
|
|
35
|
+
returning raw windows: distinct conversation counts must merge folded ids
|
|
36
|
+
and raw-tail ids in a single snapshot, and the raw complement is phrased as
|
|
37
|
+
two constant ranges ``requested_at < ceil_grid(since) OR requested_at >=
|
|
38
|
+
greatest(ceil_grid(since), least(W, floor_grid(until)))`` — an exact
|
|
39
|
+
complement of the folded buckets for any watermark position that the
|
|
40
|
+
planner serves from the ``requested_at`` index, degrading to the full
|
|
41
|
+
window when the watermark is at the epoch or the state row is missing
|
|
42
|
+
(``coalesce(W, ceil_grid(since))``).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
from collections.abc import Sequence
|
|
48
|
+
from dataclasses import dataclass
|
|
49
|
+
from datetime import datetime, timedelta
|
|
50
|
+
from typing import Any
|
|
51
|
+
|
|
52
|
+
from sqlalchemy import BigInteger, ColumnElement, Integer, Select, and_, cast, func, literal, or_, select, union_all
|
|
53
|
+
from sqlalchemy import cast as sa_cast
|
|
54
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
55
|
+
from sqlalchemy.sql import ColumnExpressionArgument
|
|
56
|
+
from sqlalchemy.sql.selectable import CompoundSelect
|
|
57
|
+
|
|
58
|
+
from app.db.models import (
|
|
59
|
+
AccountUsageRollupState,
|
|
60
|
+
RequestConversationHourlyRollup,
|
|
61
|
+
RequestDemandQuarterRollup,
|
|
62
|
+
RequestLog,
|
|
63
|
+
RequestUsageHourlyRollup,
|
|
64
|
+
)
|
|
65
|
+
from app.modules.accounts.usage_time_rollup import (
|
|
66
|
+
_STATE_ROW_ID,
|
|
67
|
+
HOURLY_BUCKET_SECONDS,
|
|
68
|
+
QUARTER_SLOT_SECONDS,
|
|
69
|
+
WARMUP_REQUEST_KINDS,
|
|
70
|
+
HourlyErrorRollupRow,
|
|
71
|
+
HourlyUsageRollupRow,
|
|
72
|
+
QuarterDemandRollupRow,
|
|
73
|
+
RequestUsageTimeRollupRepository,
|
|
74
|
+
_requested_at_epoch_bucket_expr,
|
|
75
|
+
conversation_id_expr,
|
|
76
|
+
epoch_seconds,
|
|
77
|
+
)
|
|
78
|
+
from app.modules.quota_planner.logic import (
|
|
79
|
+
DEMAND_CACHED_INPUT_TOKEN_WEIGHT,
|
|
80
|
+
DEMAND_OUTPUT_TOKEN_WEIGHT,
|
|
81
|
+
DEMAND_TOKENS_PER_UNIT,
|
|
82
|
+
DEMAND_UNITS_PER_COST_USD,
|
|
83
|
+
DEMAND_UNITS_PER_REQUEST,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
_EPOCH = datetime(1970, 1, 1)
|
|
87
|
+
|
|
88
|
+
# A raw request_logs window: half-open [start, end), end=None meaning +inf.
|
|
89
|
+
RawWindow = tuple[datetime, datetime | None]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def epoch_to_datetime(epoch: int) -> datetime:
|
|
93
|
+
"""Unix epoch seconds to the naive-UTC datetime domain of requested_at."""
|
|
94
|
+
return _EPOCH + timedelta(seconds=epoch)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def floor_to_grid(value: datetime, grid_seconds: int) -> datetime:
|
|
98
|
+
return epoch_to_datetime(epoch_seconds(value) // grid_seconds * grid_seconds)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def ceil_to_grid(value: datetime, grid_seconds: int) -> datetime:
|
|
102
|
+
floored = floor_to_grid(value, grid_seconds)
|
|
103
|
+
return floored if floored == value else floored + timedelta(seconds=grid_seconds)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _partition_raw_windows(
|
|
107
|
+
since: datetime,
|
|
108
|
+
until: datetime | None,
|
|
109
|
+
watermark: datetime | None,
|
|
110
|
+
grid_seconds: int,
|
|
111
|
+
) -> tuple[int | None, list[RawWindow]]:
|
|
112
|
+
"""Split ``[since, until)`` into the folded bound and raw complement.
|
|
113
|
+
|
|
114
|
+
Returns ``(folded_until_epoch, raw_windows)``: rollup rows with
|
|
115
|
+
``epoch < folded_until_epoch`` are authoritative (``None`` = use no
|
|
116
|
+
rollup rows), everything else must come from raw.
|
|
117
|
+
"""
|
|
118
|
+
if watermark is None:
|
|
119
|
+
return None, [(since, until)]
|
|
120
|
+
lo = ceil_to_grid(since, grid_seconds)
|
|
121
|
+
hi = watermark if until is None else min(watermark, floor_to_grid(until, grid_seconds))
|
|
122
|
+
if hi <= lo:
|
|
123
|
+
return None, [(since, until)]
|
|
124
|
+
raw_windows: list[RawWindow] = []
|
|
125
|
+
if since < lo:
|
|
126
|
+
raw_windows.append((since, lo))
|
|
127
|
+
if until is None or hi < until:
|
|
128
|
+
raw_windows.append((hi, until))
|
|
129
|
+
return epoch_seconds(hi), raw_windows
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def _read_window(reader, epoch_attr: str, since: datetime, until: datetime | None, grid_seconds: int, filters):
|
|
133
|
+
lo_epoch = epoch_seconds(ceil_to_grid(since, grid_seconds))
|
|
134
|
+
until_epoch = None if until is None else epoch_seconds(floor_to_grid(until, grid_seconds))
|
|
135
|
+
rows, watermark = await reader(since_epoch=lo_epoch, until_epoch=until_epoch, filters=filters)
|
|
136
|
+
folded_until_epoch, raw_windows = _partition_raw_windows(since, until, watermark, grid_seconds)
|
|
137
|
+
if folded_until_epoch is None:
|
|
138
|
+
return [], raw_windows
|
|
139
|
+
return [row for row in rows if getattr(row, epoch_attr) < folded_until_epoch], raw_windows
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def read_hourly_window(
|
|
143
|
+
session: AsyncSession,
|
|
144
|
+
since: datetime,
|
|
145
|
+
until: datetime | None = None,
|
|
146
|
+
*,
|
|
147
|
+
filters: Sequence[ColumnElement[bool]] = (),
|
|
148
|
+
) -> tuple[list[HourlyUsageRollupRow], list[RawWindow]]:
|
|
149
|
+
repo = RequestUsageTimeRollupRepository(session)
|
|
150
|
+
return await _read_window(repo.read_hourly, "bucket_epoch", since, until, HOURLY_BUCKET_SECONDS, filters)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def read_errors_window(
|
|
154
|
+
session: AsyncSession,
|
|
155
|
+
since: datetime,
|
|
156
|
+
until: datetime | None = None,
|
|
157
|
+
*,
|
|
158
|
+
filters: Sequence[ColumnElement[bool]] = (),
|
|
159
|
+
) -> tuple[list[HourlyErrorRollupRow], list[RawWindow]]:
|
|
160
|
+
repo = RequestUsageTimeRollupRepository(session)
|
|
161
|
+
return await _read_window(repo.read_errors, "bucket_epoch", since, until, HOURLY_BUCKET_SECONDS, filters)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def read_demand_window(
|
|
165
|
+
session: AsyncSession,
|
|
166
|
+
since: datetime,
|
|
167
|
+
until: datetime | None = None,
|
|
168
|
+
*,
|
|
169
|
+
filters: Sequence[ColumnElement[bool]] = (),
|
|
170
|
+
) -> tuple[list[QuarterDemandRollupRow], list[RawWindow]]:
|
|
171
|
+
repo = RequestUsageTimeRollupRepository(session)
|
|
172
|
+
return await _read_window(repo.read_demand, "slot_epoch", since, until, QUARTER_SLOT_SECONDS, filters)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def sum_demand_window(
|
|
176
|
+
session: AsyncSession,
|
|
177
|
+
since: datetime,
|
|
178
|
+
until: datetime | None = None,
|
|
179
|
+
*,
|
|
180
|
+
filters: Sequence[ColumnElement[bool]] = (),
|
|
181
|
+
) -> tuple[int, list[RawWindow]]:
|
|
182
|
+
"""Watermark-consistent SUM(request_count) over the demand rollup.
|
|
183
|
+
|
|
184
|
+
Same partitioning rule as the row readers, aggregated in SQL so counting
|
|
185
|
+
a long window does not materialize every demand-grain row. The watermark
|
|
186
|
+
and the folded sum come from ONE statement (state LEFT JOIN demand, with
|
|
187
|
+
the slot upper bound expressed against the state row's own watermark
|
|
188
|
+
epoch), so an escape-hatch reset committing concurrently can never pair
|
|
189
|
+
an old watermark with already-truncated rollups. With no watermark the
|
|
190
|
+
sum is 0 and the raw windows cover the full range — the caller degrades
|
|
191
|
+
to the exact legacy raw read.
|
|
192
|
+
"""
|
|
193
|
+
lo_epoch = epoch_seconds(ceil_to_grid(since, QUARTER_SLOT_SECONDS))
|
|
194
|
+
if session.get_bind().dialect.name == "postgresql":
|
|
195
|
+
watermark_epoch = sa_cast(func.extract("epoch", AccountUsageRollupState.hourly_folded_through), BigInteger)
|
|
196
|
+
else:
|
|
197
|
+
watermark_epoch = sa_cast(func.strftime("%s", AccountUsageRollupState.hourly_folded_through), Integer)
|
|
198
|
+
join_conditions = [
|
|
199
|
+
*filters,
|
|
200
|
+
RequestDemandQuarterRollup.slot_epoch >= lo_epoch,
|
|
201
|
+
RequestDemandQuarterRollup.slot_epoch < watermark_epoch,
|
|
202
|
+
]
|
|
203
|
+
if until is not None:
|
|
204
|
+
join_conditions.append(
|
|
205
|
+
RequestDemandQuarterRollup.slot_epoch < epoch_seconds(floor_to_grid(until, QUARTER_SLOT_SECONDS))
|
|
206
|
+
)
|
|
207
|
+
stmt = (
|
|
208
|
+
select(
|
|
209
|
+
AccountUsageRollupState.hourly_folded_through,
|
|
210
|
+
func.coalesce(func.sum(RequestDemandQuarterRollup.request_count), 0),
|
|
211
|
+
)
|
|
212
|
+
.select_from(AccountUsageRollupState)
|
|
213
|
+
.outerjoin(RequestDemandQuarterRollup, and_(*join_conditions))
|
|
214
|
+
.where(AccountUsageRollupState.id == _STATE_ROW_ID)
|
|
215
|
+
.group_by(AccountUsageRollupState.hourly_folded_through)
|
|
216
|
+
)
|
|
217
|
+
row = (await session.execute(stmt)).first()
|
|
218
|
+
if row is None:
|
|
219
|
+
return 0, [(since, until)]
|
|
220
|
+
watermark, folded_total = row[0], int(row[1])
|
|
221
|
+
folded_until_epoch, raw_windows = _partition_raw_windows(since, until, watermark, QUARTER_SLOT_SECONDS)
|
|
222
|
+
if folded_until_epoch is None:
|
|
223
|
+
return 0, raw_windows
|
|
224
|
+
return folded_total, raw_windows
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass(frozen=True, slots=True)
|
|
228
|
+
class DemandSlotUnitsRow:
|
|
229
|
+
"""Demand units already summed per ``(slot_epoch, request_kind)``."""
|
|
230
|
+
|
|
231
|
+
slot_epoch: int
|
|
232
|
+
request_kind: str
|
|
233
|
+
demand_units: float
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def demand_units_sql_expr(
|
|
237
|
+
*,
|
|
238
|
+
dialect: str,
|
|
239
|
+
input_tokens: ColumnExpressionArgument[Any],
|
|
240
|
+
cached_input_tokens: ColumnExpressionArgument[Any],
|
|
241
|
+
output_tokens: ColumnExpressionArgument[Any],
|
|
242
|
+
cost_usd: ColumnExpressionArgument[Any],
|
|
243
|
+
request_count: ColumnExpressionArgument[Any],
|
|
244
|
+
) -> ColumnElement[Any]:
|
|
245
|
+
"""``_bin_demand_units`` compiled to SQL for ONE legacy-grain row.
|
|
246
|
+
|
|
247
|
+
``max(token_units, cost_units, request_units)`` per row, with the same
|
|
248
|
+
``max(0, ·)`` clamps; callers SUM this over the rows of a slot. Postgres
|
|
249
|
+
spells the scalar maximum ``GREATEST``; SQLite's multi-argument ``MAX``
|
|
250
|
+
is the scalar form.
|
|
251
|
+
"""
|
|
252
|
+
scalar_max = func.greatest if dialect == "postgresql" else func.max
|
|
253
|
+
zero = literal(0)
|
|
254
|
+
token_units = (
|
|
255
|
+
scalar_max(input_tokens, zero)
|
|
256
|
+
+ DEMAND_CACHED_INPUT_TOKEN_WEIGHT * scalar_max(cached_input_tokens, zero)
|
|
257
|
+
+ DEMAND_OUTPUT_TOKEN_WEIGHT * scalar_max(output_tokens, zero)
|
|
258
|
+
) / DEMAND_TOKENS_PER_UNIT
|
|
259
|
+
cost_units = scalar_max(cost_usd, literal(0.0)) * DEMAND_UNITS_PER_COST_USD
|
|
260
|
+
request_units = scalar_max(request_count, zero) * DEMAND_UNITS_PER_REQUEST
|
|
261
|
+
return scalar_max(token_units, cost_units, request_units)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
async def read_demand_slot_units_window(
|
|
265
|
+
session: AsyncSession,
|
|
266
|
+
since: datetime,
|
|
267
|
+
until: datetime | None = None,
|
|
268
|
+
*,
|
|
269
|
+
filters: Sequence[ColumnElement[bool]] = (),
|
|
270
|
+
) -> tuple[list[DemandSlotUnitsRow], list[RawWindow]]:
|
|
271
|
+
"""Watermark-consistent per-slot demand units over the demand rollup.
|
|
272
|
+
|
|
273
|
+
Same partitioning rule as ``read_demand_window`` but reduced in SQL: the
|
|
274
|
+
per-row ``_bin_demand_units`` max is applied at the rollup's legacy grain
|
|
275
|
+
and summed per ``(slot_epoch, request_kind)`` before crossing the wire,
|
|
276
|
+
so a 28-day planning window returns a few thousand rows instead of one
|
|
277
|
+
row per (account, key, model, effort, kind, status) grain. Watermark and
|
|
278
|
+
folded rows come from ONE statement (state LEFT JOIN demand bounded by
|
|
279
|
+
the state row's own watermark epoch), exactly like ``sum_demand_window``.
|
|
280
|
+
"""
|
|
281
|
+
lo_epoch = epoch_seconds(ceil_to_grid(since, QUARTER_SLOT_SECONDS))
|
|
282
|
+
dialect = session.get_bind().dialect.name
|
|
283
|
+
if dialect == "postgresql":
|
|
284
|
+
watermark_epoch = sa_cast(func.extract("epoch", AccountUsageRollupState.hourly_folded_through), BigInteger)
|
|
285
|
+
else:
|
|
286
|
+
watermark_epoch = sa_cast(func.strftime("%s", AccountUsageRollupState.hourly_folded_through), Integer)
|
|
287
|
+
join_conditions = [
|
|
288
|
+
*filters,
|
|
289
|
+
RequestDemandQuarterRollup.slot_epoch >= lo_epoch,
|
|
290
|
+
RequestDemandQuarterRollup.slot_epoch < watermark_epoch,
|
|
291
|
+
]
|
|
292
|
+
if until is not None:
|
|
293
|
+
join_conditions.append(
|
|
294
|
+
RequestDemandQuarterRollup.slot_epoch < epoch_seconds(floor_to_grid(until, QUARTER_SLOT_SECONDS))
|
|
295
|
+
)
|
|
296
|
+
units_expr = demand_units_sql_expr(
|
|
297
|
+
dialect=dialect,
|
|
298
|
+
input_tokens=RequestDemandQuarterRollup.input_tokens,
|
|
299
|
+
cached_input_tokens=RequestDemandQuarterRollup.cached_input_tokens,
|
|
300
|
+
output_tokens=RequestDemandQuarterRollup.output_or_reasoning_tokens,
|
|
301
|
+
cost_usd=RequestDemandQuarterRollup.cost_usd,
|
|
302
|
+
request_count=RequestDemandQuarterRollup.request_count,
|
|
303
|
+
)
|
|
304
|
+
stmt = (
|
|
305
|
+
select(
|
|
306
|
+
AccountUsageRollupState.hourly_folded_through,
|
|
307
|
+
RequestDemandQuarterRollup.slot_epoch,
|
|
308
|
+
RequestDemandQuarterRollup.request_kind,
|
|
309
|
+
func.sum(units_expr),
|
|
310
|
+
)
|
|
311
|
+
.select_from(AccountUsageRollupState)
|
|
312
|
+
.outerjoin(RequestDemandQuarterRollup, and_(*join_conditions))
|
|
313
|
+
.where(AccountUsageRollupState.id == _STATE_ROW_ID)
|
|
314
|
+
.group_by(
|
|
315
|
+
AccountUsageRollupState.hourly_folded_through,
|
|
316
|
+
RequestDemandQuarterRollup.slot_epoch,
|
|
317
|
+
RequestDemandQuarterRollup.request_kind,
|
|
318
|
+
)
|
|
319
|
+
.order_by(RequestDemandQuarterRollup.slot_epoch)
|
|
320
|
+
)
|
|
321
|
+
rows = (await session.execute(stmt)).all()
|
|
322
|
+
if not rows:
|
|
323
|
+
return [], [(since, until)]
|
|
324
|
+
watermark = rows[0][0]
|
|
325
|
+
folded_until_epoch, raw_windows = _partition_raw_windows(since, until, watermark, QUARTER_SLOT_SECONDS)
|
|
326
|
+
if folded_until_epoch is None:
|
|
327
|
+
return [], raw_windows
|
|
328
|
+
return [
|
|
329
|
+
DemandSlotUnitsRow(slot_epoch=int(row[1]), request_kind=row[2], demand_units=float(row[3] or 0.0))
|
|
330
|
+
for row in rows
|
|
331
|
+
if row[1] is not None
|
|
332
|
+
], raw_windows
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def raw_windows_clause(windows: Sequence[RawWindow]) -> ColumnElement[bool]:
|
|
336
|
+
"""OR of half-open requested_at windows; callers skip raw entirely when
|
|
337
|
+
``windows`` is empty instead of emitting a degenerate clause."""
|
|
338
|
+
clauses: list[ColumnElement[bool]] = []
|
|
339
|
+
for start, end in windows:
|
|
340
|
+
if end is None:
|
|
341
|
+
clauses.append(RequestLog.requested_at >= start)
|
|
342
|
+
else:
|
|
343
|
+
clauses.append(and_(RequestLog.requested_at >= start, RequestLog.requested_at < end))
|
|
344
|
+
if not clauses:
|
|
345
|
+
raise ValueError("raw_windows_clause requires at least one window")
|
|
346
|
+
return or_(*clauses)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# --- Conversation presence primitives -------------------------------------
|
|
350
|
+
#
|
|
351
|
+
# Distinct conversation counts are not additive across the fold boundary, so
|
|
352
|
+
# the conversation readers merge the folded ids and the raw-tail ids inside
|
|
353
|
+
# ONE statement. The watermark is read from the state row as an uncorrelated
|
|
354
|
+
# scalar subquery in both UNION branches: the statement still has a single
|
|
355
|
+
# snapshot (a concurrent fold slice or an operator escape-hatch reset can
|
|
356
|
+
# never split the watermark generation between the folded segment and the
|
|
357
|
+
# raw complement), while the planner evaluates the subquery once and can use
|
|
358
|
+
# it as a constant bound on `requested_at` (PostgreSQL InitPlan parameter,
|
|
359
|
+
# SQLite scalar subquery), so the raw branch is a bounded index range instead
|
|
360
|
+
# of a per-row filter over a joined column. `request_count` rides along for
|
|
361
|
+
# the additive conversation-request totals (each raw row contributes 1).
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _conversation_watermark_subquery() -> ColumnElement:
|
|
365
|
+
"""The conversation watermark (naive-UTC), NULL when the state row is
|
|
366
|
+
missing, as a scalar subquery evaluated once per statement."""
|
|
367
|
+
return (
|
|
368
|
+
select(AccountUsageRollupState.conversation_folded_through)
|
|
369
|
+
.where(AccountUsageRollupState.id == _STATE_ROW_ID)
|
|
370
|
+
.scalar_subquery()
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _conversation_watermark_epoch_subquery(session: AsyncSession) -> ColumnElement:
|
|
375
|
+
"""Epoch seconds of the conversation watermark (whole hours, so the
|
|
376
|
+
conversion is exact), dialect-split like the bucket expressions."""
|
|
377
|
+
column = AccountUsageRollupState.conversation_folded_through
|
|
378
|
+
if session.get_bind().dialect.name == "postgresql":
|
|
379
|
+
epoch = cast(func.floor(func.extract("epoch", column)), BigInteger)
|
|
380
|
+
else:
|
|
381
|
+
epoch = cast(func.strftime("%s", column), Integer)
|
|
382
|
+
return select(epoch).where(AccountUsageRollupState.id == _STATE_ROW_ID).scalar_subquery()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _least_fn(session: AsyncSession):
|
|
386
|
+
# SQLite's two-argument min() scalar function is its least().
|
|
387
|
+
return func.least if session.get_bind().dialect.name == "postgresql" else func.min
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _greatest_fn(session: AsyncSession):
|
|
391
|
+
# SQLite's two-argument max() scalar function is its greatest().
|
|
392
|
+
return func.greatest if session.get_bind().dialect.name == "postgresql" else func.max
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _conversation_folded_select(
|
|
396
|
+
session: AsyncSession,
|
|
397
|
+
since: datetime,
|
|
398
|
+
until: datetime | None,
|
|
399
|
+
*,
|
|
400
|
+
include_deleted: bool,
|
|
401
|
+
display_bucket_seconds: int | None,
|
|
402
|
+
) -> Select:
|
|
403
|
+
rollup = RequestConversationHourlyRollup
|
|
404
|
+
# `bucket_epoch < NULL` is never true, so a missing state row yields an
|
|
405
|
+
# empty folded segment (the raw branch then covers the whole window).
|
|
406
|
+
conditions: list[ColumnElement[bool]] = [
|
|
407
|
+
rollup.bucket_epoch >= epoch_seconds(ceil_to_grid(since, HOURLY_BUCKET_SECONDS)),
|
|
408
|
+
rollup.bucket_epoch < _conversation_watermark_epoch_subquery(session),
|
|
409
|
+
]
|
|
410
|
+
if until is not None:
|
|
411
|
+
conditions.append(rollup.bucket_epoch < epoch_seconds(floor_to_grid(until, HOURLY_BUCKET_SECONDS)))
|
|
412
|
+
if not include_deleted:
|
|
413
|
+
conditions.append(rollup.is_deleted.is_(False))
|
|
414
|
+
columns: list = [rollup.conversation_id.label("cid"), rollup.request_count.label("request_count")]
|
|
415
|
+
if display_bucket_seconds is not None:
|
|
416
|
+
# Dialect-split integer flooring: SQLAlchemy renders `/` as true
|
|
417
|
+
# division (NUMERIC on PostgreSQL, where a bigint cast then ROUNDS),
|
|
418
|
+
# so mirror the raw readers' bucket arithmetic instead.
|
|
419
|
+
if session.get_bind().dialect.name == "postgresql":
|
|
420
|
+
display = cast(
|
|
421
|
+
func.floor(rollup.bucket_epoch / display_bucket_seconds) * display_bucket_seconds, BigInteger
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
display = cast(rollup.bucket_epoch / display_bucket_seconds, Integer) * display_bucket_seconds
|
|
425
|
+
columns.insert(0, display.label("bucket_epoch"))
|
|
426
|
+
return select(*columns).where(and_(*conditions))
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _conversation_tail_start(session: AsyncSession, lo: datetime, hi: datetime | None) -> ColumnElement:
|
|
430
|
+
"""Start of the un-folded raw tail, ``greatest(lo, least(coalesce(W, lo),
|
|
431
|
+
hi))``: the watermark clamped into the folded range ``[lo, hi)``.
|
|
432
|
+
|
|
433
|
+
``W`` is the uncorrelated scalar subquery of the state row and ``lo``/``hi``
|
|
434
|
+
are literals, so the whole expression is a statement constant (PostgreSQL
|
|
435
|
+
InitPlan parameter). A NULL watermark (state row missing) coalesces to
|
|
436
|
+
``lo`` and an epoch watermark clamps to ``lo``: either way the tail starts
|
|
437
|
+
at ``lo`` and the raw complement degrades to the caller's full window,
|
|
438
|
+
exactly like the legacy raw read.
|
|
439
|
+
"""
|
|
440
|
+
watermark = func.coalesce(_conversation_watermark_subquery(), lo)
|
|
441
|
+
clamped = watermark if hi is None else _least_fn(session)(watermark, hi)
|
|
442
|
+
return _greatest_fn(session)(lo, clamped)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _conversation_raw_complement_clause(
|
|
446
|
+
session: AsyncSession, lo: datetime, hi: datetime | None
|
|
447
|
+
) -> ColumnElement[bool]:
|
|
448
|
+
"""Rows NOT covered by the folded segment ``[lo, min(W, hi))``: below the
|
|
449
|
+
ceil-grid start, or at/above the tail start.
|
|
450
|
+
|
|
451
|
+
Both bounds are statement constants (``lo``/``hi`` literals, ``W`` an
|
|
452
|
+
uncorrelated scalar subquery), so PostgreSQL plans the OR as a BitmapOr of
|
|
453
|
+
two index ranges on ``requested_at`` — never a per-row test of a joined
|
|
454
|
+
column. The constant bounds are load-bearing: PostgreSQL applies an OR
|
|
455
|
+
whose bounds come from a joined row as a per-row filter instead.
|
|
456
|
+
"""
|
|
457
|
+
return or_(RequestLog.requested_at < lo, RequestLog.requested_at >= _conversation_tail_start(session, lo, hi))
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _conversation_raw_select(
|
|
461
|
+
session: AsyncSession,
|
|
462
|
+
since: datetime,
|
|
463
|
+
until: datetime | None,
|
|
464
|
+
*,
|
|
465
|
+
raw_conditions: Sequence[ColumnElement[bool]],
|
|
466
|
+
display_bucket_seconds: int | None,
|
|
467
|
+
) -> Select:
|
|
468
|
+
lo = ceil_to_grid(since, HOURLY_BUCKET_SECONDS)
|
|
469
|
+
hi = None if until is None else floor_to_grid(until, HOURLY_BUCKET_SECONDS)
|
|
470
|
+
conditions: list[ColumnElement[bool]] = [
|
|
471
|
+
RequestLog.requested_at >= since,
|
|
472
|
+
_conversation_raw_complement_clause(session, lo, hi),
|
|
473
|
+
conversation_id_expr().is_not(None),
|
|
474
|
+
*raw_conditions,
|
|
475
|
+
]
|
|
476
|
+
if until is not None:
|
|
477
|
+
conditions.append(RequestLog.requested_at < until)
|
|
478
|
+
columns: list = [conversation_id_expr().label("cid"), literal(1).label("request_count")]
|
|
479
|
+
if display_bucket_seconds is not None:
|
|
480
|
+
columns.insert(0, _requested_at_epoch_bucket_expr(session, display_bucket_seconds).label("bucket_epoch"))
|
|
481
|
+
return select(*columns).select_from(RequestLog).where(and_(*conditions))
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def conversation_presence_union(
|
|
485
|
+
session: AsyncSession,
|
|
486
|
+
since: datetime,
|
|
487
|
+
until: datetime | None = None,
|
|
488
|
+
*,
|
|
489
|
+
include_deleted: bool,
|
|
490
|
+
raw_conditions: Sequence[ColumnElement[bool]] = (),
|
|
491
|
+
display_bucket_seconds: int | None = None,
|
|
492
|
+
) -> CompoundSelect:
|
|
493
|
+
"""UNION ALL of ``(bucket_epoch?, cid, request_count)`` rows: the folded
|
|
494
|
+
presence inside ``[since, until)`` plus its exact raw complement.
|
|
495
|
+
|
|
496
|
+
``raw_conditions`` MUST be the caller's legacy row filter minus the
|
|
497
|
+
window and the non-empty-conversation predicate (both owned here);
|
|
498
|
+
``include_deleted`` mirrors whether that filter keeps soft-deleted rows.
|
|
499
|
+
Callers count ``DISTINCT cid`` (dedup across the fold boundary) and/or
|
|
500
|
+
``SUM(request_count)`` over the union. ``display_bucket_seconds`` (a
|
|
501
|
+
whole multiple of the rollup hour) adds the display-bucket column for
|
|
502
|
+
per-bucket grouping.
|
|
503
|
+
"""
|
|
504
|
+
return union_all(
|
|
505
|
+
_conversation_folded_select(
|
|
506
|
+
session, since, until, include_deleted=include_deleted, display_bucket_seconds=display_bucket_seconds
|
|
507
|
+
),
|
|
508
|
+
_conversation_raw_select(
|
|
509
|
+
session, since, until, raw_conditions=raw_conditions, display_bucket_seconds=display_bucket_seconds
|
|
510
|
+
),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
async def earliest_hourly_bucket_at(session: AsyncSession) -> datetime | None:
|
|
515
|
+
"""Hour-precision earliest countable activity according to the rollups
|
|
516
|
+
(warmup kinds excluded, mirroring the raw earliest-activity filter).
|
|
517
|
+
Used as a fallback when retention has pruned raw below the watermark."""
|
|
518
|
+
stmt = select(func.min(RequestUsageHourlyRollup.bucket_epoch)).where(
|
|
519
|
+
RequestUsageHourlyRollup.request_kind.not_in(WARMUP_REQUEST_KINDS)
|
|
520
|
+
)
|
|
521
|
+
earliest = (await session.execute(stmt)).scalar_one_or_none()
|
|
522
|
+
return None if earliest is None else epoch_to_datetime(int(earliest))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|