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,1217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Callable, Collection, Mapping, Sequence
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
|
|
10
|
+
from app.core.clients.proxy import ProxyResponseError
|
|
11
|
+
from app.core.config.settings import get_settings
|
|
12
|
+
from app.core.errors import openai_error
|
|
13
|
+
from app.core.utils.time import to_utc_naive
|
|
14
|
+
from app.db.models import HttpBridgeSessionState
|
|
15
|
+
from app.db.session import close_session
|
|
16
|
+
from app.modules.proxy.continuity import is_http_bridge_account_neutral_replay
|
|
17
|
+
from app.modules.proxy.durable_bridge_repository import (
|
|
18
|
+
DURABLE_BRIDGE_OPERATION_SPOOL_PURGE_BATCH_SIZE,
|
|
19
|
+
REBIND_ANCHOR_UNFENCED,
|
|
20
|
+
DurableBridgeAliasRegistration,
|
|
21
|
+
DurableBridgeAliasRegistrationReceipt,
|
|
22
|
+
DurableBridgeOperationAbandonment,
|
|
23
|
+
DurableBridgeOperationAbandonmentScanCursor,
|
|
24
|
+
DurableBridgeOperationEventInput,
|
|
25
|
+
DurableBridgeOperationPurgeBatchResult,
|
|
26
|
+
DurableBridgeOperationSnapshot,
|
|
27
|
+
DurableBridgeRecoveryAttemptSnapshot,
|
|
28
|
+
DurableBridgeRepository,
|
|
29
|
+
DurableBridgeRetryCircuitSnapshot,
|
|
30
|
+
DurableBridgeSessionSnapshot,
|
|
31
|
+
DurableBridgeTranscriptTurn,
|
|
32
|
+
durable_bridge_api_key_scope,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_DURABLE_TURN_STATE_ALIAS = "turn_state"
|
|
36
|
+
_DURABLE_PREVIOUS_RESPONSE_ALIAS = "previous_response_id"
|
|
37
|
+
_DURABLE_SESSION_HEADER_ALIAS = "session_header"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class DurableBridgeLookup:
|
|
42
|
+
session_id: str
|
|
43
|
+
canonical_kind: str
|
|
44
|
+
canonical_key: str
|
|
45
|
+
api_key_scope: str
|
|
46
|
+
account_id: str | None
|
|
47
|
+
owner_instance_id: str | None
|
|
48
|
+
owner_epoch: int
|
|
49
|
+
lease_expires_at: datetime | None
|
|
50
|
+
state: HttpBridgeSessionState
|
|
51
|
+
latest_turn_state: str | None
|
|
52
|
+
latest_response_id: str | None
|
|
53
|
+
latest_input_item_count: int | None = None
|
|
54
|
+
latest_input_full_fingerprint: str | None = None
|
|
55
|
+
model: str | None = None
|
|
56
|
+
latest_pending_tool_calls: dict[str, str] | None = None
|
|
57
|
+
owner_process_epoch: str | None = None
|
|
58
|
+
# True when a writer retired this row's continuity owner. The lookup then
|
|
59
|
+
# carries no owner and no anchor, and the request proceeds as if this
|
|
60
|
+
# thread had never been pinned — which is different from "no row exists",
|
|
61
|
+
# because callers must not fail closed on the missing owner.
|
|
62
|
+
continuity_abandoned: bool = False
|
|
63
|
+
retired_account_id: str | None = None
|
|
64
|
+
|
|
65
|
+
def lease_is_active(self, *, now: datetime) -> bool:
|
|
66
|
+
if self.owner_instance_id is None:
|
|
67
|
+
return False
|
|
68
|
+
if self.lease_expires_at is None:
|
|
69
|
+
return False
|
|
70
|
+
# lease_expires_at is a timestamptz column: PostgreSQL yields it
|
|
71
|
+
# offset-aware while the app clock (utcnow) and SQLite yield naive
|
|
72
|
+
# UTC. Normalize both sides — comparing them raw raises TypeError
|
|
73
|
+
# on the anchored-lookup hot path.
|
|
74
|
+
return to_utc_naive(self.lease_expires_at) > to_utc_naive(now)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def durable_bridge_snapshot_is_detached(snapshot: DurableBridgeSessionSnapshot) -> bool:
|
|
78
|
+
"""Return True for a row whose owner account was invalidated.
|
|
79
|
+
|
|
80
|
+
Account deactivation, re-authentication demands, proxy-binding changes and
|
|
81
|
+
deletion detach every durable bridge row of that account: the row is
|
|
82
|
+
CLOSED, its owner account, lease and every continuity anchor are cleared,
|
|
83
|
+
and its aliases are deleted. Such a row proves only that a conversation
|
|
84
|
+
once existed; it names no account that could still preserve continuity,
|
|
85
|
+
so it MUST NOT be treated as durable owner evidence. Reporting it as a
|
|
86
|
+
lookup hit made every hard-affinity (thread/session header) continuation
|
|
87
|
+
fail closed forever with ``previous_response_owner_unavailable`` even
|
|
88
|
+
after the account was reactivated, while a fresh thread on the same
|
|
89
|
+
client worked.
|
|
90
|
+
|
|
91
|
+
A CLOSED row that still names its account (ordinary release) keeps its
|
|
92
|
+
continuity value and is intentionally not covered here.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
snapshot.account_id is None
|
|
97
|
+
and snapshot.state == HttpBridgeSessionState.CLOSED
|
|
98
|
+
and snapshot.owner_instance_id is None
|
|
99
|
+
and snapshot.latest_turn_state is None
|
|
100
|
+
and snapshot.latest_response_id is None
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class DurableBridgeSessionCoordinator:
|
|
105
|
+
def __init__(self, session_factory: Callable[[], AsyncSession]) -> None:
|
|
106
|
+
self._session_factory = session_factory
|
|
107
|
+
self._operation_abandonment_scan_cursor: DurableBridgeOperationAbandonmentScanCursor | None = None
|
|
108
|
+
|
|
109
|
+
async def lookup_request_targets(
|
|
110
|
+
self,
|
|
111
|
+
*,
|
|
112
|
+
session_key_kind: str,
|
|
113
|
+
session_key_value: str,
|
|
114
|
+
api_key_id: str | None,
|
|
115
|
+
turn_state: str | None,
|
|
116
|
+
session_header: str | None,
|
|
117
|
+
previous_response_id: str | None,
|
|
118
|
+
) -> DurableBridgeLookup | None:
|
|
119
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
120
|
+
async with self._session() as session:
|
|
121
|
+
repository = DurableBridgeRepository(session)
|
|
122
|
+
resolved_aliases: list[tuple[str, DurableBridgeSessionSnapshot]] = []
|
|
123
|
+
for alias_kind, alias_value in (
|
|
124
|
+
(_DURABLE_TURN_STATE_ALIAS, turn_state),
|
|
125
|
+
(_DURABLE_PREVIOUS_RESPONSE_ALIAS, previous_response_id),
|
|
126
|
+
(_DURABLE_SESSION_HEADER_ALIAS, session_header),
|
|
127
|
+
):
|
|
128
|
+
if alias_value is None:
|
|
129
|
+
continue
|
|
130
|
+
snapshot = await repository.resolve_alias(
|
|
131
|
+
alias_kind=alias_kind,
|
|
132
|
+
alias_value=alias_value,
|
|
133
|
+
api_key_scope=api_key_scope,
|
|
134
|
+
)
|
|
135
|
+
if snapshot is not None and not durable_bridge_snapshot_is_detached(snapshot):
|
|
136
|
+
resolved_aliases.append((alias_kind, snapshot))
|
|
137
|
+
resolved_identities = {(snapshot.id, snapshot.account_id) for _alias_kind, snapshot in resolved_aliases}
|
|
138
|
+
resolved_account_ids = {
|
|
139
|
+
snapshot.account_id for _alias_kind, snapshot in resolved_aliases if snapshot.account_id is not None
|
|
140
|
+
}
|
|
141
|
+
has_ownerless_snapshot = any(snapshot.account_id is None for _alias_kind, snapshot in resolved_aliases)
|
|
142
|
+
if len(resolved_identities) > 1:
|
|
143
|
+
same_account_handoff = len(resolved_account_ids) == 1 and not has_ownerless_snapshot
|
|
144
|
+
if same_account_handoff:
|
|
145
|
+
account_id = next(iter(resolved_account_ids))
|
|
146
|
+
same_account_snapshots = [
|
|
147
|
+
snapshot for _alias_kind, snapshot in resolved_aliases if snapshot.account_id == account_id
|
|
148
|
+
]
|
|
149
|
+
requested_response_snapshot = next(
|
|
150
|
+
(
|
|
151
|
+
snapshot
|
|
152
|
+
for alias_kind, snapshot in resolved_aliases
|
|
153
|
+
if alias_kind == _DURABLE_PREVIOUS_RESPONSE_ALIAS and snapshot.account_id == account_id
|
|
154
|
+
),
|
|
155
|
+
None,
|
|
156
|
+
)
|
|
157
|
+
account_snapshot = requested_response_snapshot or max(
|
|
158
|
+
same_account_snapshots,
|
|
159
|
+
key=lambda snapshot: (
|
|
160
|
+
snapshot.latest_response_id is not None,
|
|
161
|
+
snapshot.last_seen_at,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
# Same-account aliases may point at different durable rows
|
|
165
|
+
# during a handoff. Preserve an explicitly requested
|
|
166
|
+
# response anchor; otherwise prefer the newest persisted
|
|
167
|
+
# response anchor rather than alias-resolution order.
|
|
168
|
+
return _to_lookup(account_snapshot)
|
|
169
|
+
specific_aliases = [
|
|
170
|
+
(alias_kind, snapshot)
|
|
171
|
+
for alias_kind, snapshot in resolved_aliases
|
|
172
|
+
if alias_kind != _DURABLE_SESSION_HEADER_ALIAS
|
|
173
|
+
]
|
|
174
|
+
specific_identities = {(snapshot.id, snapshot.account_id) for _alias_kind, snapshot in specific_aliases}
|
|
175
|
+
if len(specific_identities) == 1:
|
|
176
|
+
specific_snapshot = specific_aliases[0][1]
|
|
177
|
+
specific_identity = (specific_snapshot.id, specific_snapshot.account_id)
|
|
178
|
+
conflicting_alias_kinds = {
|
|
179
|
+
alias_kind
|
|
180
|
+
for alias_kind, snapshot in resolved_aliases
|
|
181
|
+
if (snapshot.id, snapshot.account_id) != specific_identity
|
|
182
|
+
}
|
|
183
|
+
if is_http_bridge_account_neutral_replay(
|
|
184
|
+
kind=specific_snapshot.session_key_kind,
|
|
185
|
+
key=specific_snapshot.session_key_value,
|
|
186
|
+
) and conflicting_alias_kinds == {_DURABLE_SESSION_HEADER_ALIAS}:
|
|
187
|
+
return _to_lookup(specific_snapshot)
|
|
188
|
+
# Turn-state/response/session aliases are independent hard
|
|
189
|
+
# evidence. Returning the first match would silently discard a
|
|
190
|
+
# conflicting durable owner based on source ordering.
|
|
191
|
+
raise ProxyResponseError(
|
|
192
|
+
502,
|
|
193
|
+
openai_error(
|
|
194
|
+
"continuity_owner_conflict",
|
|
195
|
+
"Durable continuity aliases resolve to conflicting upstream owners.",
|
|
196
|
+
error_type="server_error",
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
if resolved_aliases:
|
|
200
|
+
return _to_lookup(resolved_aliases[0][1])
|
|
201
|
+
snapshot = await repository.get_session(
|
|
202
|
+
session_key_kind=session_key_kind,
|
|
203
|
+
session_key_value=session_key_value,
|
|
204
|
+
api_key_scope=api_key_scope,
|
|
205
|
+
)
|
|
206
|
+
if snapshot is not None and durable_bridge_snapshot_is_detached(snapshot):
|
|
207
|
+
# The canonical key still maps to a row the account
|
|
208
|
+
# invalidation path detached. Let the request start fresh on
|
|
209
|
+
# a selectable account (the later claim re-owns this row)
|
|
210
|
+
# instead of failing closed on an owner that no longer exists.
|
|
211
|
+
snapshot = None
|
|
212
|
+
if snapshot is None:
|
|
213
|
+
if turn_state is not None:
|
|
214
|
+
snapshot = await repository.find_session_by_latest_turn_state(
|
|
215
|
+
turn_state=turn_state,
|
|
216
|
+
api_key_scope=api_key_scope,
|
|
217
|
+
)
|
|
218
|
+
if snapshot is None and previous_response_id is not None:
|
|
219
|
+
snapshot = await repository.find_session_by_latest_response_id(
|
|
220
|
+
response_id=previous_response_id,
|
|
221
|
+
api_key_scope=api_key_scope,
|
|
222
|
+
)
|
|
223
|
+
if snapshot is None:
|
|
224
|
+
return None
|
|
225
|
+
return _to_lookup(snapshot)
|
|
226
|
+
|
|
227
|
+
async def lookup_turn_state_target(
|
|
228
|
+
self,
|
|
229
|
+
*,
|
|
230
|
+
turn_state: str,
|
|
231
|
+
api_key_id: str | None,
|
|
232
|
+
) -> DurableBridgeLookup | None:
|
|
233
|
+
"""Resolve only a previously registered turn-state continuity anchor."""
|
|
234
|
+
|
|
235
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
236
|
+
async with self._session() as session:
|
|
237
|
+
repository = DurableBridgeRepository(session)
|
|
238
|
+
snapshot = await repository.resolve_alias(
|
|
239
|
+
alias_kind=_DURABLE_TURN_STATE_ALIAS,
|
|
240
|
+
alias_value=turn_state,
|
|
241
|
+
api_key_scope=api_key_scope,
|
|
242
|
+
)
|
|
243
|
+
return _to_lookup(snapshot) if snapshot is not None else None
|
|
244
|
+
|
|
245
|
+
async def lookup_sessions(self, *, session_ids: Sequence[str]) -> list[DurableBridgeLookup]:
|
|
246
|
+
"""Batch-load durable session snapshots for ownership reconciliation."""
|
|
247
|
+
|
|
248
|
+
if not session_ids:
|
|
249
|
+
return []
|
|
250
|
+
async with self._session() as session:
|
|
251
|
+
snapshots = await DurableBridgeRepository(session).get_sessions_by_ids(session_ids)
|
|
252
|
+
return [_to_lookup(snapshot) for snapshot in snapshots]
|
|
253
|
+
|
|
254
|
+
async def lookup_retry_circuit(
|
|
255
|
+
self,
|
|
256
|
+
*,
|
|
257
|
+
session_key_kind: str,
|
|
258
|
+
session_key_value: str,
|
|
259
|
+
api_key_id: str | None,
|
|
260
|
+
) -> DurableBridgeRetryCircuitSnapshot | None:
|
|
261
|
+
async with self._session() as session:
|
|
262
|
+
return await DurableBridgeRepository(session).get_retry_circuit(
|
|
263
|
+
session_key_kind=session_key_kind,
|
|
264
|
+
session_key_value=session_key_value,
|
|
265
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def persist_retry_circuit(
|
|
269
|
+
self,
|
|
270
|
+
*,
|
|
271
|
+
session_key_kind: str,
|
|
272
|
+
session_key_value: str,
|
|
273
|
+
api_key_id: str | None,
|
|
274
|
+
consecutive_failures: int,
|
|
275
|
+
cooldown_until_epoch: float,
|
|
276
|
+
last_detail: str | None,
|
|
277
|
+
updated_at_epoch: float,
|
|
278
|
+
base_updated_at_epoch: float = 0.0,
|
|
279
|
+
failure_threshold: int = 1,
|
|
280
|
+
poison_sticky_threshold: int | None = None,
|
|
281
|
+
conflict_cooldown_until_epoch: float | None = None,
|
|
282
|
+
base_backoff_seconds: float = 60.0,
|
|
283
|
+
max_backoff_seconds: float = 600.0,
|
|
284
|
+
clean_close_max_backoff_seconds: float = 30.0,
|
|
285
|
+
) -> DurableBridgeRetryCircuitSnapshot | None:
|
|
286
|
+
async with self._session() as session:
|
|
287
|
+
repository = DurableBridgeRepository(session)
|
|
288
|
+
await repository.upsert_retry_circuit(
|
|
289
|
+
session_key_kind=session_key_kind,
|
|
290
|
+
session_key_value=session_key_value,
|
|
291
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
292
|
+
consecutive_failures=consecutive_failures,
|
|
293
|
+
cooldown_until_epoch=cooldown_until_epoch,
|
|
294
|
+
last_detail=last_detail,
|
|
295
|
+
updated_at_epoch=updated_at_epoch,
|
|
296
|
+
base_updated_at_epoch=base_updated_at_epoch,
|
|
297
|
+
failure_threshold=failure_threshold,
|
|
298
|
+
poison_sticky_threshold=poison_sticky_threshold,
|
|
299
|
+
conflict_cooldown_until_epoch=conflict_cooldown_until_epoch,
|
|
300
|
+
base_backoff_seconds=base_backoff_seconds,
|
|
301
|
+
max_backoff_seconds=max_backoff_seconds,
|
|
302
|
+
clean_close_max_backoff_seconds=clean_close_max_backoff_seconds,
|
|
303
|
+
)
|
|
304
|
+
return await repository.get_retry_circuit(
|
|
305
|
+
session_key_kind=session_key_kind,
|
|
306
|
+
session_key_value=session_key_value,
|
|
307
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
async def clear_retry_circuit(
|
|
311
|
+
self,
|
|
312
|
+
*,
|
|
313
|
+
session_key_kind: str,
|
|
314
|
+
session_key_value: str,
|
|
315
|
+
api_key_id: str | None,
|
|
316
|
+
expected_updated_at_epoch: float | None = None,
|
|
317
|
+
expected_admission_generation: int | None = None,
|
|
318
|
+
expected_consecutive_failures: int | None = None,
|
|
319
|
+
reset_detail: str | None = None,
|
|
320
|
+
) -> bool:
|
|
321
|
+
async with self._session() as session:
|
|
322
|
+
return await DurableBridgeRepository(session).delete_retry_circuit(
|
|
323
|
+
session_key_kind=session_key_kind,
|
|
324
|
+
session_key_value=session_key_value,
|
|
325
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
326
|
+
expected_consecutive_failures=expected_consecutive_failures,
|
|
327
|
+
expected_updated_at_epoch=expected_updated_at_epoch,
|
|
328
|
+
expected_admission_generation=expected_admission_generation,
|
|
329
|
+
reset_detail=reset_detail,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
async def supersede_retry_circuit_detail(
|
|
333
|
+
self,
|
|
334
|
+
*,
|
|
335
|
+
session_key_kind: str,
|
|
336
|
+
session_key_value: str,
|
|
337
|
+
api_key_id: str | None,
|
|
338
|
+
expected_updated_at_epoch: float,
|
|
339
|
+
expected_consecutive_failures: int,
|
|
340
|
+
expected_last_detail: str | None,
|
|
341
|
+
last_detail: str | None,
|
|
342
|
+
) -> bool:
|
|
343
|
+
async with self._session() as session:
|
|
344
|
+
return await DurableBridgeRepository(session).supersede_retry_circuit_detail(
|
|
345
|
+
session_key_kind=session_key_kind,
|
|
346
|
+
session_key_value=session_key_value,
|
|
347
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
348
|
+
expected_updated_at_epoch=expected_updated_at_epoch,
|
|
349
|
+
expected_consecutive_failures=expected_consecutive_failures,
|
|
350
|
+
expected_last_detail=expected_last_detail,
|
|
351
|
+
last_detail=last_detail,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
async def claim_retry_circuit_generation(
|
|
355
|
+
self,
|
|
356
|
+
*,
|
|
357
|
+
session_key_kind: str,
|
|
358
|
+
session_key_value: str,
|
|
359
|
+
api_key_id: str | None,
|
|
360
|
+
expected_updated_at_epoch: float | None,
|
|
361
|
+
expected_admission_generation: int,
|
|
362
|
+
expected_consecutive_failures: int,
|
|
363
|
+
expected_cooldown_until_epoch: float,
|
|
364
|
+
) -> DurableBridgeRetryCircuitSnapshot | None:
|
|
365
|
+
async with self._session() as session:
|
|
366
|
+
return await DurableBridgeRepository(session).claim_retry_circuit_generation(
|
|
367
|
+
session_key_kind=session_key_kind,
|
|
368
|
+
session_key_value=session_key_value,
|
|
369
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
370
|
+
expected_updated_at_epoch=expected_updated_at_epoch,
|
|
371
|
+
expected_admission_generation=expected_admission_generation,
|
|
372
|
+
expected_consecutive_failures=expected_consecutive_failures,
|
|
373
|
+
expected_cooldown_until_epoch=expected_cooldown_until_epoch,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
async def purge_retry_circuit(
|
|
377
|
+
self,
|
|
378
|
+
*,
|
|
379
|
+
session_key_kind: str,
|
|
380
|
+
session_key_value: str,
|
|
381
|
+
api_key_id: str | None,
|
|
382
|
+
expected_updated_at_epoch: float | None = None,
|
|
383
|
+
expected_admission_generation: int | None = None,
|
|
384
|
+
expected_consecutive_failures: int | None = None,
|
|
385
|
+
fence_last_detail: bool = False,
|
|
386
|
+
expected_last_detail: str | None = None,
|
|
387
|
+
) -> bool:
|
|
388
|
+
async with self._session() as session:
|
|
389
|
+
return await DurableBridgeRepository(session).purge_retry_circuit(
|
|
390
|
+
session_key_kind=session_key_kind,
|
|
391
|
+
session_key_value=session_key_value,
|
|
392
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
393
|
+
expected_updated_at_epoch=expected_updated_at_epoch,
|
|
394
|
+
expected_admission_generation=expected_admission_generation,
|
|
395
|
+
expected_consecutive_failures=expected_consecutive_failures,
|
|
396
|
+
fence_last_detail=fence_last_detail,
|
|
397
|
+
expected_last_detail=expected_last_detail,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
async def claim_live_session(
|
|
401
|
+
self,
|
|
402
|
+
*,
|
|
403
|
+
session_key_kind: str,
|
|
404
|
+
session_key_value: str,
|
|
405
|
+
api_key_id: str | None,
|
|
406
|
+
instance_id: str,
|
|
407
|
+
lease_ttl_seconds: float,
|
|
408
|
+
account_id: str | None,
|
|
409
|
+
model: str | None,
|
|
410
|
+
service_tier: str | None,
|
|
411
|
+
latest_turn_state: str | None,
|
|
412
|
+
latest_response_id: str | None,
|
|
413
|
+
allow_takeover: bool,
|
|
414
|
+
owner_process_epoch: str,
|
|
415
|
+
force_owner_epoch_advance: bool = False,
|
|
416
|
+
) -> DurableBridgeLookup:
|
|
417
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
418
|
+
async with self._session() as session:
|
|
419
|
+
snapshot = await DurableBridgeRepository(session).claim_session(
|
|
420
|
+
session_key_kind=session_key_kind,
|
|
421
|
+
session_key_value=session_key_value,
|
|
422
|
+
api_key_scope=api_key_scope,
|
|
423
|
+
instance_id=instance_id,
|
|
424
|
+
lease_ttl_seconds=lease_ttl_seconds,
|
|
425
|
+
account_id=account_id,
|
|
426
|
+
model=model,
|
|
427
|
+
service_tier=service_tier,
|
|
428
|
+
latest_turn_state=latest_turn_state,
|
|
429
|
+
latest_response_id=latest_response_id,
|
|
430
|
+
allow_takeover=allow_takeover,
|
|
431
|
+
owner_process_epoch=owner_process_epoch,
|
|
432
|
+
force_owner_epoch_advance=force_owner_epoch_advance,
|
|
433
|
+
)
|
|
434
|
+
return _to_lookup(snapshot)
|
|
435
|
+
|
|
436
|
+
async def renew_live_session(
|
|
437
|
+
self,
|
|
438
|
+
*,
|
|
439
|
+
session_id: str,
|
|
440
|
+
api_key_id: str | None,
|
|
441
|
+
instance_id: str,
|
|
442
|
+
owner_epoch: int,
|
|
443
|
+
lease_ttl_seconds: float,
|
|
444
|
+
latest_turn_state: str | None = None,
|
|
445
|
+
latest_response_id: str | None = None,
|
|
446
|
+
latest_input_item_count: int | None = None,
|
|
447
|
+
latest_input_full_fingerprint: str | None = None,
|
|
448
|
+
latest_pending_tool_calls: Mapping[str, str] | None = None,
|
|
449
|
+
state: HttpBridgeSessionState | None = None,
|
|
450
|
+
) -> DurableBridgeLookup | None:
|
|
451
|
+
del api_key_id
|
|
452
|
+
async with self._session() as session:
|
|
453
|
+
snapshot = await DurableBridgeRepository(session).renew_session(
|
|
454
|
+
session_id=session_id,
|
|
455
|
+
instance_id=instance_id,
|
|
456
|
+
owner_epoch=owner_epoch,
|
|
457
|
+
lease_ttl_seconds=lease_ttl_seconds,
|
|
458
|
+
latest_turn_state=latest_turn_state,
|
|
459
|
+
latest_response_id=latest_response_id,
|
|
460
|
+
latest_input_item_count=latest_input_item_count,
|
|
461
|
+
latest_input_full_fingerprint=latest_input_full_fingerprint,
|
|
462
|
+
latest_pending_tool_calls=latest_pending_tool_calls,
|
|
463
|
+
state=state,
|
|
464
|
+
)
|
|
465
|
+
if snapshot is None:
|
|
466
|
+
return None
|
|
467
|
+
return _to_lookup(snapshot)
|
|
468
|
+
|
|
469
|
+
async def rebind_session_account(
|
|
470
|
+
self,
|
|
471
|
+
*,
|
|
472
|
+
session_id: str,
|
|
473
|
+
api_key_id: str | None,
|
|
474
|
+
instance_id: str,
|
|
475
|
+
owner_epoch: int,
|
|
476
|
+
account_id: str,
|
|
477
|
+
clear_continuity: bool = False,
|
|
478
|
+
expected_latest_response_id: object = REBIND_ANCHOR_UNFENCED,
|
|
479
|
+
expected_latest_turn_state: object = REBIND_ANCHOR_UNFENCED,
|
|
480
|
+
) -> bool:
|
|
481
|
+
del api_key_id
|
|
482
|
+
async with self._session() as session:
|
|
483
|
+
return await DurableBridgeRepository(session).rebind_session_account(
|
|
484
|
+
session_id=session_id,
|
|
485
|
+
instance_id=instance_id,
|
|
486
|
+
owner_epoch=owner_epoch,
|
|
487
|
+
account_id=account_id,
|
|
488
|
+
clear_continuity=clear_continuity,
|
|
489
|
+
expected_latest_response_id=expected_latest_response_id,
|
|
490
|
+
expected_latest_turn_state=expected_latest_turn_state,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
async def session_latest_continuity(self, *, session_id: str) -> tuple[str | None, str | None] | None:
|
|
494
|
+
"""Read the session's current continuity anchors for a fenced clear."""
|
|
495
|
+
async with self._session() as session:
|
|
496
|
+
return await DurableBridgeRepository(session).latest_session_continuity(session_id=session_id)
|
|
497
|
+
|
|
498
|
+
async def release_live_session(
|
|
499
|
+
self,
|
|
500
|
+
*,
|
|
501
|
+
session_id: str,
|
|
502
|
+
instance_id: str,
|
|
503
|
+
owner_epoch: int,
|
|
504
|
+
draining: bool,
|
|
505
|
+
) -> DurableBridgeLookup | None:
|
|
506
|
+
async with self._session() as session:
|
|
507
|
+
snapshot = await DurableBridgeRepository(session).release_session(
|
|
508
|
+
session_id=session_id,
|
|
509
|
+
instance_id=instance_id,
|
|
510
|
+
owner_epoch=owner_epoch,
|
|
511
|
+
draining=draining,
|
|
512
|
+
)
|
|
513
|
+
if snapshot is None:
|
|
514
|
+
return None
|
|
515
|
+
return _to_lookup(snapshot)
|
|
516
|
+
|
|
517
|
+
async def clear_live_session_response_anchor(
|
|
518
|
+
self,
|
|
519
|
+
*,
|
|
520
|
+
session_id: str,
|
|
521
|
+
instance_id: str,
|
|
522
|
+
owner_epoch: int,
|
|
523
|
+
) -> DurableBridgeLookup | None:
|
|
524
|
+
async with self._session() as session:
|
|
525
|
+
snapshot = await DurableBridgeRepository(session).clear_latest_response_anchor(
|
|
526
|
+
session_id=session_id,
|
|
527
|
+
instance_id=instance_id,
|
|
528
|
+
owner_epoch=owner_epoch,
|
|
529
|
+
)
|
|
530
|
+
if snapshot is None:
|
|
531
|
+
return None
|
|
532
|
+
return _to_lookup(snapshot)
|
|
533
|
+
|
|
534
|
+
async def clear_live_session_response_anchor_if_matches(
|
|
535
|
+
self,
|
|
536
|
+
*,
|
|
537
|
+
session_id: str,
|
|
538
|
+
api_key_id: str | None,
|
|
539
|
+
instance_id: str,
|
|
540
|
+
owner_epoch: int,
|
|
541
|
+
response_id: str,
|
|
542
|
+
) -> DurableBridgeLookup | None:
|
|
543
|
+
async with self._session() as session:
|
|
544
|
+
snapshot = await DurableBridgeRepository(session).clear_latest_response_anchor_if_matches(
|
|
545
|
+
session_id=session_id,
|
|
546
|
+
api_key_scope=durable_bridge_api_key_scope(api_key_id),
|
|
547
|
+
instance_id=instance_id,
|
|
548
|
+
owner_epoch=owner_epoch,
|
|
549
|
+
response_id=response_id,
|
|
550
|
+
)
|
|
551
|
+
if snapshot is None:
|
|
552
|
+
return None
|
|
553
|
+
return _to_lookup(snapshot)
|
|
554
|
+
|
|
555
|
+
async def record_recovery_attempt(
|
|
556
|
+
self,
|
|
557
|
+
*,
|
|
558
|
+
session_id: str,
|
|
559
|
+
api_key_id: str | None,
|
|
560
|
+
instance_id: str,
|
|
561
|
+
owner_epoch: int,
|
|
562
|
+
request_fingerprint: str,
|
|
563
|
+
request_id: str,
|
|
564
|
+
account_id: str | None,
|
|
565
|
+
model: str | None,
|
|
566
|
+
replay_safe: bool,
|
|
567
|
+
) -> DurableBridgeRecoveryAttemptSnapshot | None:
|
|
568
|
+
del api_key_id
|
|
569
|
+
async with self._session() as session:
|
|
570
|
+
return await DurableBridgeRepository(session).record_recovery_attempt(
|
|
571
|
+
session_id=session_id,
|
|
572
|
+
instance_id=instance_id,
|
|
573
|
+
owner_epoch=owner_epoch,
|
|
574
|
+
request_fingerprint=request_fingerprint,
|
|
575
|
+
request_id=request_id,
|
|
576
|
+
account_id=account_id,
|
|
577
|
+
model=model,
|
|
578
|
+
replay_safe=replay_safe,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
async def lookup_recovery_attempt(
|
|
582
|
+
self,
|
|
583
|
+
*,
|
|
584
|
+
session_id: str,
|
|
585
|
+
request_fingerprint: str,
|
|
586
|
+
) -> DurableBridgeRecoveryAttemptSnapshot | None:
|
|
587
|
+
async with self._session() as session:
|
|
588
|
+
return await DurableBridgeRepository(session).lookup_recovery_attempt(
|
|
589
|
+
session_id=session_id,
|
|
590
|
+
request_fingerprint=request_fingerprint,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
async def mark_recovery_attempt_replayed(
|
|
594
|
+
self,
|
|
595
|
+
*,
|
|
596
|
+
session_id: str,
|
|
597
|
+
api_key_id: str | None,
|
|
598
|
+
instance_id: str,
|
|
599
|
+
owner_epoch: int,
|
|
600
|
+
request_fingerprint: str,
|
|
601
|
+
response_id: str | None = None,
|
|
602
|
+
) -> bool:
|
|
603
|
+
del api_key_id
|
|
604
|
+
async with self._session() as session:
|
|
605
|
+
return await DurableBridgeRepository(session).mark_recovery_attempt_replayed(
|
|
606
|
+
session_id=session_id,
|
|
607
|
+
instance_id=instance_id,
|
|
608
|
+
owner_epoch=owner_epoch,
|
|
609
|
+
request_fingerprint=request_fingerprint,
|
|
610
|
+
response_id=response_id,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
async def rollback_recovery_attempt_replayed(
|
|
614
|
+
self,
|
|
615
|
+
*,
|
|
616
|
+
session_id: str,
|
|
617
|
+
api_key_id: str | None,
|
|
618
|
+
instance_id: str,
|
|
619
|
+
owner_epoch: int,
|
|
620
|
+
request_fingerprint: str,
|
|
621
|
+
) -> bool:
|
|
622
|
+
del api_key_id
|
|
623
|
+
async with self._session() as session:
|
|
624
|
+
return await DurableBridgeRepository(session).rollback_recovery_attempt_replayed(
|
|
625
|
+
session_id=session_id,
|
|
626
|
+
instance_id=instance_id,
|
|
627
|
+
owner_epoch=owner_epoch,
|
|
628
|
+
request_fingerprint=request_fingerprint,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
async def rollback_recovery_attempt_before_dispatch(
|
|
632
|
+
self,
|
|
633
|
+
*,
|
|
634
|
+
session_id: str,
|
|
635
|
+
api_key_id: str | None,
|
|
636
|
+
instance_id: str,
|
|
637
|
+
owner_epoch: int,
|
|
638
|
+
request_fingerprint: str,
|
|
639
|
+
) -> bool:
|
|
640
|
+
del api_key_id
|
|
641
|
+
async with self._session() as session:
|
|
642
|
+
return await DurableBridgeRepository(session).rollback_recovery_attempt_before_dispatch(
|
|
643
|
+
session_id=session_id,
|
|
644
|
+
instance_id=instance_id,
|
|
645
|
+
owner_epoch=owner_epoch,
|
|
646
|
+
request_fingerprint=request_fingerprint,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
async def record_operation(
|
|
650
|
+
self,
|
|
651
|
+
*,
|
|
652
|
+
operation_id: str,
|
|
653
|
+
session_id: str,
|
|
654
|
+
instance_id: str,
|
|
655
|
+
owner_epoch: int,
|
|
656
|
+
request_fingerprint: str,
|
|
657
|
+
account_id: str | None,
|
|
658
|
+
model: str | None,
|
|
659
|
+
parent_response_id: str | None,
|
|
660
|
+
api_key_scope: str | None = None,
|
|
661
|
+
request_text: str | None = None,
|
|
662
|
+
recovery_attempt_session_id: str | None = None,
|
|
663
|
+
recovery_attempt_owner_epoch: int | None = None,
|
|
664
|
+
recovery_attempt_fingerprint: str | None = None,
|
|
665
|
+
recovery_attempt_consumed: bool = False,
|
|
666
|
+
) -> DurableBridgeOperationSnapshot | None:
|
|
667
|
+
async with self._session() as session:
|
|
668
|
+
return await DurableBridgeRepository(session).record_operation(
|
|
669
|
+
operation_id=operation_id,
|
|
670
|
+
session_id=session_id,
|
|
671
|
+
instance_id=instance_id,
|
|
672
|
+
owner_epoch=owner_epoch,
|
|
673
|
+
request_fingerprint=request_fingerprint,
|
|
674
|
+
api_key_scope=api_key_scope,
|
|
675
|
+
account_id=account_id,
|
|
676
|
+
model=model,
|
|
677
|
+
parent_response_id=parent_response_id,
|
|
678
|
+
request_text=request_text,
|
|
679
|
+
recovery_attempt_session_id=recovery_attempt_session_id,
|
|
680
|
+
recovery_attempt_owner_epoch=recovery_attempt_owner_epoch,
|
|
681
|
+
recovery_attempt_fingerprint=recovery_attempt_fingerprint,
|
|
682
|
+
recovery_attempt_consumed=recovery_attempt_consumed,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
async def get_operation_events(self, *, operation_id: str) -> list[str]:
|
|
686
|
+
async with self._session() as session:
|
|
687
|
+
return await DurableBridgeRepository(session).get_operation_events(
|
|
688
|
+
operation_id=operation_id,
|
|
689
|
+
max_bytes=int(
|
|
690
|
+
getattr(
|
|
691
|
+
get_settings(), "http_responses_session_bridge_operation_event_spool_max_bytes", 2 * 1024 * 1024
|
|
692
|
+
)
|
|
693
|
+
),
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
async def abandon_stale_operations(
|
|
697
|
+
self,
|
|
698
|
+
*,
|
|
699
|
+
cutoff: datetime,
|
|
700
|
+
lease_expired_before: datetime,
|
|
701
|
+
protected_operation_ids: Collection[str] = (),
|
|
702
|
+
batch_size: int = 500,
|
|
703
|
+
) -> list[DurableBridgeOperationAbandonment]:
|
|
704
|
+
async with self._session() as session:
|
|
705
|
+
sweep = await DurableBridgeRepository(session).abandon_stale_operations(
|
|
706
|
+
cutoff=cutoff,
|
|
707
|
+
lease_expired_before=lease_expired_before,
|
|
708
|
+
protected_operation_ids=protected_operation_ids,
|
|
709
|
+
batch_size=batch_size,
|
|
710
|
+
scan_cursor=self._operation_abandonment_scan_cursor,
|
|
711
|
+
)
|
|
712
|
+
self._operation_abandonment_scan_cursor = sweep.next_cursor
|
|
713
|
+
return list(sweep.abandonments)
|
|
714
|
+
|
|
715
|
+
async def get_replayable_transcript(
|
|
716
|
+
self,
|
|
717
|
+
*,
|
|
718
|
+
response_id: str,
|
|
719
|
+
max_turns: int = 128,
|
|
720
|
+
max_bytes: int = 8 * 1024 * 1024,
|
|
721
|
+
) -> list[DurableBridgeTranscriptTurn] | None:
|
|
722
|
+
async with self._session() as session:
|
|
723
|
+
return await DurableBridgeRepository(session).get_replayable_transcript(
|
|
724
|
+
response_id=response_id,
|
|
725
|
+
max_turns=max_turns,
|
|
726
|
+
max_bytes=max_bytes,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
async def purge_operation_spool(
|
|
730
|
+
self,
|
|
731
|
+
*,
|
|
732
|
+
cutoff: datetime,
|
|
733
|
+
batch_size: int = DURABLE_BRIDGE_OPERATION_SPOOL_PURGE_BATCH_SIZE,
|
|
734
|
+
) -> int:
|
|
735
|
+
return (await self.purge_operation_spool_batch(cutoff=cutoff, batch_size=batch_size)).deleted_operations
|
|
736
|
+
|
|
737
|
+
async def purge_operation_spool_batch(
|
|
738
|
+
self,
|
|
739
|
+
*,
|
|
740
|
+
cutoff: datetime,
|
|
741
|
+
batch_size: int = DURABLE_BRIDGE_OPERATION_SPOOL_PURGE_BATCH_SIZE,
|
|
742
|
+
) -> DurableBridgeOperationPurgeBatchResult:
|
|
743
|
+
async with self._session() as session:
|
|
744
|
+
return await DurableBridgeRepository(session).purge_operation_spool_batch(
|
|
745
|
+
cutoff=cutoff,
|
|
746
|
+
batch_size=batch_size,
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
async def append_operation_event(
|
|
750
|
+
self,
|
|
751
|
+
*,
|
|
752
|
+
operation_id: str,
|
|
753
|
+
session_id: str,
|
|
754
|
+
instance_id: str,
|
|
755
|
+
owner_epoch: int,
|
|
756
|
+
event_text: str,
|
|
757
|
+
max_bytes: int,
|
|
758
|
+
) -> bool:
|
|
759
|
+
async with self._session() as session:
|
|
760
|
+
return await DurableBridgeRepository(session).append_operation_event(
|
|
761
|
+
operation_id=operation_id,
|
|
762
|
+
session_id=session_id,
|
|
763
|
+
instance_id=instance_id,
|
|
764
|
+
owner_epoch=owner_epoch,
|
|
765
|
+
event_text=event_text,
|
|
766
|
+
max_bytes=max_bytes,
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
async def append_terminal_operation_event(
|
|
770
|
+
self,
|
|
771
|
+
*,
|
|
772
|
+
operation_id: str,
|
|
773
|
+
session_id: str,
|
|
774
|
+
instance_id: str,
|
|
775
|
+
owner_epoch: int,
|
|
776
|
+
event_text: str,
|
|
777
|
+
max_bytes: int,
|
|
778
|
+
state: str,
|
|
779
|
+
expected_recovery_dispatch_count: int | None = None,
|
|
780
|
+
response_id: str | None = None,
|
|
781
|
+
complete_spool: bool = True,
|
|
782
|
+
) -> bool:
|
|
783
|
+
async with self._session() as session:
|
|
784
|
+
return await DurableBridgeRepository(session).append_terminal_operation_event(
|
|
785
|
+
operation_id=operation_id,
|
|
786
|
+
session_id=session_id,
|
|
787
|
+
instance_id=instance_id,
|
|
788
|
+
owner_epoch=owner_epoch,
|
|
789
|
+
event_text=event_text,
|
|
790
|
+
max_bytes=max_bytes,
|
|
791
|
+
state=state,
|
|
792
|
+
expected_recovery_dispatch_count=expected_recovery_dispatch_count,
|
|
793
|
+
response_id=response_id,
|
|
794
|
+
complete_spool=complete_spool,
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
async def append_operation_events(
|
|
798
|
+
self,
|
|
799
|
+
*,
|
|
800
|
+
events: Sequence[DurableBridgeOperationEventInput],
|
|
801
|
+
max_bytes: int,
|
|
802
|
+
) -> bool:
|
|
803
|
+
async with self._session() as session:
|
|
804
|
+
return await DurableBridgeRepository(session).append_operation_events(
|
|
805
|
+
events=events,
|
|
806
|
+
max_bytes=max_bytes,
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
async def append_operation_event_chunk(
|
|
810
|
+
self,
|
|
811
|
+
*,
|
|
812
|
+
events: Sequence[DurableBridgeOperationEventInput],
|
|
813
|
+
max_bytes: int,
|
|
814
|
+
) -> bool:
|
|
815
|
+
async with self._session() as session:
|
|
816
|
+
return await DurableBridgeRepository(session).append_operation_event_chunk(
|
|
817
|
+
events=events,
|
|
818
|
+
max_bytes=max_bytes,
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
async def append_terminal_operation_chunk(
|
|
822
|
+
self,
|
|
823
|
+
*,
|
|
824
|
+
operation_id: str,
|
|
825
|
+
session_id: str,
|
|
826
|
+
instance_id: str,
|
|
827
|
+
owner_epoch: int,
|
|
828
|
+
event_text: str,
|
|
829
|
+
max_bytes: int,
|
|
830
|
+
state: str,
|
|
831
|
+
expected_recovery_dispatch_count: int | None = None,
|
|
832
|
+
response_id: str | None = None,
|
|
833
|
+
complete_spool: bool = True,
|
|
834
|
+
) -> bool:
|
|
835
|
+
async with self._session() as session:
|
|
836
|
+
return await DurableBridgeRepository(session).append_terminal_operation_chunk(
|
|
837
|
+
operation_id=operation_id,
|
|
838
|
+
session_id=session_id,
|
|
839
|
+
instance_id=instance_id,
|
|
840
|
+
owner_epoch=owner_epoch,
|
|
841
|
+
event_text=event_text,
|
|
842
|
+
max_bytes=max_bytes,
|
|
843
|
+
state=state,
|
|
844
|
+
expected_recovery_dispatch_count=expected_recovery_dispatch_count,
|
|
845
|
+
response_id=response_id,
|
|
846
|
+
complete_spool=complete_spool,
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
async def finalize_operation_event_spool(
|
|
850
|
+
self,
|
|
851
|
+
*,
|
|
852
|
+
operation_id: str,
|
|
853
|
+
session_id: str,
|
|
854
|
+
instance_id: str,
|
|
855
|
+
owner_epoch: int,
|
|
856
|
+
expected_state: str | None = None,
|
|
857
|
+
) -> bool:
|
|
858
|
+
async with self._session() as session:
|
|
859
|
+
return await DurableBridgeRepository(session).finalize_operation_event_spool(
|
|
860
|
+
operation_id=operation_id,
|
|
861
|
+
session_id=session_id,
|
|
862
|
+
instance_id=instance_id,
|
|
863
|
+
owner_epoch=owner_epoch,
|
|
864
|
+
expected_state=expected_state,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
async def settle_terminal_append_failure(
|
|
868
|
+
self,
|
|
869
|
+
*,
|
|
870
|
+
operation_id: str,
|
|
871
|
+
session_id: str,
|
|
872
|
+
instance_id: str,
|
|
873
|
+
owner_epoch: int,
|
|
874
|
+
state: str,
|
|
875
|
+
expected_response_id: str | None,
|
|
876
|
+
expected_recovery_dispatch_count: int | None = None,
|
|
877
|
+
alternate_expected_response_id: str | None = None,
|
|
878
|
+
response_id: str | None = None,
|
|
879
|
+
) -> bool:
|
|
880
|
+
async with self._session() as session:
|
|
881
|
+
return await DurableBridgeRepository(session).settle_terminal_append_failure(
|
|
882
|
+
operation_id=operation_id,
|
|
883
|
+
session_id=session_id,
|
|
884
|
+
instance_id=instance_id,
|
|
885
|
+
owner_epoch=owner_epoch,
|
|
886
|
+
state=state,
|
|
887
|
+
expected_response_id=expected_response_id,
|
|
888
|
+
expected_recovery_dispatch_count=expected_recovery_dispatch_count,
|
|
889
|
+
alternate_expected_response_id=alternate_expected_response_id,
|
|
890
|
+
response_id=response_id,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
async def update_operation(
|
|
894
|
+
self,
|
|
895
|
+
*,
|
|
896
|
+
operation_id: str,
|
|
897
|
+
session_id: str,
|
|
898
|
+
instance_id: str,
|
|
899
|
+
owner_epoch: int,
|
|
900
|
+
state: str,
|
|
901
|
+
response_id: str | None = None,
|
|
902
|
+
) -> bool:
|
|
903
|
+
async with self._session() as session:
|
|
904
|
+
return await DurableBridgeRepository(session).update_operation(
|
|
905
|
+
operation_id=operation_id,
|
|
906
|
+
session_id=session_id,
|
|
907
|
+
instance_id=instance_id,
|
|
908
|
+
owner_epoch=owner_epoch,
|
|
909
|
+
state=state,
|
|
910
|
+
response_id=response_id,
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
async def get_operation(self, *, operation_id: str) -> DurableBridgeOperationSnapshot | None:
|
|
914
|
+
async with self._session() as session:
|
|
915
|
+
return await DurableBridgeRepository(session).get_operation(operation_id=operation_id)
|
|
916
|
+
|
|
917
|
+
async def reset_operation_event_spool(
|
|
918
|
+
self,
|
|
919
|
+
*,
|
|
920
|
+
operation_id: str,
|
|
921
|
+
session_id: str,
|
|
922
|
+
instance_id: str,
|
|
923
|
+
owner_epoch: int,
|
|
924
|
+
) -> bool:
|
|
925
|
+
async with self._session() as session:
|
|
926
|
+
return await DurableBridgeRepository(session).reset_operation_event_spool(
|
|
927
|
+
operation_id=operation_id,
|
|
928
|
+
session_id=session_id,
|
|
929
|
+
instance_id=instance_id,
|
|
930
|
+
owner_epoch=owner_epoch,
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
async def claim_unknown_operation_for_recovery(
|
|
934
|
+
self,
|
|
935
|
+
*,
|
|
936
|
+
operation_id: str,
|
|
937
|
+
session_id: str,
|
|
938
|
+
instance_id: str,
|
|
939
|
+
owner_epoch: int,
|
|
940
|
+
max_recovery_dispatches: int | None = None,
|
|
941
|
+
) -> bool:
|
|
942
|
+
async with self._session() as session:
|
|
943
|
+
return await DurableBridgeRepository(session).claim_unknown_operation_for_recovery(
|
|
944
|
+
operation_id=operation_id,
|
|
945
|
+
session_id=session_id,
|
|
946
|
+
instance_id=instance_id,
|
|
947
|
+
owner_epoch=owner_epoch,
|
|
948
|
+
max_recovery_dispatches=max_recovery_dispatches,
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
async def mark_operation_unknown(
|
|
952
|
+
self,
|
|
953
|
+
*,
|
|
954
|
+
operation_id: str,
|
|
955
|
+
session_id: str,
|
|
956
|
+
instance_id: str,
|
|
957
|
+
owner_epoch: int,
|
|
958
|
+
restore_recovery_dispatch_claim: bool = False,
|
|
959
|
+
) -> bool:
|
|
960
|
+
async with self._session() as session:
|
|
961
|
+
return await DurableBridgeRepository(session).mark_operation_unknown(
|
|
962
|
+
operation_id=operation_id,
|
|
963
|
+
session_id=session_id,
|
|
964
|
+
instance_id=instance_id,
|
|
965
|
+
owner_epoch=owner_epoch,
|
|
966
|
+
restore_recovery_dispatch_claim=restore_recovery_dispatch_claim,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
async def rollback_operation_before_dispatch(
|
|
970
|
+
self,
|
|
971
|
+
*,
|
|
972
|
+
operation_id: str,
|
|
973
|
+
session_id: str,
|
|
974
|
+
instance_id: str,
|
|
975
|
+
owner_epoch: int,
|
|
976
|
+
restore_rebound: bool = False,
|
|
977
|
+
rebound_from_session_id: str | None = None,
|
|
978
|
+
rebound_from_account_id: str | None = None,
|
|
979
|
+
rebound_from_model: str | None = None,
|
|
980
|
+
rebound_from_parent_response_id: str | None = None,
|
|
981
|
+
) -> bool:
|
|
982
|
+
async with self._session() as session:
|
|
983
|
+
return await DurableBridgeRepository(session).rollback_operation_before_dispatch(
|
|
984
|
+
operation_id=operation_id,
|
|
985
|
+
session_id=session_id,
|
|
986
|
+
instance_id=instance_id,
|
|
987
|
+
owner_epoch=owner_epoch,
|
|
988
|
+
restore_rebound=restore_rebound,
|
|
989
|
+
rebound_from_session_id=rebound_from_session_id,
|
|
990
|
+
rebound_from_account_id=rebound_from_account_id,
|
|
991
|
+
rebound_from_model=rebound_from_model,
|
|
992
|
+
rebound_from_parent_response_id=rebound_from_parent_response_id,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
async def get_operation_by_fingerprint(
|
|
996
|
+
self,
|
|
997
|
+
*,
|
|
998
|
+
request_fingerprint: str,
|
|
999
|
+
api_key_scope: str | None = None,
|
|
1000
|
+
) -> DurableBridgeOperationSnapshot | None:
|
|
1001
|
+
async with self._session() as session:
|
|
1002
|
+
return await DurableBridgeRepository(session).get_operation_by_fingerprint(
|
|
1003
|
+
request_fingerprint=request_fingerprint,
|
|
1004
|
+
api_key_scope=api_key_scope,
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
async def get_latest_completed_operation(
|
|
1008
|
+
self,
|
|
1009
|
+
*,
|
|
1010
|
+
session_id: str,
|
|
1011
|
+
parent_response_id: str,
|
|
1012
|
+
request_fingerprint: str | None = None,
|
|
1013
|
+
) -> DurableBridgeOperationSnapshot | None:
|
|
1014
|
+
async with self._session() as session:
|
|
1015
|
+
return await DurableBridgeRepository(session).get_latest_completed_operation(
|
|
1016
|
+
session_id=session_id,
|
|
1017
|
+
parent_response_id=parent_response_id,
|
|
1018
|
+
request_fingerprint=request_fingerprint,
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
async def get_latest_completed_operation_any_session(
|
|
1022
|
+
self,
|
|
1023
|
+
*,
|
|
1024
|
+
parent_response_id: str,
|
|
1025
|
+
api_key_scope: str | None = None,
|
|
1026
|
+
request_fingerprint: str | None = None,
|
|
1027
|
+
) -> DurableBridgeOperationSnapshot | None:
|
|
1028
|
+
async with self._session() as session:
|
|
1029
|
+
return await DurableBridgeRepository(session).get_latest_completed_operation_any_session(
|
|
1030
|
+
parent_response_id=parent_response_id,
|
|
1031
|
+
api_key_scope=api_key_scope,
|
|
1032
|
+
request_fingerprint=request_fingerprint,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
async def retire_continuity_owner_if_unavailable(
|
|
1036
|
+
self,
|
|
1037
|
+
*,
|
|
1038
|
+
session_id: str,
|
|
1039
|
+
expected_account_id: str,
|
|
1040
|
+
recovery_deadline_epoch: int,
|
|
1041
|
+
) -> bool:
|
|
1042
|
+
"""Retire a row's owner now when it cannot return before the deadline."""
|
|
1043
|
+
async with self._session() as session:
|
|
1044
|
+
return await DurableBridgeRepository(session).retire_continuity_owner_if_unavailable(
|
|
1045
|
+
session_id,
|
|
1046
|
+
expected_account_id=expected_account_id,
|
|
1047
|
+
recovery_deadline_epoch=recovery_deadline_epoch,
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
async def mark_instance_draining(self, *, instance_id: str) -> int:
|
|
1051
|
+
async with self._session() as session:
|
|
1052
|
+
return await DurableBridgeRepository(session).mark_owner_draining(instance_id=instance_id)
|
|
1053
|
+
|
|
1054
|
+
async def purge_owned_sessions_on_startup(
|
|
1055
|
+
self,
|
|
1056
|
+
*,
|
|
1057
|
+
instance_id: str,
|
|
1058
|
+
owner_process_epoch: str | None = None,
|
|
1059
|
+
ownerless_cutoff: datetime | None = None,
|
|
1060
|
+
) -> int:
|
|
1061
|
+
async with self._session() as session:
|
|
1062
|
+
return await DurableBridgeRepository(session).purge_owned_sessions_on_startup(
|
|
1063
|
+
instance_id=instance_id,
|
|
1064
|
+
owner_process_epoch=owner_process_epoch,
|
|
1065
|
+
ownerless_cutoff=ownerless_cutoff,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
async def register_turn_state(
|
|
1069
|
+
self,
|
|
1070
|
+
*,
|
|
1071
|
+
session_id: str,
|
|
1072
|
+
api_key_id: str | None,
|
|
1073
|
+
instance_id: str,
|
|
1074
|
+
owner_epoch: int,
|
|
1075
|
+
turn_state: str,
|
|
1076
|
+
lease_ttl_seconds: float,
|
|
1077
|
+
) -> DurableBridgeAliasRegistration:
|
|
1078
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
1079
|
+
async with self._session() as session:
|
|
1080
|
+
return await DurableBridgeRepository(session).register_owned_alias(
|
|
1081
|
+
session_id=session_id,
|
|
1082
|
+
api_key_scope=api_key_scope,
|
|
1083
|
+
instance_id=instance_id,
|
|
1084
|
+
owner_epoch=owner_epoch,
|
|
1085
|
+
alias_kind=_DURABLE_TURN_STATE_ALIAS,
|
|
1086
|
+
alias_value=turn_state,
|
|
1087
|
+
lease_ttl_seconds=lease_ttl_seconds,
|
|
1088
|
+
latest_turn_state=turn_state,
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
async def register_recovery_turn_state(
|
|
1092
|
+
self,
|
|
1093
|
+
*,
|
|
1094
|
+
session_id: str,
|
|
1095
|
+
api_key_id: str | None,
|
|
1096
|
+
instance_id: str,
|
|
1097
|
+
owner_epoch: int,
|
|
1098
|
+
turn_state: str,
|
|
1099
|
+
lease_ttl_seconds: float,
|
|
1100
|
+
) -> DurableBridgeAliasRegistrationReceipt:
|
|
1101
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
1102
|
+
async with self._session() as session:
|
|
1103
|
+
return await DurableBridgeRepository(session).register_reversible_turn_state_alias(
|
|
1104
|
+
session_id=session_id,
|
|
1105
|
+
api_key_scope=api_key_scope,
|
|
1106
|
+
instance_id=instance_id,
|
|
1107
|
+
owner_epoch=owner_epoch,
|
|
1108
|
+
turn_state=turn_state,
|
|
1109
|
+
lease_ttl_seconds=lease_ttl_seconds,
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
async def rollback_recovery_turn_state_registration(
|
|
1113
|
+
self,
|
|
1114
|
+
*,
|
|
1115
|
+
receipt: DurableBridgeAliasRegistrationReceipt,
|
|
1116
|
+
) -> bool:
|
|
1117
|
+
async with self._session() as session:
|
|
1118
|
+
return await DurableBridgeRepository(session).rollback_reversible_turn_state_alias(
|
|
1119
|
+
receipt=receipt,
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
async def register_previous_response_id(
|
|
1123
|
+
self,
|
|
1124
|
+
*,
|
|
1125
|
+
session_id: str,
|
|
1126
|
+
api_key_id: str | None,
|
|
1127
|
+
instance_id: str,
|
|
1128
|
+
owner_epoch: int,
|
|
1129
|
+
response_id: str,
|
|
1130
|
+
lease_ttl_seconds: float,
|
|
1131
|
+
input_item_count: int | None = None,
|
|
1132
|
+
input_full_fingerprint: str | None = None,
|
|
1133
|
+
pending_tool_calls: Mapping[str, str] | None = None,
|
|
1134
|
+
) -> DurableBridgeAliasRegistration:
|
|
1135
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
1136
|
+
async with self._session() as session:
|
|
1137
|
+
return await DurableBridgeRepository(session).register_owned_alias(
|
|
1138
|
+
session_id=session_id,
|
|
1139
|
+
api_key_scope=api_key_scope,
|
|
1140
|
+
instance_id=instance_id,
|
|
1141
|
+
owner_epoch=owner_epoch,
|
|
1142
|
+
alias_kind=_DURABLE_PREVIOUS_RESPONSE_ALIAS,
|
|
1143
|
+
alias_value=response_id,
|
|
1144
|
+
lease_ttl_seconds=lease_ttl_seconds,
|
|
1145
|
+
latest_response_id=response_id,
|
|
1146
|
+
latest_input_item_count=input_item_count,
|
|
1147
|
+
latest_input_full_fingerprint=input_full_fingerprint,
|
|
1148
|
+
latest_pending_tool_calls=pending_tool_calls,
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
async def register_session_header(
|
|
1152
|
+
self,
|
|
1153
|
+
*,
|
|
1154
|
+
session_id: str,
|
|
1155
|
+
api_key_id: str | None,
|
|
1156
|
+
session_header: str,
|
|
1157
|
+
) -> None:
|
|
1158
|
+
api_key_scope = durable_bridge_api_key_scope(api_key_id)
|
|
1159
|
+
async with self._session() as session:
|
|
1160
|
+
await DurableBridgeRepository(session).upsert_alias(
|
|
1161
|
+
session_id=session_id,
|
|
1162
|
+
alias_kind=_DURABLE_SESSION_HEADER_ALIAS,
|
|
1163
|
+
alias_value=session_header,
|
|
1164
|
+
api_key_scope=api_key_scope,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
@asynccontextmanager
|
|
1168
|
+
async def _session(self) -> AsyncIterator[AsyncSession]:
|
|
1169
|
+
session = self._session_factory()
|
|
1170
|
+
try:
|
|
1171
|
+
yield session
|
|
1172
|
+
finally:
|
|
1173
|
+
await close_session(session)
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def _to_lookup(snapshot: DurableBridgeSessionSnapshot) -> DurableBridgeLookup:
|
|
1177
|
+
# Every lookup path funnels through here, so retirement is applied once
|
|
1178
|
+
# rather than at each call site — the detached-row filter above had to be
|
|
1179
|
+
# repeated and ended up covering only two of the four paths.
|
|
1180
|
+
#
|
|
1181
|
+
# A retired row keeps its identity (callers still need the session id and
|
|
1182
|
+
# canonical key to claim it) but surrenders every piece of continuity
|
|
1183
|
+
# evidence: the owner, the response anchor and the turn state. Dropping
|
|
1184
|
+
# the anchors alongside the owner is what makes the retirement coherent —
|
|
1185
|
+
# an anchor without its account is upstream state no replacement account
|
|
1186
|
+
# can read, and leaving it would have the next request inject a pointer
|
|
1187
|
+
# that only the retired owner could resolve.
|
|
1188
|
+
retired = snapshot.continuity_abandoned
|
|
1189
|
+
return DurableBridgeLookup(
|
|
1190
|
+
session_id=snapshot.id,
|
|
1191
|
+
canonical_kind=snapshot.session_key_kind,
|
|
1192
|
+
canonical_key=snapshot.session_key_value,
|
|
1193
|
+
api_key_scope=snapshot.api_key_scope,
|
|
1194
|
+
account_id=None if retired else snapshot.account_id,
|
|
1195
|
+
# The owning replica goes with the owner. ``_durable_bridge_lookup_active_owner``
|
|
1196
|
+
# would otherwise still name it and forward the request there, and the
|
|
1197
|
+
# takeover gates would treat the row as live. The sweep only retires
|
|
1198
|
+
# rows that have been idle for the whole grace window, so no lease can
|
|
1199
|
+
# realistically still be running — but leaving these set makes the
|
|
1200
|
+
# invariant depend on the lease TTL staying below the grace window,
|
|
1201
|
+
# which is a setting nobody would think to check before changing.
|
|
1202
|
+
owner_instance_id=None if retired else snapshot.owner_instance_id,
|
|
1203
|
+
owner_process_epoch=None if retired else snapshot.owner_process_epoch,
|
|
1204
|
+
# The epoch is fencing state, not continuity evidence: a later claim
|
|
1205
|
+
# must still advance past it, so it is preserved.
|
|
1206
|
+
owner_epoch=snapshot.owner_epoch,
|
|
1207
|
+
lease_expires_at=None if retired else snapshot.lease_expires_at,
|
|
1208
|
+
state=snapshot.state,
|
|
1209
|
+
latest_turn_state=None if retired else snapshot.latest_turn_state,
|
|
1210
|
+
latest_response_id=None if retired else snapshot.latest_response_id,
|
|
1211
|
+
latest_input_item_count=None if retired else snapshot.latest_input_item_count,
|
|
1212
|
+
latest_input_full_fingerprint=None if retired else snapshot.latest_input_full_fingerprint,
|
|
1213
|
+
model=snapshot.model,
|
|
1214
|
+
latest_pending_tool_calls=None if retired else snapshot.latest_pending_tool_calls,
|
|
1215
|
+
continuity_abandoned=retired,
|
|
1216
|
+
retired_account_id=snapshot.abandoned_account_id,
|
|
1217
|
+
)
|