From 37e5062298a625ca930e1955b119da6ceebc9ddf Mon Sep 17 00:00:00 2001 From: ding113 Date: Fri, 7 Aug 2026 03:16:39 +0800 Subject: [PATCH] feat(replay): add configurable cache TTL system setting Introduce replayCacheTtlMinutes (5-120 min, default 30) as a database-backed system setting controlling how long completed Replay payloads remain reusable in the PostgreSQL durable layer. The Redis hot-layer TTL is capped to the same window so both tiers expire in sync. This replaces the removed REPLAY_COMPLETED_TTL_SECONDS environment variable, moving the durable TTL into the admin-editable settings surface with full validation, i18n labels, API schema, and UI input. Also fix the replay cleanup query to bind the cutoff Date through sql.param for correct PostgreSQL type coercion, and preserve wrapped database error causes in the cleanup scheduler logging. --- drizzle/0119_tiresome_banshee.sql | 1 + drizzle/meta/0119_snapshot.json | 5404 +++++++++++++++++ drizzle/meta/_journal.json | 7 + messages/en/settings/config.json | 4 + messages/ja/settings/config.json | 4 + messages/ru/settings/config.json | 4 + messages/zh-CN/settings/config.json | 4 + messages/zh-TW/settings/config.json | 4 + src/actions/system-config.ts | 20 +- .../_components/system-settings-form.tsx | 73 +- src/app/[locale]/settings/config/page.tsx | 1 + src/app/api/v1/resources/system/router.ts | 4 +- src/app/v1/_lib/proxy/replay/replay-store.ts | 27 +- src/drizzle/schema.ts | 2 + src/instrumentation.ts | 16 +- src/lib/api-client/v1/openapi-types.gen.ts | 6 + src/lib/api/v1/schemas/system-config.ts | 10 + src/lib/config/env.schema.ts | 2 - src/lib/config/system-settings-cache.ts | 4 + src/lib/system-settings/proxy-runtime.ts | 5 + src/lib/validation/replay-settings.ts | 16 + src/lib/validation/schemas.ts | 12 + src/repository/_shared/transformers.test.ts | 5 + src/repository/_shared/transformers.ts | 2 + src/repository/system-config.ts | 14 + src/types/system-config.ts | 4 + tests/api/v1/system/system-config.test.ts | 21 +- tests/unit/actions/system-config-save.test.ts | 20 + .../instrumentation-replay-cleanup.test.ts | 48 +- tests/unit/proxy/replay-store.test.ts | 44 +- .../proxy/stream-gate-mode-resolution.test.ts | 44 +- .../system-config-degradation-ladder.test.ts | 27 +- ...stem-config-update-missing-columns.test.ts | 27 +- ...ettings-form-replay-cache-toggles.test.tsx | 46 + 34 files changed, 5856 insertions(+), 76 deletions(-) create mode 100644 drizzle/0119_tiresome_banshee.sql create mode 100644 drizzle/meta/0119_snapshot.json create mode 100644 src/lib/validation/replay-settings.ts diff --git a/drizzle/0119_tiresome_banshee.sql b/drizzle/0119_tiresome_banshee.sql new file mode 100644 index 000000000..4b38b09ee --- /dev/null +++ b/drizzle/0119_tiresome_banshee.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN "replay_cache_ttl_minutes" integer DEFAULT 30 NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0119_snapshot.json b/drizzle/meta/0119_snapshot.json new file mode 100644 index 000000000..39af8c9a9 --- /dev/null +++ b/drizzle/meta/0119_snapshot.json @@ -0,0 +1,5404 @@ +{ + "id": "12d909a0-d12b-4617-b7af-5a64a50201a7", + "prevId": "0a61db74-24d2-408d-b247-974f2413fa50", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "routing_trace": { + "name": "routing_trace", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_byte_ms": { + "name": "first_byte_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hedge_losers": { + "name": "hedge_losers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_image_generation_preference": { + "name": "codex_image_generation_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'CC Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_byte_ms": { + "name": "first_byte_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f09e1f133..f6b450d14 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -834,6 +834,13 @@ "when": 1785688550789, "tag": "0118_bright_sunspot", "breakpoints": true + }, + { + "idx": 119, + "version": "7", + "when": 1786038550610, + "tag": "0119_tiresome_banshee", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index d75b54fc4..c6f6a1832 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -172,6 +172,10 @@ }, "replayEnabled": "Request Replay", "replayEnabledDesc": "Caches upstream responses and reuses upstream connections: identical concurrent or reconnecting requests attach to the in-flight stream instead of re-hitting the provider. Follows the ENABLE_REQUEST_REPLAY environment variable until saved here. Default on.", + "replayCacheTtlMinutes": "Replay cache duration", + "replayCacheTtlMinutesDesc": "How long completed Replay payloads remain reusable. Applies only to payloads written afterward. Range: 5-120 minutes.", + "replayCacheTtlInvalid": "Replay cache duration must be a whole number from 5 to 120 minutes.", + "replayCacheTtlMinutesUnit": "min", "cacheEffectivenessEnabled": "Prefix Cache Simulation", "cacheEffectivenessEnabledDesc": "Simulates longest-prefix cache hit rates (theoretical vs actual) for observability only; never affects routing. Follows the ENABLE_CACHE_EFFECTIVENESS environment variable until saved here. Default on." }, diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index 8a9aa2bae..27cb07459 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -172,6 +172,10 @@ }, "replayEnabled": "リクエスト Replay", "replayEnabledDesc": "上流レスポンスをキャッシュし上流接続を再利用します。同一リクエストの並行実行や再接続は進行中のストリームに追随し、プロバイダーへ再送しません。保存するまでは環境変数 ENABLE_REQUEST_REPLAY に従います。デフォルトはオン。", + "replayCacheTtlMinutes": "Replay キャッシュ時間", + "replayCacheTtlMinutesDesc": "完了した Replay payload を再利用できる時間です。以降に書き込まれるデータにのみ適用されます。範囲: 5-120 分。", + "replayCacheTtlInvalid": "Replay キャッシュ時間には 5-120 分の整数を指定してください。", + "replayCacheTtlMinutesUnit": "分", "cacheEffectivenessEnabled": "プレフィックスキャッシュシミュレーション", "cacheEffectivenessEnabledDesc": "最長プレフィックス一致のキャッシュヒット率(理論値 vs 実測値)を観測目的でシミュレートします。ルーティングには影響しません。保存するまでは環境変数 ENABLE_CACHE_EFFECTIVENESS に従います。デフォルトはオン。" }, diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 3bc6d24f6..3171cce69 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -172,6 +172,10 @@ }, "replayEnabled": "Replay запросов", "replayEnabledDesc": "Кэширует ответы провайдера и переиспользует соединения: одинаковые параллельные или переподключающиеся запросы присоединяются к текущему потоку вместо повторного обращения к провайдеру. До сохранения следует переменной окружения ENABLE_REQUEST_REPLAY. По умолчанию включено.", + "replayCacheTtlMinutes": "Срок кэша Replay", + "replayCacheTtlMinutesDesc": "Срок повторного использования завершенных payload Replay. Применяется только к последующим записям. Диапазон: 5-120 минут.", + "replayCacheTtlInvalid": "Срок кэша Replay должен быть целым числом от 5 до 120 минут.", + "replayCacheTtlMinutesUnit": "мин", "cacheEffectivenessEnabled": "Симуляция префиксного кэша", "cacheEffectivenessEnabledDesc": "Симулирует хит-рейт кэша по наибольшему префиксу (теория и факт) только для наблюдаемости; не влияет на маршрутизацию. До сохранения следует переменной окружения ENABLE_CACHE_EFFECTIVENESS. По умолчанию включено." }, diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index 51dd007fb..6b3e4ed08 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -96,6 +96,10 @@ }, "replayEnabled": "请求 Replay", "replayEnabledDesc": "缓存上游响应并复用上游连接:并发或断线重连的相同请求直接跟尾在途流,不再重复请求供应商。保存前跟随环境变量 ENABLE_REQUEST_REPLAY,默认开启。", + "replayCacheTtlMinutes": "Replay 缓存时间", + "replayCacheTtlMinutesDesc": "已完成 Replay payload 的可重放时间,仅影响之后写入的数据。范围 5-120 分钟。", + "replayCacheTtlInvalid": "Replay 缓存时间必须是 5-120 分钟内的整数。", + "replayCacheTtlMinutesUnit": "分钟", "cacheEffectivenessEnabled": "前缀缓存模拟", "cacheEffectivenessEnabledDesc": "模拟最长前缀匹配的缓存命中率(理论 vs 实际),仅用于观测,不影响路由。保存前跟随环境变量 ENABLE_CACHE_EFFECTIVENESS,默认开启。", "affinityIgnoreClientSessionId": "忽略客户端 Session ID", diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index b233d57ce..45b9debf2 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -172,6 +172,10 @@ }, "replayEnabled": "請求 Replay", "replayEnabledDesc": "快取上游回應並重用上游連線:並發或斷線重連的相同請求直接跟尾在途串流,不再重複請求供應商。儲存前跟隨環境變數 ENABLE_REQUEST_REPLAY,預設開啟。", + "replayCacheTtlMinutes": "Replay 快取時間", + "replayCacheTtlMinutesDesc": "已完成 Replay payload 的可重播時間,僅影響之後寫入的資料。範圍 5-120 分鐘。", + "replayCacheTtlInvalid": "Replay 快取時間必須是 5-120 分鐘內的整數。", + "replayCacheTtlMinutesUnit": "分鐘", "cacheEffectivenessEnabled": "前綴快取模擬", "cacheEffectivenessEnabledDesc": "模擬最長前綴匹配的快取命中率(理論 vs 實際),僅用於觀測,不影響路由。儲存前跟隨環境變數 ENABLE_CACHE_EFFECTIVENESS,預設開啟。" }, diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index 639f6dbe0..d673e45fa 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -20,6 +20,10 @@ import { DISCOVERY_WINDOW_INVALID_ERROR_CODE, getDiscoveryValidationErrorCode, } from "@/lib/validation/discovery-settings"; +import { + getReplayCacheTtlValidationErrorCode, + REPLAY_CACHE_TTL_INVALID_ERROR_CODE, +} from "@/lib/validation/replay-settings"; import { UpdateSystemSettingsSchema } from "@/lib/validation/schemas"; import { getSystemSettings, updateSystemSettings } from "@/repository/system-config"; import type { IpExtractionConfig } from "@/types/ip-extraction"; @@ -32,9 +36,13 @@ import type { } from "@/types/system-config"; import type { ActionResult } from "./types"; -function discoveryValidationErrorCode(error: unknown): string | null { +function systemSettingsValidationErrorCode(error: unknown): string | null { if (!(error instanceof ZodError)) return null; - return getDiscoveryValidationErrorCode(error.issues) ?? null; + return ( + getDiscoveryValidationErrorCode(error.issues) ?? + getReplayCacheTtlValidationErrorCode(error.issues) ?? + null + ); } export async function fetchSystemSettings(): Promise> { @@ -106,6 +114,7 @@ export async function saveSystemSettings(formData: { streamGateMode?: StreamGateSettingMode; affinityIgnoreClientSessionId?: boolean; replayEnabled?: boolean | null; + replayCacheTtlMinutes?: number; cacheEffectivenessEnabled?: boolean | null; enableCodexSessionIdCompletion?: boolean; enableClaudeMetadataUserIdInjection?: boolean; @@ -196,6 +205,7 @@ export async function saveSystemSettings(formData: { streamGateMode: validated.streamGateMode, affinityIgnoreClientSessionId: validated.affinityIgnoreClientSessionId, replayEnabled: validated.replayEnabled, + replayCacheTtlMinutes: validated.replayCacheTtlMinutes, cacheEffectivenessEnabled: validated.cacheEffectivenessEnabled, enableCodexSessionIdCompletion: validated.enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection: validated.enableClaudeMetadataUserIdInjection, @@ -297,9 +307,11 @@ export async function saveSystemSettings(formData: { return { ok: true, data: { ...updated, publicStatusProjectionWarningCode } }; } catch (error) { logger.error("更新系统设置失败:", error); - const validationErrorCode = discoveryValidationErrorCode(error); + const validationErrorCode = systemSettingsValidationErrorCode(error); const message = validationErrorCode - ? "Discovery settings validation failed." + ? validationErrorCode === REPLAY_CACHE_TTL_INVALID_ERROR_CODE + ? "Replay cache TTL validation failed." + : "Discovery settings validation failed." : error instanceof Error ? error.message : "更新系统设置失败"; diff --git a/src/app/[locale]/settings/config/_components/system-settings-form.tsx b/src/app/[locale]/settings/config/_components/system-settings-form.tsx index 8a371b85c..789c01b02 100644 --- a/src/app/[locale]/settings/config/_components/system-settings-form.tsx +++ b/src/app/[locale]/settings/config/_components/system-settings-form.tsx @@ -52,6 +52,11 @@ import { shouldWarnQuotaLeasePercentZero, } from "@/lib/utils/validation/quota-lease-warnings"; import { DISCOVERY_FIELD_LIMITS } from "@/lib/validation/discovery-settings"; +import { + REPLAY_CACHE_TTL_INVALID_ERROR_CODE, + REPLAY_CACHE_TTL_MINUTES_MAX, + REPLAY_CACHE_TTL_MINUTES_MIN, +} from "@/lib/validation/replay-settings"; import { DEFAULT_IP_EXTRACTION_CONFIG, type IpExtractionConfig } from "@/types/ip-extraction"; import type { BillingModelSource, @@ -100,6 +105,7 @@ interface SystemSettingsFormProps { | "streamGateMode" | "affinityIgnoreClientSessionId" | "replayEnabled" + | "replayCacheTtlMinutes" | "cacheEffectivenessEnabled" | "enableCodexSessionIdCompletion" | "enableClaudeMetadataUserIdInjection" @@ -220,6 +226,9 @@ export function SystemSettingsForm({ // null = 跟随环境变量:未触碰开关时按 null 原样保存, // 避免无关字段的保存把覆写写死为布尔值;仅用户切换后才落显式值 const [replayEnabled, setReplayEnabled] = useState(initialSettings.replayEnabled); + const [replayCacheTtlMinutes, setReplayCacheTtlMinutes] = useState( + String(initialSettings.replayCacheTtlMinutes) + ); const [cacheEffectivenessEnabled, setCacheEffectivenessEnabled] = useState( initialSettings.cacheEffectivenessEnabled ); @@ -409,6 +418,7 @@ export function SystemSettingsForm({ streamGateMode, affinityIgnoreClientSessionId, replayEnabled, + replayCacheTtlMinutes: Number(replayCacheTtlMinutes), cacheEffectivenessEnabled, enableThinkingBudgetRectifier, enableThinkingEffortConflictRectifier, @@ -433,7 +443,9 @@ export function SystemSettingsForm({ ? t("discoveryWindowInvalid") : result.errorCode === "DISCOVERY_SETTINGS_INVALID" ? t("discoverySettingsInvalid") - : result.error || t("saveFailed"); + : result.errorCode === REPLAY_CACHE_TTL_INVALID_ERROR_CODE + ? t("replayCacheTtlInvalid") + : result.error || t("saveFailed"); toast.error(errorMessage); return; } @@ -475,6 +487,7 @@ export function SystemSettingsForm({ setStreamGateMode(result.data.streamGateMode); setAffinityIgnoreClientSessionId(result.data.affinityIgnoreClientSessionId); setReplayEnabled(result.data.replayEnabled ?? null); + setReplayCacheTtlMinutes(String(result.data.replayCacheTtlMinutes)); setCacheEffectivenessEnabled(result.data.cacheEffectivenessEnabled ?? null); setEnableThinkingBudgetRectifier(result.data.enableThinkingBudgetRectifier); setEnableThinkingEffortConflictRectifier(result.data.enableThinkingEffortConflictRectifier); @@ -743,11 +756,11 @@ export function SystemSettingsForm({ {/* Bounded Streaming Discovery */}
-
+
-
+

{t("discoveryEnabled")}

{t("discoveryEnabledDesc")}

@@ -1177,23 +1190,51 @@ export function SystemSettingsForm({
{/* F2 Request Replay */} -
-
-
- +
+
+
+
+ +
+
+

{t("replayEnabled")}

+

{t("replayEnabledDesc")}

+
+ setReplayEnabled(checked)} + disabled={isPending} + /> +
+
-

{t("replayEnabled")}

-

{t("replayEnabledDesc")}

+ +

+ {t("replayCacheTtlMinutesDesc")} +

+
+
+ setReplayCacheTtlMinutes(event.target.value)} + disabled={isPending} + className={`${inputClassName} max-sm:text-base`} + /> +

+ {t("replayCacheTtlMinutesUnit")} +

- setReplayEnabled(checked)} - disabled={isPending} - />
{/* F3b Cache Effectiveness Simulation */} diff --git a/src/app/[locale]/settings/config/page.tsx b/src/app/[locale]/settings/config/page.tsx index c66ae4e87..c32f4e579 100644 --- a/src/app/[locale]/settings/config/page.tsx +++ b/src/app/[locale]/settings/config/page.tsx @@ -83,6 +83,7 @@ async function SettingsConfigContent({ locale }: { locale: string }) { streamGateMode: settings.streamGateMode, affinityIgnoreClientSessionId: settings.affinityIgnoreClientSessionId, replayEnabled: settings.replayEnabled, + replayCacheTtlMinutes: settings.replayCacheTtlMinutes, cacheEffectivenessEnabled: settings.cacheEffectivenessEnabled, enableCodexSessionIdCompletion: settings.enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection: settings.enableClaudeMetadataUserIdInjection, diff --git a/src/app/api/v1/resources/system/router.ts b/src/app/api/v1/resources/system/router.ts index 3e693e98f..b3962aa50 100644 --- a/src/app/api/v1/resources/system/router.ts +++ b/src/app/api/v1/resources/system/router.ts @@ -10,6 +10,7 @@ import { SystemTimezoneResponseSchema, } from "@/lib/api/v1/schemas/system-config"; import { getDiscoveryValidationErrorCode } from "@/lib/validation/discovery-settings"; +import { getReplayCacheTtlValidationErrorCode } from "@/lib/validation/replay-settings"; import { getSystemDisplaySettings, getSystemSettings, @@ -23,7 +24,8 @@ export const systemRouter = new OpenAPIHono({ return fromZodError( result.error, new URL(c.req.url).pathname, - getDiscoveryValidationErrorCode(result.error.issues) + getDiscoveryValidationErrorCode(result.error.issues) ?? + getReplayCacheTtlValidationErrorCode(result.error.issues) ); } }, diff --git a/src/app/v1/_lib/proxy/replay/replay-store.ts b/src/app/v1/_lib/proxy/replay/replay-store.ts index a58875a96..02a6c44fb 100644 --- a/src/app/v1/_lib/proxy/replay/replay-store.ts +++ b/src/app/v1/_lib/proxy/replay/replay-store.ts @@ -9,6 +9,12 @@ import { logger } from "@/lib/logger"; import { getRedisClient } from "@/lib/redis/client"; import { RedisKVStore } from "@/lib/redis/redis-kv-store"; import { RedisListStore } from "@/lib/redis/redis-list-store"; +import { getCachedProxyRuntimeSettings } from "@/lib/system-settings/proxy-runtime"; +import { + REPLAY_CACHE_TTL_MINUTES_DEFAULT, + REPLAY_CACHE_TTL_MINUTES_MAX, + REPLAY_CACHE_TTL_MINUTES_MIN, +} from "@/lib/validation/replay-settings"; /** * F2 Replay 双层存储: @@ -373,9 +379,8 @@ export class ReplayStore { * (过期行清理由 instrumentation 定时调度器负责,不在写路径顺带执行。) */ async persistCompleted(row: ReplayPersistedRow): Promise<"persisted" | "existing"> { - const env = getEnvConfig(); const now = new Date(); - const expiresAt = new Date(now.getTime() + env.REPLAY_COMPLETED_TTL_SECONDS * 1000); + const expiresAt = new Date(now.getTime() + resolveReplayCompletedTtlSeconds() * 1000); const persistedValues = { verifier: row.verifier, scopeTag: row.scopeTag, @@ -434,7 +439,7 @@ export class ReplayStore { WITH doomed AS ( SELECT replay_id FROM replay_payloads - WHERE expires_at < ${cutoff} + WHERE expires_at < ${sql.param(cutoff, replayPayloads.expiresAt)} ORDER BY expires_at, replay_id LIMIT ${REPLAY_CLEANUP_BATCH_SIZE} FOR UPDATE SKIP LOCKED @@ -466,13 +471,25 @@ export class ReplayStore { } export function resolveReplayTtlSeconds(): number { + const completedTtlSeconds = resolveReplayCompletedTtlSeconds(); try { - return getEnvConfig().REPLAY_TTL_SECONDS; + return Math.min(getEnvConfig().REPLAY_TTL_SECONDS, completedTtlSeconds); } catch { - return 600; + return Math.min(600, completedTtlSeconds); } } +export function resolveReplayCompletedTtlSeconds(): number { + const minutes = + getCachedProxyRuntimeSettings()?.replayCacheTtlMinutes ?? REPLAY_CACHE_TTL_MINUTES_DEFAULT; + return ( + Math.max( + REPLAY_CACHE_TTL_MINUTES_MIN, + Math.min(REPLAY_CACHE_TTL_MINUTES_MAX, Math.trunc(minutes)) + ) * 60 + ); +} + let sharedReplayStore: ReplayStore | null = null; export function getReplayStore(): ReplayStore { diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index f0684ff8e..60f497c0a 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -1056,6 +1056,8 @@ export const systemSettings = pgTable('system_settings', { // F2 Replay 开关覆写(null = 跟随环境变量 ENABLE_REQUEST_REPLAY) replayEnabled: boolean('replay_enabled'), + // F2 Replay 完成 payload 的可重放窗口(分钟,默认 30) + replayCacheTtlMinutes: integer('replay_cache_ttl_minutes').notNull().default(30), // F3b 最长前缀匹配缓存模拟开关覆写(null = 跟随环境变量 ENABLE_CACHE_EFFECTIVENESS) cacheEffectivenessEnabled: boolean('cache_effectiveness_enabled'), diff --git a/src/instrumentation.ts b/src/instrumentation.ts index 192eb0ac1..45b0b23b6 100644 --- a/src/instrumentation.ts +++ b/src/instrumentation.ts @@ -328,11 +328,17 @@ export async function startReplayCleanupScheduler(): Promise { const intervalMs = 10 * 60 * 1000; const runTick = () => { - void runReplayCleanupTick().catch((error) => { - logger.warn("[Instrumentation] Replay cleanup tick failed", { - error: error instanceof Error ? error.message : String(error), + void runReplayCleanupTick() + .then((result) => { + if (result.deleted > 0) { + logger.info("[Instrumentation] Replay cleanup tick completed", result); + } + }) + .catch((error) => { + logger.warn("[Instrumentation] Replay cleanup tick failed", { + ...describeSchedulerError(error), + }); }); - }); }; runTick(); @@ -344,7 +350,7 @@ export async function startReplayCleanupScheduler(): Promise { }); } catch (error) { logger.warn("[Instrumentation] Replay cleanup scheduler init failed", { - error: error instanceof Error ? error.message : String(error), + ...describeSchedulerError(error), }); } } diff --git a/src/lib/api-client/v1/openapi-types.gen.ts b/src/lib/api-client/v1/openapi-types.gen.ts index 82c1038ef..01539443a 100644 --- a/src/lib/api-client/v1/openapi-types.gen.ts +++ b/src/lib/api-client/v1/openapi-types.gen.ts @@ -12262,6 +12262,8 @@ export interface operations { affinityIgnoreClientSessionId: boolean; /** @description Request replay (response caching and upstream connection reuse) override. Null follows the ENABLE_REQUEST_REPLAY environment variable. */ replayEnabled: boolean | null; + /** @description Replay completed payload reuse window in minutes. */ + replayCacheTtlMinutes: number; /** @description Longest-prefix cache-effectiveness simulation override (observability only). Null follows the ENABLE_CACHE_EFFECTIVENESS environment variable. */ cacheEffectivenessEnabled: boolean | null; /** @@ -12548,6 +12550,8 @@ export interface operations { affinityIgnoreClientSessionId?: boolean; /** @description Request replay (response caching and upstream connection reuse) override. Null follows the ENABLE_REQUEST_REPLAY environment variable. */ replayEnabled?: boolean | null; + /** @description Replay completed payload reuse window in minutes. */ + replayCacheTtlMinutes?: number; /** @description Longest-prefix cache-effectiveness simulation override (observability only). Null follows the ENABLE_CACHE_EFFECTIVENESS environment variable. */ cacheEffectivenessEnabled?: boolean | null; }; @@ -12709,6 +12713,8 @@ export interface operations { affinityIgnoreClientSessionId: boolean; /** @description Request replay (response caching and upstream connection reuse) override. Null follows the ENABLE_REQUEST_REPLAY environment variable. */ replayEnabled: boolean | null; + /** @description Replay completed payload reuse window in minutes. */ + replayCacheTtlMinutes: number; /** @description Longest-prefix cache-effectiveness simulation override (observability only). Null follows the ENABLE_CACHE_EFFECTIVENESS environment variable. */ cacheEffectivenessEnabled: boolean | null; /** diff --git a/src/lib/api/v1/schemas/system-config.ts b/src/lib/api/v1/schemas/system-config.ts index c3c06dd70..5dd0253a6 100644 --- a/src/lib/api/v1/schemas/system-config.ts +++ b/src/lib/api/v1/schemas/system-config.ts @@ -4,6 +4,10 @@ import { DISCOVERY_FIELD_LIMITS, DISCOVERY_SETTINGS_INVALID_ERROR_CODE, } from "@/lib/validation/discovery-settings"; +import { + REPLAY_CACHE_TTL_MINUTES_MAX, + REPLAY_CACHE_TTL_MINUTES_MIN, +} from "@/lib/validation/replay-settings"; import { IsoDateTimeStringSchema } from "./_common"; const currencyValues = Object.keys(CURRENCY_CONFIG) as [ @@ -228,6 +232,12 @@ export const SystemSettingsSchema = z .describe( "Request replay (response caching and upstream connection reuse) override. Null follows the ENABLE_REQUEST_REPLAY environment variable." ), + replayCacheTtlMinutes: z + .number() + .int() + .min(REPLAY_CACHE_TTL_MINUTES_MIN) + .max(REPLAY_CACHE_TTL_MINUTES_MAX) + .describe("Replay completed payload reuse window in minutes."), cacheEffectivenessEnabled: z .boolean() .nullable() diff --git a/src/lib/config/env.schema.ts b/src/lib/config/env.schema.ts index f0bcdff90..7193f9549 100644 --- a/src/lib/config/env.schema.ts +++ b/src/lib/config/env.schema.ts @@ -205,8 +205,6 @@ export const EnvSchema = z.object({ REPLAY_MAX_CONCURRENT_SPOOLS: z.coerce.number().int().min(1).max(1024).default(64), // Redis 热层 TTL(活跃/刚完成的响应块与元数据) REPLAY_TTL_SECONDS: z.coerce.number().int().min(60).max(7200).default(600), - // PG 完成持久层 TTL(跨小时级重放窗口) - REPLAY_COMPLETED_TTL_SECONDS: z.coerce.number().int().min(300).max(86400).default(3600), // 单响应缓存上限(超限即放弃 spool,fail-open 回现状) REPLAY_MAX_PAYLOAD_BYTES: z.coerce .number() diff --git a/src/lib/config/system-settings-cache.ts b/src/lib/config/system-settings-cache.ts index 8f8cc0511..ec5032b9f 100644 --- a/src/lib/config/system-settings-cache.ts +++ b/src/lib/config/system-settings-cache.ts @@ -14,6 +14,7 @@ import { logger } from "@/lib/logger"; import { DEFAULT_SITE_TITLE } from "@/lib/site-title"; +import { REPLAY_CACHE_TTL_MINUTES_DEFAULT } from "@/lib/validation/replay-settings"; import { getSystemSettings } from "@/repository/system-config"; import type { SystemSettings } from "@/types/system-config"; import { getEnvConfig } from "./env.schema"; @@ -108,6 +109,7 @@ export const DEFAULT_SETTINGS: Pick< | "publicStatusAggregationIntervalMinutes" | "streamGateMode" | "affinityIgnoreClientSessionId" + | "replayCacheTtlMinutes" | "discoveryEnabled" | "discoveryConcurrency" | "maxDiscoveryRounds" @@ -147,6 +149,7 @@ export const DEFAULT_SETTINGS: Pick< publicStatusAggregationIntervalMinutes: 5, streamGateMode: "enforce", affinityIgnoreClientSessionId: true, + replayCacheTtlMinutes: REPLAY_CACHE_TTL_MINUTES_DEFAULT, discoveryEnabled: false, discoveryConcurrency: 2, maxDiscoveryRounds: 2, @@ -239,6 +242,7 @@ export async function getCachedSystemSettings(): Promise { streamGateMode: getFallbackStreamGateMode(), affinityIgnoreClientSessionId: DEFAULT_SETTINGS.affinityIgnoreClientSessionId, replayEnabled: null, + replayCacheTtlMinutes: DEFAULT_SETTINGS.replayCacheTtlMinutes, cacheEffectivenessEnabled: null, discoveryEnabled: DEFAULT_SETTINGS.discoveryEnabled, discoveryConcurrency: DEFAULT_SETTINGS.discoveryConcurrency, diff --git a/src/lib/system-settings/proxy-runtime.ts b/src/lib/system-settings/proxy-runtime.ts index 94d1a5604..48c17e3d6 100644 --- a/src/lib/system-settings/proxy-runtime.ts +++ b/src/lib/system-settings/proxy-runtime.ts @@ -2,6 +2,7 @@ import "server-only"; import { getEnvConfig } from "@/lib/config/env.schema"; import { getCachedSystemSettings } from "@/lib/config/system-settings-cache"; +import { REPLAY_CACHE_TTL_MINUTES_DEFAULT } from "@/lib/validation/replay-settings"; /** * 代理热路径消费的系统设置快照。 @@ -20,6 +21,7 @@ export interface ProxyRuntimeSettings { streamGateMode: "off" | "shadow" | "enforce"; affinityIgnoreClientSessionId: boolean; replayEnabled: boolean; + replayCacheTtlMinutes: number; cacheEffectivenessEnabled: boolean; } @@ -49,6 +51,7 @@ function envFallback(): ProxyRuntimeSettings { streamGateMode: env.STREAM_GATE_MODE, affinityIgnoreClientSessionId: true, replayEnabled: env.ENABLE_REQUEST_REPLAY, + replayCacheTtlMinutes: REPLAY_CACHE_TTL_MINUTES_DEFAULT, cacheEffectivenessEnabled: env.ENABLE_CACHE_EFFECTIVENESS, }; } catch { @@ -56,6 +59,7 @@ function envFallback(): ProxyRuntimeSettings { streamGateMode: "off", affinityIgnoreClientSessionId: true, replayEnabled: false, + replayCacheTtlMinutes: REPLAY_CACHE_TTL_MINUTES_DEFAULT, cacheEffectivenessEnabled: true, }; } @@ -68,6 +72,7 @@ export async function getProxyRuntimeSettings(): Promise { streamGateMode: settings.streamGateMode, affinityIgnoreClientSessionId: settings.affinityIgnoreClientSessionId, replayEnabled: settings.replayEnabled ?? envReplayDefault(), + replayCacheTtlMinutes: settings.replayCacheTtlMinutes ?? REPLAY_CACHE_TTL_MINUTES_DEFAULT, cacheEffectivenessEnabled: settings.cacheEffectivenessEnabled ?? envCacheEffectivenessDefault(), }; diff --git a/src/lib/validation/replay-settings.ts b/src/lib/validation/replay-settings.ts new file mode 100644 index 000000000..c625e6c15 --- /dev/null +++ b/src/lib/validation/replay-settings.ts @@ -0,0 +1,16 @@ +export const REPLAY_CACHE_TTL_MINUTES_DEFAULT = 30; +export const REPLAY_CACHE_TTL_MINUTES_MIN = 5; +export const REPLAY_CACHE_TTL_MINUTES_MAX = 120; +export const REPLAY_CACHE_TTL_INVALID_ERROR_CODE = "REPLAY_CACHE_TTL_INVALID"; + +export function getReplayCacheTtlValidationErrorCode( + issues: ReadonlyArray<{ message: string; path: readonly PropertyKey[] }> +): string | undefined { + return issues.some( + (issue) => + issue.path[0] === "replayCacheTtlMinutes" || + issue.message === REPLAY_CACHE_TTL_INVALID_ERROR_CODE + ) + ? REPLAY_CACHE_TTL_INVALID_ERROR_CODE + : undefined; +} diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index c5bba2c71..58c259857 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -21,6 +21,11 @@ import { DISCOVERY_SETTINGS_INVALID_ERROR_CODE, DISCOVERY_WINDOW_INVALID_ERROR_CODE, } from "./discovery-settings"; +import { + REPLAY_CACHE_TTL_INVALID_ERROR_CODE, + REPLAY_CACHE_TTL_MINUTES_MAX, + REPLAY_CACHE_TTL_MINUTES_MIN, +} from "./replay-settings"; export { DISCOVERY_SETTINGS_INVALID_ERROR_CODE, @@ -1120,6 +1125,13 @@ export const UpdateSystemSettingsSchema = z affinityIgnoreClientSessionId: z.boolean().optional(), // F2 Replay 响应缓存与复用(可选;null = 跟随环境变量) replayEnabled: z.boolean().nullable().optional(), + // F2 Replay 完成 payload 可重放窗口(分钟) + replayCacheTtlMinutes: z.coerce + .number() + .int(REPLAY_CACHE_TTL_INVALID_ERROR_CODE) + .min(REPLAY_CACHE_TTL_MINUTES_MIN, REPLAY_CACHE_TTL_INVALID_ERROR_CODE) + .max(REPLAY_CACHE_TTL_MINUTES_MAX, REPLAY_CACHE_TTL_INVALID_ERROR_CODE) + .optional(), // F3b 最长前缀匹配缓存模拟(可选;null = 跟随环境变量) cacheEffectivenessEnabled: z.boolean().nullable().optional(), // Codex Session ID 补全(可选) diff --git a/src/repository/_shared/transformers.test.ts b/src/repository/_shared/transformers.test.ts index f80c37498..b9c515058 100644 --- a/src/repository/_shared/transformers.test.ts +++ b/src/repository/_shared/transformers.test.ts @@ -291,10 +291,15 @@ describe("src/repository/_shared/transformers.ts", () => { expect(result.enableHttp2).toBe(false); expect(result.enableOpenaiResponsesWebsocket).toBe(true); expect(result.interceptAnthropicWarmupRequests).toBe(false); + expect(result.replayCacheTtlMinutes).toBe(30); expect(result.createdAt).toEqual(now); expect(result.updatedAt).toEqual(now); }); + it("应映射 Replay 缓存时间", () => { + expect(toSystemSettings({ replayCacheTtlMinutes: 45 }).replayCacheTtlMinutes).toBe(45); + }); + it("应映射 interceptAnthropicWarmupRequests 字段", () => { const result = toSystemSettings({ id: 1, diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index 474d19b8d..f401d6485 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -2,6 +2,7 @@ import { PROVIDER_TIMEOUT_DEFAULTS } from "@/lib/constants/provider.constants"; import { normalizeProviderModelRedirectRules } from "@/lib/provider-model-redirects"; import { DEFAULT_SITE_TITLE } from "@/lib/site-title"; import { formatCostForStorage } from "@/lib/utils/currency"; +import { REPLAY_CACHE_TTL_MINUTES_DEFAULT } from "@/lib/validation/replay-settings"; import type { Key } from "@/types/key"; import type { MessageRequest } from "@/types/message"; import type { ModelPrice } from "@/types/model-price"; @@ -320,6 +321,7 @@ export function toSystemSettings(dbSettings: any): SystemSettings { : "enforce", affinityIgnoreClientSessionId: dbSettings?.affinityIgnoreClientSessionId ?? true, replayEnabled: dbSettings?.replayEnabled ?? null, + replayCacheTtlMinutes: dbSettings?.replayCacheTtlMinutes ?? REPLAY_CACHE_TTL_MINUTES_DEFAULT, cacheEffectivenessEnabled: dbSettings?.cacheEffectivenessEnabled ?? null, createdAt: dbSettings?.createdAt ? new Date(dbSettings.createdAt) : new Date(), updatedAt: dbSettings?.updatedAt ? new Date(dbSettings.updatedAt) : new Date(), diff --git a/src/repository/system-config.ts b/src/repository/system-config.ts index ae2a0eb1d..c26d8fcba 100644 --- a/src/repository/system-config.ts +++ b/src/repository/system-config.ts @@ -6,6 +6,7 @@ import { db } from "@/drizzle/db"; import { systemSettings } from "@/drizzle/schema"; import { logger } from "@/lib/logger"; import { DEFAULT_SITE_TITLE } from "@/lib/site-title"; +import { REPLAY_CACHE_TTL_MINUTES_DEFAULT } from "@/lib/validation/replay-settings"; import { DEFAULT_FAKE_STREAMING_WHITELIST, type SystemSettings, @@ -203,6 +204,7 @@ function createFallbackSettings(): SystemSettings { streamGateMode: "enforce", affinityIgnoreClientSessionId: true, replayEnabled: null, + replayCacheTtlMinutes: REPLAY_CACHE_TTL_MINUTES_DEFAULT, cacheEffectivenessEnabled: null, createdAt: now, updatedAt: now, @@ -282,6 +284,12 @@ const RECENT_COLUMN_LADDER: ReadonlyArray<{ // 本层更新失败(仍有列缺失)时记录的告警 updateWarn: string; }> = [ + { + key: "replayCacheTtlMinutes", + column: systemSettings.replayCacheTtlMinutes, + selectWarn: "system_settings 缺少 replayCacheTtlMinutes,回退到上一代字段集.", + updateWarn: "system_settings 缺少 replayCacheTtlMinutes,继续降级更新.", + }, { key: "cacheEffectivenessEnabled", column: systemSettings.cacheEffectivenessEnabled, @@ -554,6 +562,7 @@ export async function getSystemSettings(): Promise { enableHighConcurrencyMode: false, publicStatusWindowHours: 24, publicStatusAggregationIntervalMinutes: 5, + replayCacheTtlMinutes: REPLAY_CACHE_TTL_MINUTES_DEFAULT, }) .onConflictDoNothing(); } catch (error) { @@ -896,6 +905,11 @@ export async function updateSystemSettings( updates.replayEnabled = payload.replayEnabled; } + // F2 Replay 完成 payload 可重放窗口(分钟) + if (payload.replayCacheTtlMinutes !== undefined) { + updates.replayCacheTtlMinutes = payload.replayCacheTtlMinutes; + } + // F3b 缓存模拟开关覆写(如果提供;null = 清除覆写跟随环境变量) if (payload.cacheEffectivenessEnabled !== undefined) { updates.cacheEffectivenessEnabled = payload.cacheEffectivenessEnabled; diff --git a/src/types/system-config.ts b/src/types/system-config.ts index 401642f5d..a1eea2b3a 100644 --- a/src/types/system-config.ts +++ b/src/types/system-config.ts @@ -161,6 +161,8 @@ export interface SystemSettings { // F2 Replay(响应缓存与上游连接复用)开关覆写 // null = 跟随环境变量 ENABLE_REQUEST_REPLAY(默认 true) replayEnabled: boolean | null; + // F2 Replay 完成 payload 的可重放窗口(分钟) + replayCacheTtlMinutes: number; // F3b 最长前缀匹配缓存模拟(理论 vs 实际缓存命中率,仅观测不影响路由)开关覆写 // null = 跟随环境变量 ENABLE_CACHE_EFFECTIVENESS(默认 true) @@ -295,6 +297,8 @@ export interface UpdateSystemSettingsInput { // F2 Replay 开关(可选;null = 清除覆写跟随环境变量) replayEnabled?: boolean | null; + // F2 Replay 完成 payload 的可重放窗口(分钟) + replayCacheTtlMinutes?: number; // F3b 缓存模拟开关(可选;null = 清除覆写跟随环境变量) cacheEffectivenessEnabled?: boolean | null; diff --git a/tests/api/v1/system/system-config.test.ts b/tests/api/v1/system/system-config.test.ts index 2409e9e3b..c16d0eac6 100644 --- a/tests/api/v1/system/system-config.test.ts +++ b/tests/api/v1/system/system-config.test.ts @@ -78,6 +78,7 @@ const settings: SystemSettings = { quotaLeaseCapUsd: null, publicStatusWindowHours: 24, publicStatusAggregationIntervalMinutes: 5, + replayCacheTtlMinutes: 30, ipExtractionConfig: null, ipGeoLookupEnabled: true, createdAt: new Date("2026-04-28T00:00:00.000Z"), @@ -92,7 +93,7 @@ describe("v1 system config endpoints", () => { getSystemSettingsRepoMock.mockResolvedValue(settings); saveSystemSettingsMock.mockResolvedValue({ ok: true, - data: { ...settings, siteTitle: "CCH Ops", timezone: "UTC" }, + data: { ...settings, siteTitle: "CCH Ops", timezone: "UTC", replayCacheTtlMinutes: 45 }, }); getServerTimeZoneMock.mockResolvedValue({ ok: true, data: { timeZone: "Asia/Shanghai" } }); }); @@ -113,13 +114,18 @@ describe("v1 system config endpoints", () => { method: "PUT", pathname: "/api/v1/system/settings", headers: { Authorization: "Bearer admin-token" }, - body: { siteTitle: "CCH Ops", timezone: "UTC" }, + body: { siteTitle: "CCH Ops", timezone: "UTC", replayCacheTtlMinutes: 45 }, }); expect(updated.response.status).toBe(200); - expect(updated.json).toMatchObject({ siteTitle: "CCH Ops", timezone: "UTC" }); + expect(updated.json).toMatchObject({ + siteTitle: "CCH Ops", + timezone: "UTC", + replayCacheTtlMinutes: 45, + }); expect(saveSystemSettingsMock).toHaveBeenCalledWith({ siteTitle: "CCH Ops", timezone: "UTC", + replayCacheTtlMinutes: 45, }); }); @@ -174,6 +180,15 @@ describe("v1 system config endpoints", () => { }); expect(invalidTimezone.response.status).toBe(400); expect(invalidTimezone.json).toMatchObject({ errorCode: "request.validation_failed" }); + + const invalidReplayTtl = await callV1Route({ + method: "PUT", + pathname: "/api/v1/system/settings", + headers: { Authorization: "Bearer admin-token" }, + body: { replayCacheTtlMinutes: 4 }, + }); + expect(invalidReplayTtl.response.status).toBe(400); + expect(invalidReplayTtl.json).toMatchObject({ errorCode: "REPLAY_CACHE_TTL_INVALID" }); }); test("returns a stable error code for out-of-range Discovery settings", async () => { diff --git a/tests/unit/actions/system-config-save.test.ts b/tests/unit/actions/system-config-save.test.ts index 89c19dd30..ebc5cc160 100644 --- a/tests/unit/actions/system-config-save.test.ts +++ b/tests/unit/actions/system-config-save.test.ts @@ -152,6 +152,26 @@ describe("saveSystemSettings", () => { ); }); + it("accepts and forwards the Replay cache TTL", async () => { + const result = await saveSystemSettings({ replayCacheTtlMinutes: 30 }); + + expect(result.ok).toBe(true); + expect(updateSystemSettingsMock).toHaveBeenCalledWith( + expect.objectContaining({ replayCacheTtlMinutes: 30 }) + ); + }); + + it.each([4, 121, 30.5])("rejects invalid Replay cache TTL %s", async (value) => { + const result = await saveSystemSettings({ replayCacheTtlMinutes: value }); + + expect(result).toMatchObject({ + ok: false, + error: "Replay cache TTL validation failed.", + errorCode: "REPLAY_CACHE_TTL_INVALID", + }); + expect(updateSystemSettingsMock).not.toHaveBeenCalled(); + }); + it("returns a structured error code for invalid Discovery field ranges", async () => { const result = await saveSystemSettings({ discoveryConcurrency: 33 }); diff --git a/tests/unit/instrumentation-replay-cleanup.test.ts b/tests/unit/instrumentation-replay-cleanup.test.ts index 54bd81e77..937aca87a 100644 --- a/tests/unit/instrumentation-replay-cleanup.test.ts +++ b/tests/unit/instrumentation-replay-cleanup.test.ts @@ -4,11 +4,16 @@ const cleanupControl = vi.hoisted(() => ({ runReplayCleanupTick: vi.fn(), })); +const loggerControl = vi.hoisted(() => ({ + info: vi.fn(), + warn: vi.fn(), +})); + vi.mock("@/lib/logger", () => ({ logger: { debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), + info: loggerControl.info, + warn: loggerControl.warn, error: vi.fn(), trace: vi.fn(), fatal: vi.fn(), @@ -24,6 +29,8 @@ import { startReplayCleanupScheduler } from "@/instrumentation"; describe("startReplayCleanupScheduler", () => { beforeEach(() => { vi.useFakeTimers(); + loggerControl.info.mockReset(); + loggerControl.warn.mockReset(); cleanupControl.runReplayCleanupTick.mockReset().mockResolvedValue({ status: "completed", batches: 1, @@ -55,4 +62,41 @@ describe("startReplayCleanupScheduler", () => { expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 10 * 60 * 1000); expect(cleanupControl.runReplayCleanupTick).toHaveBeenCalledTimes(2); }); + + it("logs the wrapped Postgres cause when a cleanup tick fails", async () => { + const cause = Object.assign(new Error("canceling statement due to lock timeout"), { + code: "55P03", + }); + cleanupControl.runReplayCleanupTick.mockRejectedValueOnce(new Error("Failed query", { cause })); + + await startReplayCleanupScheduler(); + await vi.runOnlyPendingTimersAsync(); + + expect(loggerControl.warn).toHaveBeenCalledWith( + "[Instrumentation] Replay cleanup tick failed", + expect.objectContaining({ + error: "Failed query", + errorName: "Error", + errorCause: "canceling statement due to lock timeout", + errorCauseName: "Error", + errorCauseCode: "55P03", + }) + ); + }); + + it("logs batch and deletion progress when a cleanup tick removes rows", async () => { + cleanupControl.runReplayCleanupTick.mockResolvedValue({ + status: "completed", + batches: 2, + deleted: 150, + }); + + await startReplayCleanupScheduler(); + await vi.runOnlyPendingTimersAsync(); + + expect(loggerControl.info).toHaveBeenCalledWith( + "[Instrumentation] Replay cleanup tick completed", + { status: "completed", batches: 2, deleted: 150 } + ); + }); }); diff --git a/tests/unit/proxy/replay-store.test.ts b/tests/unit/proxy/replay-store.test.ts index 0b2b9dcc3..50e6cdfb3 100644 --- a/tests/unit/proxy/replay-store.test.ts +++ b/tests/unit/proxy/replay-store.test.ts @@ -6,6 +6,7 @@ import { type ReplayMeta, type ReplayPersistedRow, ReplayStore, + resolveReplayCompletedTtlSeconds, resolveReplayTtlSeconds, } from "@/app/v1/_lib/proxy/replay/replay-store"; @@ -21,7 +22,10 @@ import { const envControl = vi.hoisted(() => ({ shouldThrow: false, replayTtlSeconds: 600, - completedTtlSeconds: 3600, +})); + +const runtimeSettingsControl = vi.hoisted(() => ({ + replayCacheTtlMinutes: 30 as number | null, })); const redisControl = vi.hoisted(() => ({ @@ -63,7 +67,6 @@ vi.mock("@/lib/config/env.schema", async (importOriginal) => { return { ...baseEnv, REPLAY_TTL_SECONDS: envControl.replayTtlSeconds, - REPLAY_COMPLETED_TTL_SECONDS: envControl.completedTtlSeconds, }; }, }; @@ -73,6 +76,13 @@ vi.mock("@/lib/redis/client", () => ({ getRedisClient: () => redisControl.client, })); +vi.mock("@/lib/system-settings/proxy-runtime", () => ({ + getCachedProxyRuntimeSettings: () => + runtimeSettingsControl.replayCacheTtlMinutes === null + ? null + : { replayCacheTtlMinutes: runtimeSettingsControl.replayCacheTtlMinutes }, +})); + vi.mock("@/drizzle/db", () => ({ db: { execute: async (query: unknown) => { @@ -259,7 +269,7 @@ function toSqlText(condition: unknown): string { beforeEach(() => { envControl.shouldThrow = false; envControl.replayTtlSeconds = 600; - envControl.completedTtlSeconds = 3600; + runtimeSettingsControl.replayCacheTtlMinutes = 30; redisControl.client = createFakeRedis(); dbState.insertValues = []; dbState.onConflictCalls = 0; @@ -561,8 +571,8 @@ describe("ReplayStore:owner 租约", () => { }); describe("ReplayStore:PG 完成持久层", () => { - it("persistCompleted 写入行(expiresAt = now + REPLAY_COMPLETED_TTL_SECONDS),写路径不顺带清理", async () => { - envControl.completedTtlSeconds = 1000; + it("persistCompleted 按系统设置写入 30 分钟有效期,写路径不顺带清理", async () => { + runtimeSettingsControl.replayCacheTtlMinutes = 30; const store = new ReplayStore(); const row = makePersistedRow(); @@ -587,8 +597,8 @@ describe("ReplayStore:PG 完成持久层", () => { sourceMessageRequestId: 77, }); const expiresAt = (inserted.expiresAt as Date).getTime(); - expect(expiresAt).toBeGreaterThanOrEqual(before + 1000 * 1000); - expect(expiresAt).toBeLessThanOrEqual(after + 1000 * 1000); + expect(expiresAt).toBeGreaterThanOrEqual(before + 30 * 60 * 1000); + expect(expiresAt).toBeLessThanOrEqual(after + 30 * 60 * 1000); expect(dbState.onConflictCalls).toBe(1); // 过期行清理只归定时调度器:写路径不做机会式扫尾 @@ -676,6 +686,9 @@ describe("ReplayStore:PG 完成持久层", () => { expect(deleteSql).toContain("for update skip locked"); expect(deleteSql).toContain("delete from replay_payloads"); expect(deleteSql).toContain("returning 1"); + expect(dialect.sqlToQuery(dbState.executeQueries[0] as SQL).params[0]).toBe( + cutoff.toISOString() + ); }); it("findCompleted 只按 replayId + 未过期条件查询并返回首行", async () => { @@ -699,7 +712,7 @@ describe("ReplayStore:PG 完成持久层", () => { }); }); -describe("resolveReplayTtlSeconds / getReplayStore", () => { +describe("Replay TTL resolver / getReplayStore", () => { it("读 env 的 REPLAY_TTL_SECONDS", () => { envControl.replayTtlSeconds = 1234; expect(resolveReplayTtlSeconds()).toBe(1234); @@ -710,6 +723,21 @@ describe("resolveReplayTtlSeconds / getReplayStore", () => { expect(resolveReplayTtlSeconds()).toBe(600); }); + it("Redis 热层 TTL 不超过系统设置的 Replay 窗口", () => { + runtimeSettingsControl.replayCacheTtlMinutes = 5; + expect(resolveReplayTtlSeconds()).toBe(300); + }); + + it.each([ + { minutes: null, expected: 1800 }, + { minutes: 4, expected: 300 }, + { minutes: 121, expected: 7200 }, + { minutes: 30.9, expected: 1800 }, + ])("PG Replay TTL 规范化 $minutes 分钟为 $expected 秒", ({ minutes, expected }) => { + runtimeSettingsControl.replayCacheTtlMinutes = minutes; + expect(resolveReplayCompletedTtlSeconds()).toBe(expected); + }); + it("getReplayStore 返回共享单例", () => { const first = getReplayStore(); expect(getReplayStore()).toBe(first); diff --git a/tests/unit/proxy/stream-gate-mode-resolution.test.ts b/tests/unit/proxy/stream-gate-mode-resolution.test.ts index 0577fbb40..17464806b 100644 --- a/tests/unit/proxy/stream-gate-mode-resolution.test.ts +++ b/tests/unit/proxy/stream-gate-mode-resolution.test.ts @@ -32,6 +32,9 @@ function createSettings(overrides: Partial = {}): Partial { vi.clearAllMocks(); vi.resetModules(); - getEnvConfigMock.mockReturnValue({ STREAM_GATE_MODE: "off" }); + getEnvConfigMock.mockReturnValue({ + STREAM_GATE_MODE: "off", + ENABLE_REQUEST_REPLAY: false, + ENABLE_CACHE_EFFECTIVENESS: true, + }); }); describe("getProxyRuntimeSettings / getCachedProxyRuntimeSettings", () => { @@ -54,27 +61,46 @@ describe("getProxyRuntimeSettings / getCachedProxyRuntimeSettings", () => { expect(getCachedProxyRuntimeSettings()).toBeNull(); }); - test("getProxyRuntimeSettings 从系统设置缓存映射两字段并更新快照", async () => { + test("getProxyRuntimeSettings 从系统设置缓存映射代理字段并更新快照", async () => { getCachedSystemSettingsMock.mockResolvedValue( createSettings({ streamGateMode: "shadow", affinityIgnoreClientSessionId: false }) ); const { getProxyRuntimeSettings, getCachedProxyRuntimeSettings } = await loadModules(); const settings = await getProxyRuntimeSettings(); - expect(settings).toEqual({ streamGateMode: "shadow", affinityIgnoreClientSessionId: false }); + expect(settings).toEqual({ + streamGateMode: "shadow", + affinityIgnoreClientSessionId: false, + replayEnabled: false, + replayCacheTtlMinutes: 30, + cacheEffectivenessEnabled: true, + }); expect(getCachedProxyRuntimeSettings()).toEqual({ streamGateMode: "shadow", affinityIgnoreClientSessionId: false, + replayEnabled: false, + replayCacheTtlMinutes: 30, + cacheEffectivenessEnabled: true, }); }); test("系统设置读取异常且无快照时回退 env(affinity 默认开)", async () => { getCachedSystemSettingsMock.mockRejectedValue(new Error("db down")); - getEnvConfigMock.mockReturnValue({ STREAM_GATE_MODE: "shadow" }); + getEnvConfigMock.mockReturnValue({ + STREAM_GATE_MODE: "shadow", + ENABLE_REQUEST_REPLAY: false, + ENABLE_CACHE_EFFECTIVENESS: true, + }); const { getProxyRuntimeSettings } = await loadModules(); const settings = await getProxyRuntimeSettings(); - expect(settings).toEqual({ streamGateMode: "shadow", affinityIgnoreClientSessionId: true }); + expect(settings).toEqual({ + streamGateMode: "shadow", + affinityIgnoreClientSessionId: true, + replayEnabled: false, + replayCacheTtlMinutes: 30, + cacheEffectivenessEnabled: true, + }); }); test("系统设置读取异常但已有快照时返回旧快照", async () => { @@ -84,7 +110,13 @@ describe("getProxyRuntimeSettings / getCachedProxyRuntimeSettings", () => { getCachedSystemSettingsMock.mockRejectedValueOnce(new Error("db down")); const settings = await getProxyRuntimeSettings(); - expect(settings).toEqual({ streamGateMode: "off", affinityIgnoreClientSessionId: true }); + expect(settings).toEqual({ + streamGateMode: "off", + affinityIgnoreClientSessionId: true, + replayEnabled: false, + replayCacheTtlMinutes: 30, + cacheEffectivenessEnabled: true, + }); }); }); diff --git a/tests/unit/repository/system-config-degradation-ladder.test.ts b/tests/unit/repository/system-config-degradation-ladder.test.ts index 0996e8b72..c1f120d7c 100644 --- a/tests/unit/repository/system-config-degradation-ladder.test.ts +++ b/tests/unit/repository/system-config-degradation-ladder.test.ts @@ -7,6 +7,7 @@ import type { UpdateSystemSettingsInput } from "@/types/system-config"; // 近代新增列(最新在前),降级链按引入顺序逐层累计剥离。 const RECENT_COLUMNS = [ + "replayCacheTtlMinutes", "cacheEffectivenessEnabled", "replayEnabled", "affinityIgnoreClientSessionId", @@ -27,8 +28,9 @@ const RECENT_COLUMNS = [ "allowNonConversationEndpointProviderFallback", ] as const; -// 全量字段集(46 列)。 +// 全量字段集. const FULL_COLUMNS = [ + "replayCacheTtlMinutes", "cacheEffectivenessEnabled", "replayEnabled", "affinityIgnoreClientSessionId", @@ -189,7 +191,7 @@ describe("SystemSettings:列降级阶梯的尝试序列锁定", () => { const selectMock = vi.fn((selection: Record) => { selections.push(sortedKeys(selection)); callIndex += 1; - if (callIndex < 20) { + if (callIndex < RECENT_COLUMNS.length + 2) { return createRejectingSelectQuery({ code: "42703" }); } return createResolvingSelectQuery([ @@ -222,14 +224,17 @@ describe("SystemSettings:列降级阶梯的尝试序列锁定", () => { const result = await getSystemSettings(); - expect(selectMock).toHaveBeenCalledTimes(20); - // 第 19 次(近代链末层)不含这些新列;第 20 次(passThrough 世代)重新包含旧列。 - expect(selections[18]).not.toContain("enableThinkingEffortConflictRectifier"); - expect(selections[18]).not.toContain("allowNonConversationEndpointProviderFallback"); - expect(selections[18]).toContain("passThroughUpstreamErrorMessage"); - expect(selections[19]).toContain("enableThinkingEffortConflictRectifier"); - expect(selections[19]).toContain("allowNonConversationEndpointProviderFallback"); - expect(selections[19]).not.toContain("passThroughUpstreamErrorMessage"); + const lastRecentIndex = RECENT_COLUMNS.length; + const passThroughIndex = lastRecentIndex + 1; + expect(selectMock).toHaveBeenCalledTimes(passThroughIndex + 1); + expect(selections[lastRecentIndex]).not.toContain("enableThinkingEffortConflictRectifier"); + expect(selections[lastRecentIndex]).not.toContain( + "allowNonConversationEndpointProviderFallback" + ); + expect(selections[lastRecentIndex]).toContain("passThroughUpstreamErrorMessage"); + expect(selections[passThroughIndex]).toContain("enableThinkingEffortConflictRectifier"); + expect(selections[passThroughIndex]).toContain("allowNonConversationEndpointProviderFallback"); + expect(selections[passThroughIndex]).not.toContain("passThroughUpstreamErrorMessage"); // 世代字段集选出的真实值要透传,缺失列由 transformer 落默认值。 expect(result.siteTitle).toBe("Era Row"); @@ -309,7 +314,7 @@ describe("SystemSettings:列降级阶梯的尝试序列锁定", () => { "system_settings 表列缺失,请执行数据库迁移以升级数据库结构。" ); - expect(updateMock).toHaveBeenCalledTimes(22); + expect(updateMock).toHaveBeenCalledTimes(23); const expectedReturningSequence = [ [...FULL_COLUMNS], diff --git a/tests/unit/repository/system-config-update-missing-columns.test.ts b/tests/unit/repository/system-config-update-missing-columns.test.ts index 9b26aed05..b968e07e4 100644 --- a/tests/unit/repository/system-config-update-missing-columns.test.ts +++ b/tests/unit/repository/system-config-update-missing-columns.test.ts @@ -299,10 +299,12 @@ describe("SystemSettings:数据库缺列时的保存兜底", () => { vi.setSystemTime(now); // 第一次 select(fullSelection) 因新列缺失而抛 42703; - // 第二次 select(去掉 cacheEffectivenessEnabled)命中——验证新列已加入降级链最外层。 + // 第二次仅去掉最新的 replayCacheTtlMinutes 后仍失败; + // 第三次累计去掉 cacheEffectivenessEnabled 后命中. const selectMock = vi .fn() .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" })) + .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" })) .mockReturnValueOnce( createThenableQuery([ { @@ -335,23 +337,22 @@ describe("SystemSettings:数据库缺列时的保存兜底", () => { const result = await getSystemSettings(); // 降级读取成功(未抛错),缺失列由 transformer 落默认值。 - expect(selectMock).toHaveBeenCalledTimes(2); + expect(selectMock).toHaveBeenCalledTimes(3); expect(result.siteTitle).toBe("CC Hub"); expect(result.enableHttp2).toBe(true); expect(result.affinityIgnoreClientSessionId).toBe(true); expect(result.streamGateMode).toBe("enforce"); - // 关键回归保护:第二次 select 必须恰好剥离了最新列(最外层降级), - // 而非旧行为先剥离更早引入的列。若新列未加入降级链最外层,下面断言会失败。 - const secondSelection = selectMock.mock.calls[1]?.[0] as Record; - expect(secondSelection).not.toHaveProperty("cacheEffectivenessEnabled"); - expect(secondSelection).toHaveProperty("replayEnabled"); - expect(secondSelection).toHaveProperty("affinityIgnoreClientSessionId"); - expect(secondSelection).toHaveProperty("streamGateMode"); - expect(secondSelection).toHaveProperty("stickyTimeoutCooldownMs"); - expect(secondSelection).toHaveProperty("racingTotalTimeoutMs"); - expect(secondSelection).toHaveProperty("enableGeminiFunctionIdRectifier"); - expect(secondSelection).toHaveProperty("enableThinkingEffortConflictRectifier"); + const thirdSelection = selectMock.mock.calls[2]?.[0] as Record; + expect(thirdSelection).not.toHaveProperty("replayCacheTtlMinutes"); + expect(thirdSelection).not.toHaveProperty("cacheEffectivenessEnabled"); + expect(thirdSelection).toHaveProperty("replayEnabled"); + expect(thirdSelection).toHaveProperty("affinityIgnoreClientSessionId"); + expect(thirdSelection).toHaveProperty("streamGateMode"); + expect(thirdSelection).toHaveProperty("stickyTimeoutCooldownMs"); + expect(thirdSelection).toHaveProperty("racingTotalTimeoutMs"); + expect(thirdSelection).toHaveProperty("enableGeminiFunctionIdRectifier"); + expect(thirdSelection).toHaveProperty("enableThinkingEffortConflictRectifier"); vi.useRealTimers(); }); diff --git a/tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx b/tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx index b7f15aa2c..4ebde2a0b 100644 --- a/tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx +++ b/tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx @@ -67,6 +67,7 @@ const baseSettings = { ipExtractionConfig: null, // null = 跟随环境变量:本组用例的核心前置 replayEnabled: null, + replayCacheTtlMinutes: 30, cacheEffectivenessEnabled: null, } satisfies Pick< SystemSettings, @@ -100,6 +101,7 @@ const baseSettings = { | "ipGeoLookupEnabled" | "ipExtractionConfig" | "replayEnabled" + | "replayCacheTtlMinutes" | "cacheEffectivenessEnabled" >; @@ -206,4 +208,48 @@ describe("SystemSettingsForm replay/cache-effectiveness null 三态", () => { unmount(); }); + + test("显示默认 Replay 缓存时间并随系统设置提交", async () => { + const { unmount } = render( + + ); + + const ttlInput = document.getElementById("replay-cache-ttl-minutes") as HTMLInputElement | null; + expect(ttlInput?.value).toBe("30"); + + await act(async () => { + if (!ttlInput) throw new Error("未找到 Replay 缓存时间输入框"); + const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set; + setter?.call(ttlInput, "45"); + ttlInput.dispatchEvent(new Event("input", { bubbles: true })); + ttlInput.dispatchEvent(new Event("change", { bubbles: true })); + await Promise.resolve(); + }); + await submitForm(); + + expect(systemConfigActionMocks.saveSystemSettings).toHaveBeenCalledWith( + expect.objectContaining({ replayCacheTtlMinutes: 45 }) + ); + + unmount(); + }); + + test("Replay 缓存时间错误码显示本地化消息", async () => { + systemConfigActionMocks.saveSystemSettings.mockResolvedValueOnce({ + ok: false, + error: "One or more fields are invalid.", + errorCode: "REPLAY_CACHE_TTL_INVALID", + }); + const { unmount } = render( + + ); + + await submitForm(); + + expect(sonnerMocks.toast.error).toHaveBeenCalledWith( + "Replay cache duration must be a whole number from 5 to 120 minutes." + ); + + unmount(); + }); });