diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index e90516235..3ba9700c9 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -31,7 +31,7 @@ jobs: node-version: '20' - name: 📦 Install dependencies - run: bun install + run: bun install --frozen-lockfile - name: 🔍 Type check run: bun run typecheck diff --git a/drizzle/0078_huge_weapon_omega.sql b/drizzle/0078_huge_weapon_omega.sql new file mode 100644 index 000000000..70134a0d9 --- /dev/null +++ b/drizzle/0078_huge_weapon_omega.sql @@ -0,0 +1,4 @@ +-- Note: message_request is a high-write table. Standard CREATE INDEX may block writes during index creation. +-- Drizzle migrator does not support CREATE INDEX CONCURRENTLY. If write blocking is a concern, +-- manually pre-create indexes with CONCURRENTLY before running this migration (IF NOT EXISTS prevents conflicts). +CREATE INDEX IF NOT EXISTS "idx_message_request_user_created_at_id_completed" ON "message_request" USING btree ("user_id","created_at" DESC NULLS LAST,"id" DESC NULLS LAST) WHERE "message_request"."deleted_at" IS NULL AND "message_request"."duration_ms" IS NOT NULL AND ("message_request"."blocked_by" IS NULL OR "message_request"."blocked_by" <> 'warmup'); diff --git a/drizzle/0079_special_zarda.sql b/drizzle/0079_special_zarda.sql new file mode 100644 index 000000000..f62d39886 --- /dev/null +++ b/drizzle/0079_special_zarda.sql @@ -0,0 +1,4 @@ +-- Note: message_request is a high-write table. Standard CREATE INDEX may block writes during index creation. +-- Drizzle migrator does not support CREATE INDEX CONCURRENTLY. If write blocking is a concern, +-- manually pre-create indexes with CONCURRENTLY before running this migration (IF NOT EXISTS prevents conflicts). +CREATE INDEX IF NOT EXISTS "idx_message_request_active_created_at_id" ON "message_request" USING btree ("created_at","id") WHERE "message_request"."deleted_at" IS NULL AND "message_request"."duration_ms" IS NULL; diff --git a/drizzle/meta/0078_snapshot.json b/drizzle/meta/0078_snapshot.json new file mode 100644 index 000000000..167b820e4 --- /dev/null +++ b/drizzle/meta/0078_snapshot.json @@ -0,0 +1,3936 @@ +{ + "id": "e0e0d836-cd6e-42ec-8531-7a65fa046cb4", + "prevId": "22eb3652-56d7-4a04-9845-5fa18210ef90", + "version": "7", + "dialect": "postgresql", + "tables": { + "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_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 + }, + "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 + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "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_user_created_at_id_completed": { + "name": "idx_message_request_user_created_at_id_completed", + "columns": [ + { + "expression": "user_id", + "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 AND \"message_request\".\"duration_ms\" IS NOT 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_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_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_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": {} + } + }, + "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": "'litellm'" + }, + "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": {} + } + }, + "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_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_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(50)", + "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 + }, + "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_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 + }, + "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 + }, + "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.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 + }, + "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": {} + } + }, + "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": "'Claude Code 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'" + }, + "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 + }, + "enable_http2": { + "name": "enable_http2", + "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_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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 + }, + "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 + }, + "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 + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "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 + }, + "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", + "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", + "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", + "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_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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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_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 + }, + "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/0079_snapshot.json b/drizzle/meta/0079_snapshot.json new file mode 100644 index 000000000..5d05ebfb3 --- /dev/null +++ b/drizzle/meta/0079_snapshot.json @@ -0,0 +1,3958 @@ +{ + "id": "474b3b30-ed05-4fcf-8fd6-50f781438fd9", + "prevId": "e0e0d836-cd6e-42ec-8531-7a65fa046cb4", + "version": "7", + "dialect": "postgresql", + "tables": { + "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_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 + }, + "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 + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "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_user_created_at_id_completed": { + "name": "idx_message_request_user_created_at_id_completed", + "columns": [ + { + "expression": "user_id", + "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 AND \"message_request\".\"duration_ms\" IS NOT 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_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_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_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_active_created_at_id": { + "name": "idx_message_request_active_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"duration_ms\" 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": {} + } + }, + "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": "'litellm'" + }, + "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": {} + } + }, + "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_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_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(50)", + "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 + }, + "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_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 + }, + "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 + }, + "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.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 + }, + "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": {} + } + }, + "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": "'Claude Code 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'" + }, + "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 + }, + "enable_http2": { + "name": "enable_http2", + "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_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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 + }, + "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 + }, + "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 + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "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 + }, + "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", + "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", + "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", + "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_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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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_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 + }, + "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 fb7b5a646..c286b3a60 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -547,6 +547,20 @@ "when": 1772219877045, "tag": "0077_nappy_giant_man", "breakpoints": true + }, + { + "idx": 78, + "version": "7", + "when": 1772505850994, + "tag": "0078_huge_weapon_omega", + "breakpoints": true + }, + { + "idx": 79, + "version": "7", + "when": 1772511987232, + "tag": "0079_special_zarda", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 519e1c55e..050646136 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "zod": "^4" }, "devDependencies": { - "@biomejs/biome": "^2", + "@biomejs/biome": "2.4.4", "@tailwindcss/postcss": "^4", "@types/ioredis": "^5", "@types/node": "^24", diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index 60a1f8e17..efa0e3f10 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -500,6 +500,12 @@ export const messageRequest = pgTable('message_request', { .where(sql`${table.deletedAt} IS NULL AND (${table.blockedBy} IS NULL OR ${table.blockedBy} <> 'warmup')`), // 优化用户查询的复合索引(按创建时间倒序) messageRequestUserQueryIdx: index('idx_message_request_user_query').on(table.userId, table.createdAt).where(sql`${table.deletedAt} IS NULL`), + // #854:proxy-status LATERAL lastRequest 查询加速(仅扫描已结束请求,避免孤儿积累时回溯过深) + messageRequestUserCreatedAtIdCompletedIdx: index('idx_message_request_user_created_at_id_completed') + .on(table.userId, table.createdAt.desc(), table.id.desc()) + .where( + sql`${table.deletedAt} IS NULL AND ${table.durationMs} IS NOT NULL AND (${table.blockedBy} IS NULL OR ${table.blockedBy} <> 'warmup')` + ), messageRequestProviderCreatedAtActiveIdx: index('idx_message_request_provider_created_at_active') .on(table.providerId, table.createdAt) .where(sql`${table.deletedAt} IS NULL AND (${table.blockedBy} IS NULL OR ${table.blockedBy} <> 'warmup')`), @@ -541,6 +547,11 @@ export const messageRequest = pgTable('message_request', { table.createdAt.desc(), table.id.desc() ).where(sql`${table.deletedAt} IS NULL`), + // #854:进行中/孤儿请求 created_at 扫描加速(proxy-status activeRequests + orphan sweeper) + messageRequestActiveCreatedAtIdIdx: index('idx_message_request_active_created_at_id').on( + table.createdAt, + table.id + ).where(sql`${table.deletedAt} IS NULL AND ${table.durationMs} IS NULL`), // #779:筛选器 DISTINCT model / status_code 加速(admin usage logs) messageRequestModelActiveIdx: index('idx_message_request_model_active').on(table.model).where(sql`${table.deletedAt} IS NULL AND ${table.model} IS NOT NULL`), messageRequestStatusCodeActiveIdx: index('idx_message_request_status_code_active').on(table.statusCode).where(sql`${table.deletedAt} IS NULL AND ${table.statusCode} IS NOT NULL`), diff --git a/src/instrumentation.ts b/src/instrumentation.ts index 74afd79d1..9cd0bc7b6 100644 --- a/src/instrumentation.ts +++ b/src/instrumentation.ts @@ -17,6 +17,8 @@ const instrumentationState = globalThis as unknown as { __CCH_SHUTDOWN_IN_PROGRESS__?: boolean; __CCH_CLOUD_PRICE_SYNC_STARTED__?: boolean; __CCH_CLOUD_PRICE_SYNC_INTERVAL_ID__?: ReturnType; + __CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_STARTED__?: boolean; + __CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__?: ReturnType; __CCH_API_KEY_VF_SYNC_STARTED__?: boolean; __CCH_API_KEY_VF_SYNC_CLEANUP__?: (() => void) | null; }; @@ -137,6 +139,129 @@ function warmupApiKeyVacuumFilter(): void { void startApiKeyVacuumFilterSync(); } +/** + * 封闭历史遗留的“孤儿请求”记录(duration/status 长期为空)。 + * + * 这些记录通常来自:进程被非优雅方式终止(OOM/SIGKILL),导致 async 批量写入的尾部更新丢失。 + * 若不处理,会被 Dashboard/统计视为“进行中”并持续累积,引发页面异常与资源风险。 + */ +async function startOrphanedMessageRequestSweeper(): Promise { + if (instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_STARTED__) { + return; + } + + // 先标记 started,避免并发初始化(例如热重载/多入口 init)导致重复注册 interval, + // 从而出现“先注册的 intervalId 被后注册覆盖,导致无法停止”的问题。 + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_STARTED__ = true; + + try { + const { sealOrphanedMessageRequests } = await import("@/repository/message"); + const intervalMs = 60 * 1000; + const timeoutMs = 30 * 1000; + const slowLogMs = 5 * 1000; + + const withTimeout = async ( + promise: Promise, + ms: number + ): Promise<{ timedOut: true } | { timedOut: false; value: T }> => { + let timeoutId: NodeJS.Timeout | null = null; + const timeoutPromise = new Promise<{ timedOut: true }>((resolve) => { + timeoutId = setTimeout(() => resolve({ timedOut: true }), ms); + }); + + const result = await Promise.race([ + promise.then((value) => ({ timedOut: false as const, value })), + timeoutPromise, + ]); + + if (timeoutId) { + clearTimeout(timeoutId); + } + + return result; + }; + + let inFlight = false; + const runOnce = async (reason: "startup" | "scheduled") => { + if (inFlight) { + return; + } + inFlight = true; + let timedOut = false; + const startedAt = Date.now(); + const sealPromise = sealOrphanedMessageRequests(); + try { + const result = await withTimeout(sealPromise, timeoutMs); + if (result.timedOut) { + timedOut = true; + // 避免出现 unhandled rejection(promise 仍可能在超时后继续 reject) + void sealPromise + .catch(() => {}) + .finally(() => { + inFlight = false; + }); + logger.warn("[Instrumentation] Orphaned message_request sweeper timed out", { + reason, + timeoutMs, + }); + return; + } + + const { sealedCount } = result.value; + const durationMs = Date.now() - startedAt; + if (durationMs > slowLogMs) { + logger.warn("[Instrumentation] Orphaned message_request sweeper slow run", { + reason, + durationMs, + timeoutMs, + }); + } + if (reason === "startup") { + logger.info("[Instrumentation] Orphaned message_request sweeper startup run completed", { + sealedCount, + durationMs, + }); + } + if (sealedCount > 0) { + logger.warn("[Instrumentation] Orphaned message_request records sealed", { + sealedCount, + reason, + durationMs, + }); + } + } catch (error) { + logger.warn("[Instrumentation] Failed to seal orphaned message_request records", { + reason, + error: error instanceof Error ? error.message : String(error), + }); + } finally { + if (!timedOut) { + inFlight = false; + } + } + }; + + void runOnce("startup"); + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__ = setInterval(() => { + void runOnce("scheduled"); + }, intervalMs); + + logger.info("[Instrumentation] Orphaned message_request sweeper started", { + intervalSeconds: intervalMs / 1000, + }); + } catch (error) { + // init 失败:回滚 started 标记,允许后续重试 + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_STARTED__ = false; + if (instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__) { + clearInterval(instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__); + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__ = undefined; + } + logger.warn("[Instrumentation] Orphaned message_request sweeper init failed", { + error: error instanceof Error ? error.message : String(error), + }); + } +} + export async function register() { // 仅在服务器端执行 if (process.env.NEXT_RUNTIME === "nodejs") { @@ -258,6 +383,24 @@ export async function register() { error: error instanceof Error ? error.message : String(error), }); } + + try { + if (instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__) { + clearInterval( + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__ + ); + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_INTERVAL_ID__ = undefined; + instrumentationState.__CCH_ORPHANED_MESSAGE_REQUEST_SWEEPER_STARTED__ = false; + } + } catch (error) { + logger.warn("[Instrumentation] Failed to stop orphaned message request sweeper", { + error: error instanceof Error ? error.message : String(error), + }); + } + + // 重要:注册 SIGTERM/SIGINT handler 会覆盖默认退出行为。 + // 若不显式退出,进程会在“半关闭”状态继续运行(例如异步写入队列已停止),导致后续日志长期异常。 + process.exit(0); }; process.once("SIGTERM", () => { @@ -314,6 +457,7 @@ export async function register() { }); warmupApiKeyVacuumFilter(); + await startOrphanedMessageRequestSweeper(); // 回填 provider_vendors/provider_endpoints(幂等) // 多实例启动时仅允许一个实例执行,避免重复扫描/写入导致的启动抖动(#779/#781)。 @@ -454,6 +598,7 @@ export async function register() { }); warmupApiKeyVacuumFilter(); + await startOrphanedMessageRequestSweeper(); // 回填 provider_vendors(按域名自动聚合旧 providers) try { diff --git a/src/lib/proxy-status-tracker.ts b/src/lib/proxy-status-tracker.ts index f449ccf1c..b4ed3ee79 100644 --- a/src/lib/proxy-status-tracker.ts +++ b/src/lib/proxy-status-tracker.ts @@ -1,6 +1,7 @@ -import { and, eq, isNull, sql } from "drizzle-orm"; +import { and, desc, eq, isNull, sql } from "drizzle-orm"; import { db } from "@/drizzle/db"; import { keys, messageRequest, providers, users } from "@/drizzle/schema"; +import { logger } from "@/lib/logger"; import { maskKey } from "@/lib/utils/validation"; import type { ProxyStatusResponse } from "@/types/proxy-status"; @@ -10,7 +11,7 @@ type ActiveRequestRow = { keyString: string; keyName: string | null; providerId: number; - providerName: string; + providerName: string | null; model: string | null; createdAt: Date | string | null; }; @@ -21,11 +22,25 @@ type LastRequestRow = { keyString: string; keyName: string | null; providerId: number; - providerName: string; + providerName: string | null; model: string | null; endTime: Date | string | null; }; +type DbUserRow = { + id: number; + name: string; +}; + +type StatusSnapshot = { + expiresAt: number; + dbUsers: DbUserRow[]; + activeRequestRows: ActiveRequestRow[]; + lastRequestRows: LastRequestRow[]; +}; + +const PROXY_STATUS_SNAPSHOT_TTL_MS = 5000; + function toTimestamp(value: Date | string | number | null | undefined): number | null { if (value == null) { return null; @@ -52,6 +67,9 @@ function toTimestamp(value: Date | string | number | null | undefined): number | export class ProxyStatusTracker { private static instance: ProxyStatusTracker | null = null; + private statusSnapshotCache: StatusSnapshot | null = null; + private statusSnapshotInFlight: Promise | null = null; + static getInstance(): ProxyStatusTracker { if (!ProxyStatusTracker.instance) { ProxyStatusTracker.instance = new ProxyStatusTracker(); @@ -59,6 +77,9 @@ export class ProxyStatusTracker { return ProxyStatusTracker.instance; } + /** + * @deprecated 已迁移为基于数据库聚合的实现(getAllUsersStatus)。保留仅为兼容既有调用点。 + */ startRequest(params: { userId: number; userName: string; @@ -68,28 +89,81 @@ export class ProxyStatusTracker { providerName: string; model: string; }): void { + // no-op:当前实现基于数据库聚合(getAllUsersStatus),保留方法仅为兼容既有调用点 void params; } + /** + * @deprecated 已迁移为基于数据库聚合的实现(getAllUsersStatus)。保留仅为兼容既有调用点。 + */ endRequest(userId: number, requestId: number): void { + // no-op:当前实现基于数据库聚合(getAllUsersStatus),保留方法仅为兼容既有调用点 void userId; void requestId; } + private async getStatusSnapshot(now: number): Promise { + const cached = this.statusSnapshotCache; + if (cached && cached.expiresAt > now) { + return cached; + } + + if (this.statusSnapshotInFlight) { + return await this.statusSnapshotInFlight; + } + + this.statusSnapshotInFlight = (async () => { + try { + const [dbUsers, activeRequestRows, lastRequestRows] = await Promise.all([ + db + .select({ + id: users.id, + name: users.name, + }) + .from(users) + .where(isNull(users.deletedAt)), + this.loadActiveRequests(), + this.loadLastRequests(), + ]); + + const snapshot: StatusSnapshot = { + expiresAt: Date.now() + PROXY_STATUS_SNAPSHOT_TTL_MS, + dbUsers: dbUsers as unknown as DbUserRow[], + activeRequestRows, + lastRequestRows, + }; + + // cache:避免 dashboard 轮询频繁触发全量 users + LATERAL 扫描 + this.statusSnapshotCache = snapshot; + + return snapshot; + } catch (error) { + logger.warn("[ProxyStatusTracker] Failed to refresh status snapshot, serving stale cache", { + error: error instanceof Error ? error.message : String(error), + }); + + if (this.statusSnapshotCache) { + // 退化:延长过期时间,避免 DB 抖动时 dashboard 轮询形成错误风暴 + this.statusSnapshotCache = { + ...this.statusSnapshotCache, + expiresAt: Date.now() + PROXY_STATUS_SNAPSHOT_TTL_MS, + }; + return this.statusSnapshotCache; + } + + throw error; + } + })().finally(() => { + this.statusSnapshotInFlight = null; + }); + + return await this.statusSnapshotInFlight; + } + async getAllUsersStatus(): Promise { const now = Date.now(); - const [dbUsers, activeRequestRows, lastRequestRows] = await Promise.all([ - db - .select({ - id: users.id, - name: users.name, - }) - .from(users) - .where(isNull(users.deletedAt)), - this.loadActiveRequests(), - this.loadLastRequests(), - ]); + const { dbUsers, activeRequestRows, lastRequestRows } = await this.getStatusSnapshot(now); const activeMap = new Map(); for (const row of activeRequestRows) { @@ -99,7 +173,7 @@ export class ProxyStatusTracker { requestId: row.requestId, keyName: row.keyName ?? maskKey(row.keyString), providerId: row.providerId, - providerName: row.providerName, + providerName: row.providerName || "unknown", model: row.model || "unknown", startTime, duration: now - startTime, @@ -107,6 +181,8 @@ export class ProxyStatusTracker { activeMap.set(row.userId, list); } + // lastRequestRows 仅包含已结束请求(duration_ms IS NOT NULL): + // 若用户仅有进行中请求,则 lastRequest 会保持为 null,由 activeRequests 展示进行中状态。 const lastMap = new Map(); for (const row of lastRequestRows) { if (!lastMap.has(row.userId)) { @@ -125,7 +201,7 @@ export class ProxyStatusTracker { requestId: lastRow.requestId, keyName: lastRow.keyName ?? maskKey(lastRow.keyString), providerId: lastRow.providerId, - providerName: lastRow.providerName, + providerName: lastRow.providerName || "unknown", model: lastRow.model || "unknown", endTime, elapsed: now - endTime, @@ -146,48 +222,91 @@ export class ProxyStatusTracker { } private async loadActiveRequests(): Promise { + const limit = 1000; + const rows = await db .select({ requestId: messageRequest.id, userId: messageRequest.userId, keyString: messageRequest.key, keyName: keys.name, - providerId: providers.id, + providerId: messageRequest.providerId, providerName: providers.name, model: messageRequest.model, createdAt: messageRequest.createdAt, }) .from(messageRequest) - .innerJoin(providers, eq(messageRequest.providerId, providers.id)) + .leftJoin( + providers, + and(eq(messageRequest.providerId, providers.id), isNull(providers.deletedAt)) + ) .leftJoin(keys, and(eq(keys.key, messageRequest.key), isNull(keys.deletedAt))) .where( and( isNull(messageRequest.deletedAt), isNull(messageRequest.durationMs), - isNull(providers.deletedAt) + isNull(messageRequest.statusCode), + // warmup 请求仅用于探测/预热:不应污染活跃请求列表与统计 + sql`(${messageRequest.blockedBy} IS NULL OR ${messageRequest.blockedBy} <> 'warmup')` ) + ) + // 防御:异常情况下 durationMs 长期为空会导致“活跃请求”无限累积,进而撑爆查询与响应体。 + // 这里对返回明细做上限保护(监控用途不需要无穷列表)。 + .orderBy(desc(messageRequest.createdAt)) + .limit(limit); + + if (rows.length >= limit) { + logger.warn( + "[ProxyStatusTracker] Active requests query hit limit, results may be incomplete", + { + limit, + rowCount: rows.length, + } ); + } return rows as ActiveRequestRow[]; } private async loadLastRequests(): Promise { + // 注意:该接口需要返回所有用户状态,因此整体复杂度与 users 数量线性相关。 + // 这里使用 LATERAL + 索引扫描来避免在 message_request 大表上做全表排序去重(DISTINCT ON), + // 若未来用户规模显著增大(例如 1e4+),建议为该接口增加分页/按需查询,或引入专门的汇总表/物化视图。 + // 本查询仅返回“存在已结束请求”的用户;其余用户由 getAllUsersStatus 补齐 lastRequest=null。 const query = sql` - SELECT DISTINCT ON (mr.user_id) - mr.user_id AS "userId", - mr.id AS "requestId", - mr.key AS "keyString", + SELECT + u.id AS "userId", + last.request_id AS "requestId", + last.key_string AS "keyString", k.name AS "keyName", - mr.provider_id AS "providerId", - p.name AS "providerName", - mr.model AS "model", - mr.updated_at AS "endTime" - FROM message_request mr - JOIN providers p ON mr.provider_id = p.id AND p.deleted_at IS NULL - LEFT JOIN keys k ON k.key = mr.key AND k.deleted_at IS NULL - WHERE mr.deleted_at IS NULL - AND (mr.blocked_by IS NULL OR mr.blocked_by <> 'warmup') - ORDER BY mr.user_id, mr.updated_at DESC + last.provider_id AS "providerId", + last.provider_name AS "providerName", + last.model AS "model", + last.end_time AS "endTime" + FROM users u + -- 使用 LATERAL 为每个用户做一次“取最新请求”的索引扫描,避免在 message_request 大表上做 DISTINCT ON 全表排序去重。 + JOIN LATERAL ( + SELECT + mr.id AS request_id, + mr.key AS key_string, + mr.provider_id AS provider_id, + p.name AS provider_name, + mr.model AS model, + -- 使用 created_at + duration_ms 推导结束时间:避免 async 批量写入导致 updated_at 漂移而“看起来更近”。 + (mr.created_at + (mr.duration_ms * interval '1 millisecond')) AS end_time + FROM message_request mr + LEFT JOIN providers p ON mr.provider_id = p.id AND p.deleted_at IS NULL + WHERE mr.user_id = u.id + AND mr.deleted_at IS NULL + -- lastRequest 仅统计已结束请求:activeRequests 已覆盖进行中请求,避免这里误选“请求中”的记录。 + AND mr.duration_ms IS NOT NULL + AND (mr.blocked_by IS NULL OR mr.blocked_by <> 'warmup') + -- 这里使用 created_at + id 排序以命中 idx_message_request_user_created_at_id_completed,避免孤儿积累时回溯过深。 + ORDER BY mr.created_at DESC, mr.id DESC + LIMIT 1 + ) last ON true + LEFT JOIN keys k ON k.key = last.key_string AND k.deleted_at IS NULL + WHERE u.deleted_at IS NULL `; const result = await db.execute(query); diff --git a/src/repository/message-orphaned-requests.ts b/src/repository/message-orphaned-requests.ts new file mode 100644 index 000000000..b66088557 --- /dev/null +++ b/src/repository/message-orphaned-requests.ts @@ -0,0 +1,3 @@ +export const ORPHANED_MESSAGE_REQUEST_STATUS_CODE = 520; +// 注意:这里写入的是稳定的“错误码”,展示层若直接展示 error_message,应做 i18n 映射。 +export const ORPHANED_MESSAGE_REQUEST_ERROR_CODE = "ORPHANED_REQUEST"; diff --git a/src/repository/message-write-buffer.ts b/src/repository/message-write-buffer.ts index d2f690189..c6787f78e 100644 --- a/src/repository/message-write-buffer.ts +++ b/src/repository/message-write-buffer.ts @@ -30,6 +30,12 @@ export type MessageRequestUpdatePatch = { specialSettings?: CreateMessageRequestData["special_settings"]; }; +export type MessageRequestUpdateEnqueueResult = + | { kind: "enqueued" } + | { kind: "rejected_invalid" } + | { kind: "buffer_unavailable" } + | { kind: "dropped_overflow"; patch: MessageRequestUpdatePatch }; + type MessageRequestUpdateRecord = { id: number; patch: MessageRequestUpdatePatch; @@ -64,6 +70,551 @@ const COLUMN_MAP: Record = { specialSettings: "special_settings", }; +const INT_CASE_KEYS = new Set([ + "durationMs", + "statusCode", + "ttfbMs", + "providerId", +]); +const BIGINT_CASE_KEYS = new Set([ + "inputTokens", + "outputTokens", + "cacheCreationInputTokens", + "cacheReadInputTokens", + "cacheCreation5mInputTokens", + "cacheCreation1hInputTokens", +]); +const BOOLEAN_CASE_KEYS = new Set([ + "context1mApplied", + "swapCacheTtlApplied", +]); + +const INT32_MAX = 2147483647; +const BIGINT_JS_MAX = Number.MAX_SAFE_INTEGER; // 2^53 - 1(JS 可精确表示的最大整数) +const NUMERIC_LIKE_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/; + +const REJECTED_INVALID_LOG_THROTTLE_MS = 60_000; +let _lastRejectedInvalidLogAt = 0; + +const INT32_CLAMP_LOG_THROTTLE_MS = 60_000; +const _lastInt32ClampLogAt = new Map(); +const SAFE_INT_CLAMP_LOG_THROTTLE_MS = INT32_CLAMP_LOG_THROTTLE_MS; +const _lastSafeIntClampLogAt = new Map(); + +const STATUS_CODE_REJECT_LOG_THROTTLE_MS = 60_000; +let _lastStatusCodeRejectLogAt = 0; + +const COST_USD_RANGE_LOG_THROTTLE_MS = 60_000; +let _lastCostUsdRangeLogAt = 0; + +const STRING_TRUNCATE_LOG_THROTTLE_MS = 60_000; +const _lastStringTruncateLogAt = new Map(); + +const MESSAGE_REQUEST_MODEL_MAX_LENGTH = 128; +const MESSAGE_REQUEST_CACHE_TTL_APPLIED_MAX_LENGTH = 10; +const MESSAGE_REQUEST_ERROR_MESSAGE_MAX_LENGTH = 8_192; +const MESSAGE_REQUEST_ERROR_STACK_MAX_LENGTH = 65_536; +const MESSAGE_REQUEST_ERROR_CAUSE_MAX_LENGTH = 8_192; + +const REQUEUE_OVERFLOW_LOG_THROTTLE_MS = 60_000; +let _lastRequeueOverflowLogAt = 0; + +// 终态 patch(duration/status)尽快刷库,但仍保留极短延迟以便 micro-batch,避免高并发下频繁 flush。 +const TERMINAL_FLUSH_DELAY_MS = 10; + +function toFiniteNumber(value: unknown): number | null { + if (typeof value === "number") { + return Number.isFinite(value) ? value : null; + } + + if (typeof value !== "string") { + return null; + } + + const trimmed = value.trim(); + if (trimmed.length === 0) { + return null; + } + + if (!NUMERIC_LIKE_RE.test(trimmed)) { + return null; + } + + const parsed = Number(trimmed); + return Number.isFinite(parsed) ? parsed : null; +} + +function summarizePatchTypes(patch: MessageRequestUpdatePatch): Record { + const summary: Record = {}; + for (const [key, value] of Object.entries(patch)) { + if (value === null) { + summary[key] = "null"; + } else if (Array.isArray(value)) { + summary[key] = "array"; + } else { + summary[key] = typeof value; + } + } + return summary; +} + +function truncateString(value: string, options: { field: string; maxLength: number }): string { + if (value.length <= options.maxLength) { + return value; + } + + const now = Date.now(); + const lastLogAt = _lastStringTruncateLogAt.get(options.field) ?? 0; + if (now - lastLogAt > STRING_TRUNCATE_LOG_THROTTLE_MS) { + _lastStringTruncateLogAt.set(options.field, now); + logger.warn("[MessageRequestWriteBuffer] String field too long, truncating", { + field: options.field, + maxLength: options.maxLength, + originalLength: value.length, + }); + } + + return value.slice(0, options.maxLength); +} + +function sanitizeInt32( + value: unknown, + options?: { min?: number; max?: number; field?: string } +): number | undefined { + const numeric = toFiniteNumber(value); + if (numeric === null) { + return undefined; + } + + const truncated = Math.trunc(numeric); + const min = options?.min ?? -INT32_MAX - 1; + const max = options?.max ?? INT32_MAX; + + if (truncated < min) { + const field = options?.field; + if (field) { + const now = Date.now(); + const lastLogAt = _lastInt32ClampLogAt.get(field) ?? 0; + if (now - lastLogAt > INT32_CLAMP_LOG_THROTTLE_MS) { + _lastInt32ClampLogAt.set(field, now); + logger.warn("[MessageRequestWriteBuffer] Int32 value out of range, clamping", { + field, + value: typeof value === "string" ? value.slice(0, 64) : value, + clampedTo: min, + min, + max, + }); + } + } + return min; + } + if (truncated > max) { + const field = options?.field; + if (field) { + const now = Date.now(); + const lastLogAt = _lastInt32ClampLogAt.get(field) ?? 0; + if (now - lastLogAt > INT32_CLAMP_LOG_THROTTLE_MS) { + _lastInt32ClampLogAt.set(field, now); + logger.warn("[MessageRequestWriteBuffer] Int32 value out of range, clamping", { + field, + value: typeof value === "string" ? value.slice(0, 64) : value, + clampedTo: max, + min, + max, + }); + } + } + return max; + } + return truncated; +} + +function sanitizeNullableInt32( + value: unknown, + options?: { min?: number; max?: number; field?: string } +): number | null | undefined { + if (value === null) { + return null; + } + return sanitizeInt32(value, options); +} + +function sanitizeSafeInt( + value: unknown, + options?: { min?: number; max?: number; field?: string } +): number | undefined { + const numeric = toFiniteNumber(value); + if (numeric === null) { + return undefined; + } + + const truncated = Math.trunc(numeric); + const min = options?.min ?? -BIGINT_JS_MAX; + const max = options?.max ?? BIGINT_JS_MAX; + + if (truncated < min) { + const field = options?.field; + if (field) { + const now = Date.now(); + const lastLogAt = _lastSafeIntClampLogAt.get(field) ?? 0; + if (now - lastLogAt > SAFE_INT_CLAMP_LOG_THROTTLE_MS) { + _lastSafeIntClampLogAt.set(field, now); + logger.warn("[MessageRequestWriteBuffer] Safe integer value out of range, clamping", { + field, + value: typeof value === "string" ? value.slice(0, 64) : value, + clampedTo: min, + min, + max, + }); + } + } + return min; + } + if (truncated > max) { + const field = options?.field; + if (field) { + const now = Date.now(); + const lastLogAt = _lastSafeIntClampLogAt.get(field) ?? 0; + if (now - lastLogAt > SAFE_INT_CLAMP_LOG_THROTTLE_MS) { + _lastSafeIntClampLogAt.set(field, now); + logger.warn("[MessageRequestWriteBuffer] Safe integer value out of range, clamping", { + field, + value: typeof value === "string" ? value.slice(0, 64) : value, + clampedTo: max, + min, + max, + }); + } + } + return max; + } + + return truncated; +} + +function sanitizeStatusCode(value: unknown): number | undefined { + const numeric = toFiniteNumber(value); + if (numeric === null) { + return undefined; + } + + const truncated = Math.trunc(numeric); + // HTTP status code 正常范围为 100-999(本项目还会写入 520 作为“孤儿请求”哨兵) + if (truncated < 100 || truncated > 999) { + const now = Date.now(); + if (now - _lastStatusCodeRejectLogAt > STATUS_CODE_REJECT_LOG_THROTTLE_MS) { + _lastStatusCodeRejectLogAt = now; + logger.warn("[MessageRequestWriteBuffer] Status code out of expected range, skipping", { + value: typeof value === "string" ? value.slice(0, 64) : value, + min: 100, + max: 999, + }); + } + return undefined; + } + + return truncated; +} + +function sanitizeCostUsdString(value: unknown): string | undefined { + let raw: string | undefined; + if (typeof value === "number") { + if (!Number.isFinite(value)) { + return undefined; + } + raw = String(value); + } else if (typeof value === "string") { + raw = value; + } else { + return undefined; + } + + const trimmed = raw.trim(); + if (trimmed.length === 0) { + return undefined; + } + + // 允许常见十进制与科学计数法,拒绝 NaN/Infinity/空白/十六进制等异常输入 + if (!NUMERIC_LIKE_RE.test(trimmed)) { + return undefined; + } + + // 数值过大(例如 1e309)会变成 Infinity;这种输入对 numeric 列也大概率不可用,直接拒绝。 + const parsed = Number(trimmed); + if (!Number.isFinite(parsed)) { + return undefined; + } + + // 目前仅用于 costUsd(schema: numeric(21, 15),整数部分最多 6 位:< 1,000,000) + if (parsed < 0 || parsed >= 1_000_000) { + const now = Date.now(); + if (now - _lastCostUsdRangeLogAt > COST_USD_RANGE_LOG_THROTTLE_MS) { + _lastCostUsdRangeLogAt = now; + logger.warn("[MessageRequestWriteBuffer] costUsd out of accepted range, skipping", { + value: trimmed.slice(0, 32), + min: 0, + max: 1_000_000, + }); + } + return undefined; + } + + return trimmed; +} + +function sanitizePatch(patch: MessageRequestUpdatePatch): MessageRequestUpdatePatch { + const sanitized: MessageRequestUpdatePatch = {}; + + const durationMs = sanitizeInt32(patch.durationMs, { + field: "durationMs", + min: 0, + max: INT32_MAX, + }); + if (durationMs !== undefined) { + sanitized.durationMs = durationMs; + } + + const statusCode = sanitizeStatusCode(patch.statusCode); + if (statusCode !== undefined) { + sanitized.statusCode = statusCode; + } + + const inputTokens = sanitizeSafeInt(patch.inputTokens, { + field: "inputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (inputTokens !== undefined) { + sanitized.inputTokens = inputTokens; + } + + const outputTokens = sanitizeSafeInt(patch.outputTokens, { + field: "outputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (outputTokens !== undefined) { + sanitized.outputTokens = outputTokens; + } + + const ttfbMs = sanitizeNullableInt32(patch.ttfbMs, { field: "ttfbMs", min: 0, max: INT32_MAX }); + if (ttfbMs !== undefined) { + sanitized.ttfbMs = ttfbMs; + } + + const cacheCreationInputTokens = sanitizeSafeInt(patch.cacheCreationInputTokens, { + field: "cacheCreationInputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (cacheCreationInputTokens !== undefined) { + sanitized.cacheCreationInputTokens = cacheCreationInputTokens; + } + + const cacheReadInputTokens = sanitizeSafeInt(patch.cacheReadInputTokens, { + field: "cacheReadInputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (cacheReadInputTokens !== undefined) { + sanitized.cacheReadInputTokens = cacheReadInputTokens; + } + + const cacheCreation5mInputTokens = sanitizeSafeInt(patch.cacheCreation5mInputTokens, { + field: "cacheCreation5mInputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (cacheCreation5mInputTokens !== undefined) { + sanitized.cacheCreation5mInputTokens = cacheCreation5mInputTokens; + } + + const cacheCreation1hInputTokens = sanitizeSafeInt(patch.cacheCreation1hInputTokens, { + field: "cacheCreation1hInputTokens", + min: 0, + max: BIGINT_JS_MAX, + }); + if (cacheCreation1hInputTokens !== undefined) { + sanitized.cacheCreation1hInputTokens = cacheCreation1hInputTokens; + } + + if (patch.cacheTtlApplied === null) { + sanitized.cacheTtlApplied = null; + } else if (typeof patch.cacheTtlApplied === "string") { + sanitized.cacheTtlApplied = truncateString(patch.cacheTtlApplied, { + field: "cacheTtlApplied", + maxLength: MESSAGE_REQUEST_CACHE_TTL_APPLIED_MAX_LENGTH, + }); + } else if (patch.cacheTtlApplied !== undefined) { + logger.warn("[MessageRequestWriteBuffer] Invalid cacheTtlApplied type, skipping", { + cacheTtlAppliedType: typeof patch.cacheTtlApplied, + }); + } + + const costUsd = sanitizeCostUsdString(patch.costUsd); + if (costUsd !== undefined) { + sanitized.costUsd = costUsd; + } + + if (patch.providerChain !== undefined) { + if (!Array.isArray(patch.providerChain)) { + logger.warn("[MessageRequestWriteBuffer] Invalid providerChain type, skipping", { + providerChainType: typeof patch.providerChain, + }); + } else { + try { + const json = JSON.stringify(patch.providerChain); + sanitized.providerChain = JSON.parse(json) as CreateMessageRequestData["provider_chain"]; + } catch (error) { + logger.warn("[MessageRequestWriteBuffer] Invalid providerChain, skipping", { + error: error instanceof Error ? error.message : String(error), + }); + } + } + } + + if (typeof patch.errorMessage === "string") { + sanitized.errorMessage = truncateString(patch.errorMessage, { + field: "errorMessage", + maxLength: MESSAGE_REQUEST_ERROR_MESSAGE_MAX_LENGTH, + }); + } + if (typeof patch.errorStack === "string") { + sanitized.errorStack = truncateString(patch.errorStack, { + field: "errorStack", + maxLength: MESSAGE_REQUEST_ERROR_STACK_MAX_LENGTH, + }); + } + if (typeof patch.errorCause === "string") { + sanitized.errorCause = truncateString(patch.errorCause, { + field: "errorCause", + maxLength: MESSAGE_REQUEST_ERROR_CAUSE_MAX_LENGTH, + }); + } + if (typeof patch.model === "string") { + sanitized.model = truncateString(patch.model, { + field: "model", + maxLength: MESSAGE_REQUEST_MODEL_MAX_LENGTH, + }); + } + + const providerId = sanitizeInt32(patch.providerId, { + field: "providerId", + min: 0, + max: INT32_MAX, + }); + if (providerId !== undefined) { + sanitized.providerId = providerId; + } + + if (typeof patch.context1mApplied === "boolean") { + sanitized.context1mApplied = patch.context1mApplied; + } else if (patch.context1mApplied !== undefined) { + logger.warn("[MessageRequestWriteBuffer] Invalid context1mApplied type, skipping", { + context1mAppliedType: typeof patch.context1mApplied, + }); + } + if (typeof patch.swapCacheTtlApplied === "boolean") { + sanitized.swapCacheTtlApplied = patch.swapCacheTtlApplied; + } else if (patch.swapCacheTtlApplied !== undefined) { + logger.warn("[MessageRequestWriteBuffer] Invalid swapCacheTtlApplied type, skipping", { + swapCacheTtlAppliedType: typeof patch.swapCacheTtlApplied, + }); + } + + if (patch.specialSettings === null) { + sanitized.specialSettings = null; + } else if (patch.specialSettings !== undefined) { + if (!Array.isArray(patch.specialSettings)) { + logger.warn("[MessageRequestWriteBuffer] Invalid specialSettings type, skipping", { + specialSettingsType: typeof patch.specialSettings, + }); + } else { + try { + const json = JSON.stringify(patch.specialSettings); + sanitized.specialSettings = JSON.parse( + json + ) as CreateMessageRequestData["special_settings"]; + } catch (error) { + logger.warn("[MessageRequestWriteBuffer] Invalid specialSettings, skipping", { + error: error instanceof Error ? error.message : String(error), + }); + } + } + } + + return sanitized; +} + +export function sanitizeMessageRequestUpdatePatch( + patch: MessageRequestUpdatePatch +): MessageRequestUpdatePatch { + return sanitizePatch(patch); +} + +function isTerminalPatch(patch: MessageRequestUpdatePatch): boolean { + return patch.durationMs !== undefined || patch.statusCode !== undefined; +} + +function getErrorCode(error: unknown): string | null { + const visited = new Set(); + let current: unknown = error; + + for (let depth = 0; depth < 5; depth++) { + if (!current || typeof current !== "object") { + return null; + } + if (visited.has(current)) { + return null; + } + visited.add(current); + + const code = (current as { code?: unknown }).code; + if (typeof code === "string") { + return code; + } + + current = (current as { cause?: unknown }).cause; + } + + return null; +} + +function isDataRelatedDbError(error: unknown): boolean { + const code = getErrorCode(error); + if (!code) { + return false; + } + + // 仅对“数据/约束类”错误做隔离处理,避免对连接/暂态问题造成额外压力 + return code.startsWith("22") || code.startsWith("23"); +} + +function getSafePatch(patch: MessageRequestUpdatePatch): MessageRequestUpdatePatch { + // 刻意排除 costUsd/providerChain/specialSettings:这些字段更容易引发类型/JSON 异常 + const { + costUsd: _costUsd, + providerChain: _providerChain, + specialSettings: _specialSettings, + ...rest + } = patch; + return rest; +} + +function getMinimalPatch(patch: MessageRequestUpdatePatch): MessageRequestUpdatePatch { + const minimal: MessageRequestUpdatePatch = {}; + if (patch.durationMs !== undefined) { + minimal.durationMs = patch.durationMs; + } + if (patch.statusCode !== undefined) { + minimal.statusCode = patch.statusCode; + } + if (patch.errorMessage !== undefined) { + minimal.errorMessage = patch.errorMessage; + } + return minimal; +} + function loadWriterConfig(): WriterConfig { const env = getEnvConfig(); return { @@ -105,11 +656,22 @@ function buildBatchUpdateSql(updates: MessageRequestUpdateRecord[]): SQL | null if (key === "providerChain" || key === "specialSettings") { if (value === null) { - cases.push(sql`WHEN ${update.id} THEN NULL`); + cases.push(sql`WHEN ${update.id} THEN NULL::jsonb`); continue; } - const json = JSON.stringify(value); - cases.push(sql`WHEN ${update.id} THEN ${json}::jsonb`); + try { + const json = JSON.stringify(value); + cases.push(sql`WHEN ${update.id} THEN ${json}::jsonb`); + } catch (error) { + logger.warn( + "[MessageRequestWriteBuffer] Failed to stringify JSON patch field, skipping", + { + requestId: update.id, + field: key, + error: error instanceof Error ? error.message : String(error), + } + ); + } continue; } @@ -119,6 +681,21 @@ function buildBatchUpdateSql(updates: MessageRequestUpdateRecord[]): SQL | null continue; } + if (INT_CASE_KEYS.has(key)) { + cases.push(sql`WHEN ${update.id} THEN ${value}::int`); + continue; + } + + if (BIGINT_CASE_KEYS.has(key)) { + cases.push(sql`WHEN ${update.id} THEN ${value}::bigint`); + continue; + } + + if (BOOLEAN_CASE_KEYS.has(key)) { + cases.push(sql`WHEN ${update.id} THEN ${value}::boolean`); + continue; + } + cases.push(sql`WHEN ${update.id} THEN ${value}`); } @@ -153,7 +730,10 @@ function buildBatchUpdateSql(updates: MessageRequestUpdateRecord[]): SQL | null class MessageRequestWriteBuffer { private readonly config: WriterConfig; private readonly pending = new Map(); + // 不含终态字段(duration/status)的待写入条目集合;始终与 pending 内合并后的 patch 状态保持一致。 + private readonly nonTerminalIds = new Set(); private flushTimer: NodeJS.Timeout | null = null; + private flushTimerDueAt: number | null = null; private flushAgainAfterCurrent = false; private flushInFlight: Promise | null = null; private stopping = false; @@ -162,48 +742,52 @@ class MessageRequestWriteBuffer { this.config = config; } - enqueue(id: number, patch: MessageRequestUpdatePatch): void { - const existing = this.pending.get(id) ?? {}; - const merged: MessageRequestUpdatePatch = { ...existing }; - for (const [k, v] of Object.entries(patch) as Array< - [keyof MessageRequestUpdatePatch, MessageRequestUpdatePatch[keyof MessageRequestUpdatePatch]] - >) { - if (v !== undefined) { - merged[k] = v as never; + enqueue(id: number, patch: MessageRequestUpdatePatch): MessageRequestUpdateEnqueueResult { + if (this.stopping) { + return { kind: "buffer_unavailable" }; + } + + const sanitized = sanitizePatch(patch); + if (Object.keys(sanitized).length === 0) { + const now = Date.now(); + if (now - _lastRejectedInvalidLogAt > REJECTED_INVALID_LOG_THROTTLE_MS) { + _lastRejectedInvalidLogAt = now; + logger.warn("[MessageRequestWriteBuffer] Patch rejected: empty after sanitize", { + requestId: id, + originalKeys: Object.keys(patch), + originalTypes: summarizePatchTypes(patch), + }); } + return { kind: "rejected_invalid" }; } + + const existing = this.pending.get(id); + const isNewId = existing === undefined; + const merged = { ...(existing ?? {}), ...sanitized }; this.pending.set(id, merged); + if (isTerminalPatch(merged)) { + this.nonTerminalIds.delete(id); + } else { + this.nonTerminalIds.add(id); + } + let result: MessageRequestUpdateEnqueueResult = { kind: "enqueued" }; // 队列上限保护:DB 异常时避免无限增长导致 OOM - if (this.pending.size > this.config.maxPending) { - // 优先丢弃非“终态”更新(没有 durationMs 的条目),尽量保留请求完成信息 - let droppedId: number | undefined; - let droppedPatch: MessageRequestUpdatePatch | undefined; - - for (const [candidateId, candidatePatch] of this.pending) { - if (candidatePatch.durationMs === undefined) { - droppedId = candidateId; - droppedPatch = candidatePatch; - break; - } - } - - if (droppedId === undefined) { - const first = this.pending.entries().next().value as - | [number, MessageRequestUpdatePatch] - | undefined; - if (first) { - droppedId = first[0]; - droppedPatch = first[1]; + if (isNewId && this.pending.size > this.config.maxPending) { + const trimResult = this.trimPendingToMaxPending({ currentId: id, allowDropTerminal: true }); + if (trimResult.droppedCount > 0) { + if (trimResult.droppedCurrent) { + result = { + kind: "dropped_overflow", + patch: trimResult.droppedCurrentPatch ?? sanitized, + }; } - } - - if (droppedId !== undefined) { - this.pending.delete(droppedId); - logger.warn("[MessageRequestWriteBuffer] Pending queue overflow, dropping update", { + logger.warn("[MessageRequestWriteBuffer] Pending queue overflow, dropping updates", { maxPending: this.config.maxPending, - droppedId, - droppedHasDurationMs: droppedPatch?.durationMs !== undefined, + droppedCount: trimResult.droppedCount, + droppedTerminalCount: trimResult.droppedTerminalCount, + droppedIdsSample: trimResult.droppedIdsSample, + droppedCurrent: trimResult.droppedCurrent, currentPending: this.pending.size, }); } @@ -212,36 +796,339 @@ class MessageRequestWriteBuffer { // flush 过程中有新任务:标记需要再跑一轮(避免刚好 flush 完成时遗漏) if (this.flushInFlight) { this.flushAgainAfterCurrent = true; - return; + return result; } // 停止阶段不再调度 timer,避免阻止进程退出 if (!this.stopping) { - this.ensureFlushTimer(); + // 终态 patch 尽快落库,减少 duration/status 为空的“悬挂窗口” + this.ensureFlushTimer(isTerminalPatch(merged) ? TERMINAL_FLUSH_DELAY_MS : undefined); } // 达到批量阈值时尽快 flush,降低 durationMs 为空的“悬挂时间” if (this.pending.size >= this.config.batchSize) { + // 若刚刚调度了 timer(尤其是终态 10ms timer),这里直接 flush 会让 timer 变成多余噪声; + // 提前清理可避免短时间内重复触发 flush 以及额外的事件循环负担。 + this.clearFlushTimer(); void this.flush(); } + + return result; } - private ensureFlushTimer(): void { - if (this.stopping || this.flushTimer) { + private ensureFlushTimer(delayMs?: number): void { + if (this.stopping) { return; } + const delay = Math.max(0, delayMs ?? this.config.flushIntervalMs); + const dueAt = Date.now() + delay; + + if (this.flushTimer) { + if (this.flushTimerDueAt !== null && this.flushTimerDueAt <= dueAt) { + return; + } + this.clearFlushTimer(); + } + + this.flushTimerDueAt = dueAt; this.flushTimer = setTimeout(() => { this.flushTimer = null; + this.flushTimerDueAt = null; void this.flush(); - }, this.config.flushIntervalMs); + }, delay); } private clearFlushTimer(): void { if (this.flushTimer) { clearTimeout(this.flushTimer); this.flushTimer = null; + this.flushTimerDueAt = null; + } + } + + private requeueBatchForRetry(batch: MessageRequestUpdateRecord[]): void { + // 合并策略:保留“更新更晚”的字段(existing 优先),避免覆盖新数据 + for (const item of batch) { + const existing = this.pending.get(item.id) ?? {}; + const merged = { ...item.patch, ...existing }; + this.pending.set(item.id, merged); + if (isTerminalPatch(merged)) { + this.nonTerminalIds.delete(item.id); + } else { + this.nonTerminalIds.add(item.id); + } + } + + const trimResult = this.trimPendingToMaxPending({ allowDropTerminal: false }); + if (trimResult.droppedCount > 0) { + logger.warn( + "[MessageRequestWriteBuffer] Pending queue overflow after requeue, dropping updates", + { + maxPending: this.config.maxPending, + droppedCount: trimResult.droppedCount, + droppedTerminalCount: trimResult.droppedTerminalCount, + droppedIdsSample: trimResult.droppedIdsSample, + currentPending: this.pending.size, + } + ); + } + + // requeue 场景下尽量不丢弃终态 patch(duration/status),避免“请求中”悬挂;若仍超上限,允许暂时超过 maxPending。 + if (this.pending.size > this.config.maxPending) { + const now = Date.now(); + if (now - _lastRequeueOverflowLogAt > REQUEUE_OVERFLOW_LOG_THROTTLE_MS) { + _lastRequeueOverflowLogAt = now; + logger.warn( + "[MessageRequestWriteBuffer] Pending queue exceeds maxPending after requeue, keeping terminal patches", + { + maxPending: this.config.maxPending, + currentPending: this.pending.size, + } + ); + } + } + + // 极端防御:若 DB 长时间不可用且流量持续涌入,requeue + enqueue 可能导致队列在短时间内暴涨。 + // 这里设置一个硬上限,避免进程被 OOM 杀死(OOM 会导致更多终态丢失/孤儿请求放大)。 + const hardCap = this.config.maxPending * 2; + if (hardCap > 0 && this.pending.size > hardCap) { + const hardTrim = this.trimPendingToMaxPending({ allowDropTerminal: true }); + if (hardTrim.droppedCount > 0) { + logger.error("[MessageRequestWriteBuffer] Pending queue exceeded hard cap after requeue", { + maxPending: this.config.maxPending, + hardCap, + droppedCount: hardTrim.droppedCount, + droppedTerminalCount: hardTrim.droppedTerminalCount, + droppedIdsSample: hardTrim.droppedIdsSample, + currentPending: this.pending.size, + }); + } + } + } + + private pruneNonTerminalIds(): void { + // 防御:确保 nonTerminalIds 不含 stale/终态条目,避免 overflow 场景下反复扫描“幽灵 id” + const toDelete: number[] = []; + for (const id of this.nonTerminalIds) { + const patch = this.pending.get(id); + if (!patch || isTerminalPatch(patch)) { + toDelete.push(id); + } + } + for (const id of toDelete) { + this.nonTerminalIds.delete(id); + } + } + + private trimPendingToMaxPending(options?: { currentId?: number; allowDropTerminal?: boolean }): { + droppedCount: number; + droppedTerminalCount: number; + droppedIdsSample: number[]; + droppedCurrent: boolean; + droppedCurrentPatch?: MessageRequestUpdatePatch; + } { + this.pruneNonTerminalIds(); + + const currentId = options?.currentId; + const allowDropTerminal = options?.allowDropTerminal ?? true; + let droppedCount = 0; + let droppedTerminalCount = 0; + const droppedIdsSample: number[] = []; + let droppedCurrent = false; + let droppedCurrentPatch: MessageRequestUpdatePatch | undefined; + + while (this.pending.size > this.config.maxPending) { + let droppedId: number | undefined; + let droppedPatch: MessageRequestUpdatePatch | undefined; + + // 优先丢弃非终态更新(不含 durationMs/statusCode 的条目),尽量保留请求完成信息。 + // 若存在其他可丢弃条目,尽量不要丢弃 currentId(避免本次 enqueue 的 patch 被优先淘汰)。 + for (const candidateId of this.nonTerminalIds) { + if (candidateId === currentId) { + continue; + } + const candidatePatch = this.pending.get(candidateId); + if (!candidatePatch) { + this.nonTerminalIds.delete(candidateId); + continue; + } + droppedId = candidateId; + droppedPatch = candidatePatch; + break; + } + + if (droppedId === undefined) { + for (const candidateId of this.nonTerminalIds) { + const candidatePatch = this.pending.get(candidateId); + if (!candidatePatch) { + this.nonTerminalIds.delete(candidateId); + continue; + } + droppedId = candidateId; + droppedPatch = candidatePatch; + break; + } + } + + if (droppedId === undefined) { + if (!allowDropTerminal) { + break; + } + // 当 pending 全部为终态 patch 时,不应随机淘汰已有终态(会导致其他请求永久缺失完成信息)。 + // enqueue 路径会优先丢弃“当前” patch 并由调用方决定是否同步兜底;其他场景(如 requeue)则退化为丢弃最早条目。 + if (currentId !== undefined && this.pending.has(currentId)) { + droppedId = currentId; + droppedPatch = this.pending.get(currentId); + } else { + const first = this.pending.keys().next(); + if (first.done) { + break; + } + droppedId = first.value; + droppedPatch = this.pending.get(droppedId); + } + } + + if (droppedId === undefined) { + break; + } + + this.pending.delete(droppedId); + this.nonTerminalIds.delete(droppedId); + + if (droppedPatch && isTerminalPatch(droppedPatch)) { + droppedTerminalCount++; + } + + droppedCount++; + if (droppedIdsSample.length < 5) { + droppedIdsSample.push(droppedId); + } + if (droppedId === currentId) { + droppedCurrent = true; + droppedCurrentPatch = droppedPatch; + } } + + return { + droppedCount, + droppedTerminalCount, + droppedIdsSample, + droppedCurrent, + droppedCurrentPatch, + }; + } + + private handleTransientPerItemError( + error: unknown, + batch: MessageRequestUpdateRecord[], + startIndex: number, + logMessage: string + ): true { + // 连接/暂态问题:把当前及剩余条目回队列,留待下次 flush + this.requeueBatchForRetry(batch.slice(startIndex)); + logger.error(logMessage, { + error: error instanceof Error ? error.message : String(error), + errorCode: getErrorCode(error), + pending: this.pending.size, + }); + return true; + } + + private async flushBatchPerItem(batch: MessageRequestUpdateRecord[]): Promise { + for (let index = 0; index < batch.length; index++) { + const item = batch[index]; + if (!item) { + continue; + } + + const patchStrategies = [ + { name: "full" as const, patch: item.patch }, + { name: "safe" as const, patch: getSafePatch(item.patch) }, + { name: "minimal" as const, patch: getMinimalPatch(item.patch) }, + ]; + const noSqlColumnsStrategies: Array<(typeof patchStrategies)[number]["name"]> = []; + + let lastFailure: { + kind: "build" | "execute"; + strategy: "full" | "safe" | "minimal"; + error: unknown; + } | null = null; + + for (const { name, patch } of patchStrategies) { + let singleQuery: SQL | null = null; + try { + singleQuery = buildBatchUpdateSql([{ id: item.id, patch }]); + } catch (error) { + lastFailure = { kind: "build", strategy: name, error }; + continue; + } + + if (!singleQuery) { + // 本策略未产生任何可更新列(例如字段被过滤/无法序列化):尝试下一个降级策略。 + // 注意:这不是“错误”,不要覆盖前面真实的 DB/data 错误上下文。 + noSqlColumnsStrategies.push(name); + continue; + } + + try { + await db.execute(singleQuery); + lastFailure = null; + break; + } catch (error) { + lastFailure = { kind: "execute", strategy: name, error }; + if (!isDataRelatedDbError(error)) { + return this.handleTransientPerItemError( + error, + batch, + index, + "[MessageRequestWriteBuffer] Per-item flush hit transient error, will retry" + ); + } + } + } + + if (!lastFailure && noSqlColumnsStrategies.length === patchStrategies.length) { + // 所有策略都无法产生 SQL:通常是 patch 字段被过滤/无法序列化,且不含终态字段(duration/status)。 + // 这类更新对“请求中”问题无关键影响,直接跳过即可;仅用 debug 降噪。 + logger.debug("[MessageRequestWriteBuffer] Skipping update with no writable columns", { + requestId: item.id, + keys: Object.keys(item.patch), + types: summarizePatchTypes(item.patch), + noSqlColumnsStrategies, + }); + continue; + } + + if (lastFailure) { + const isTerminal = isTerminalPatch(item.patch); + const log = isTerminal ? logger.error : logger.warn; + log("[MessageRequestWriteBuffer] Dropping update to unblock queue", { + requestId: item.id, + isTerminal, + keys: Object.keys(item.patch), + types: summarizePatchTypes(item.patch), + noSqlColumnsStrategies, + sample: (() => { + try { + return JSON.stringify(item.patch).slice(0, 200); + } catch { + return undefined; + } + })(), + failureKind: lastFailure.kind, + failureStrategy: lastFailure.strategy, + error: + lastFailure.error instanceof Error + ? lastFailure.error.message + : String(lastFailure.error), + errorCode: getErrorCode(lastFailure.error), + }); + } + } + + return false; } async flush(): Promise { @@ -256,10 +1143,38 @@ class MessageRequestWriteBuffer { this.flushInFlight = (async () => { do { this.flushAgainAfterCurrent = false; + let shouldYieldToTimer = false; while (this.pending.size > 0) { const batch = takeBatch(this.pending, this.config.batchSize); - const query = buildBatchUpdateSql(batch); + for (const item of batch) { + this.nonTerminalIds.delete(item.id); + } + let query: SQL | null = null; + + try { + query = buildBatchUpdateSql(batch); + } catch (error) { + logger.error( + "[MessageRequestWriteBuffer] Build batch SQL failed, falling back to per-item writes", + { + error: error instanceof Error ? error.message : String(error), + errorCode: getErrorCode(error), + pending: this.pending.size, + batchSize: batch.length, + } + ); + + // 通过 per-item 写入确保队列可排空,避免 build 失败导致无限重试。 + const shouldRetryLater = await this.flushBatchPerItem(batch); + if (shouldRetryLater) { + // 暂态错误:避免 flushAgainAfterCurrent 在高并发下形成忙等/重试风暴,交由 timer 退避重试。 + shouldYieldToTimer = true; + break; + } + continue; + } + if (!query) { continue; } @@ -267,23 +1182,47 @@ class MessageRequestWriteBuffer { try { await db.execute(query); } catch (error) { - // 失败重试:将 batch 放回队列 - // 合并策略:保留“更新更晚”的字段(existing 优先),避免覆盖新数据 - for (const item of batch) { - const existing = this.pending.get(item.id) ?? {}; - this.pending.set(item.id, { ...item.patch, ...existing }); + if (isDataRelatedDbError(error)) { + logger.error( + "[MessageRequestWriteBuffer] Flush failed with data error, falling back to per-item writes", + { + error: error instanceof Error ? error.message : String(error), + errorCode: getErrorCode(error), + pending: this.pending.size, + batchSize: batch.length, + } + ); + + const shouldRetryLater = await this.flushBatchPerItem(batch); + + if (shouldRetryLater) { + // 暂态错误:避免 flushAgainAfterCurrent 在高并发下形成忙等/重试风暴,交由 timer 退避重试。 + shouldYieldToTimer = true; + break; + } + + continue; } + // 失败重试:将 batch 放回队列 + this.requeueBatchForRetry(batch); + logger.error("[MessageRequestWriteBuffer] Flush failed, will retry later", { error: error instanceof Error ? error.message : String(error), + errorCode: getErrorCode(error), pending: this.pending.size, batchSize: batch.length, }); // DB 异常时不在当前循环内死磕,留待下一次 timer/手动 flush + // 同时避免 flushAgainAfterCurrent 在高并发下形成忙等/重试风暴。 + shouldYieldToTimer = true; break; } } + if (shouldYieldToTimer) { + break; + } } while (this.flushAgainAfterCurrent); })().finally(() => { this.flushInFlight = null; @@ -299,11 +1238,19 @@ class MessageRequestWriteBuffer { async stop(): Promise { this.stopping = true; this.clearFlushTimer(); + const pendingBefore = this.pending.size; await this.flush(); // stop 期间尽量补刷一次,避免极小概率竞态导致的 tail 更新残留 if (this.pending.size > 0) { await this.flush(); } + if (this.pending.size > 0) { + logger.error("[MessageRequestWriteBuffer] Stop finished with pending updates remaining", { + pendingBefore, + pendingAfter: this.pending.size, + nonTerminalIds: this.nonTerminalIds.size, + }); + } } } @@ -311,25 +1258,28 @@ let _buffer: MessageRequestWriteBuffer | null = null; let _bufferState: "running" | "stopping" | "stopped" = "running"; function getBuffer(): MessageRequestWriteBuffer | null { + if (_bufferState !== "running") { + return null; + } if (!_buffer) { - if (_bufferState !== "running") { - return null; - } _buffer = new MessageRequestWriteBuffer(loadWriterConfig()); } return _buffer; } -export function enqueueMessageRequestUpdate(id: number, patch: MessageRequestUpdatePatch): void { +export function enqueueMessageRequestUpdate( + id: number, + patch: MessageRequestUpdatePatch +): MessageRequestUpdateEnqueueResult { // 只在 async 模式下启用队列,避免额外内存/定时器开销 if (getEnvConfig().MESSAGE_REQUEST_WRITE_MODE !== "async") { - return; + return { kind: "buffer_unavailable" }; } const buffer = getBuffer(); if (!buffer) { - return; + return { kind: "buffer_unavailable" }; } - buffer.enqueue(id, patch); + return buffer.enqueue(id, patch); } export async function flushMessageRequestWriteBuffer(): Promise { diff --git a/src/repository/message.ts b/src/repository/message.ts index 32a849e6d..7a41bf770 100644 --- a/src/repository/message.ts +++ b/src/repository/message.ts @@ -5,13 +5,22 @@ import { db } from "@/drizzle/db"; import { keys as keysTable, messageRequest, providers, usageLedger, users } from "@/drizzle/schema"; import { getEnvConfig } from "@/lib/config/env.schema"; import { isLedgerOnlyMode } from "@/lib/ledger-fallback"; +import { logger } from "@/lib/logger"; import { formatCostForStorage } from "@/lib/utils/currency"; +import { + ORPHANED_MESSAGE_REQUEST_ERROR_CODE, + ORPHANED_MESSAGE_REQUEST_STATUS_CODE, +} from "@/repository/message-orphaned-requests"; +import type { MessageRequestUpdatePatch } from "@/repository/message-write-buffer"; +import { + enqueueMessageRequestUpdate, + sanitizeMessageRequestUpdatePatch, +} from "@/repository/message-write-buffer"; import type { CreateMessageRequestData, MessageRequest, ProviderChainItem } from "@/types/message"; import type { SpecialSetting } from "@/types/special-settings"; import { LEDGER_BILLING_CONDITION } from "./_shared/ledger-conditions"; import { EXCLUDE_WARMUP_CONDITION } from "./_shared/message-request-conditions"; import { toMessageRequest } from "./_shared/transformers"; -import { enqueueMessageRequestUpdate } from "./message-write-buffer"; /** * 创建消息请求记录 @@ -71,22 +80,58 @@ export async function createMessageRequest( return toMessageRequest(result); } -/** - * 更新消息请求的耗时 - */ -export async function updateMessageRequestDuration(id: number, durationMs: number): Promise { - if (getEnvConfig().MESSAGE_REQUEST_WRITE_MODE === "async") { - enqueueMessageRequestUpdate(id, { durationMs }); +async function writeMessageRequestUpdateToDb( + id: number, + patch: MessageRequestUpdatePatch +): Promise { + // 防御:即使 patch 来源于 buffer 的已清洗数据(例如 dropped_overflow),这里也统一再 sanitize 一次(幂等)。 + // 这样可避免未来调用点误传未清洗 patch 时把脏数据直接写入数据库。 + const sanitized = sanitizeMessageRequestUpdatePatch(patch); + if (Object.keys(sanitized).length === 0) { + const definedKeys = Object.entries(patch) + .filter(([, value]) => value !== undefined) + .map(([key]) => key); + if (definedKeys.length > 0) { + logger.warn("[MessageRepository] Message request patch rejected: empty after sanitize", { + requestId: id, + keys: definedKeys, + }); + } return; } - await db + const updated = await db .update(messageRequest) .set({ - durationMs: durationMs, + ...sanitized, updatedAt: new Date(), }) - .where(eq(messageRequest.id, id)); + .where(and(eq(messageRequest.id, id), isNull(messageRequest.deletedAt))) + .returning({ id: messageRequest.id }); + + if (updated.length === 0) { + logger.warn( + "[MessageRepository] Message request update affected 0 rows (missing or soft-deleted)", + { + requestId: id, + keys: Object.keys(sanitized), + } + ); + } +} + +/** + * 更新消息请求的耗时 + */ +export async function updateMessageRequestDuration(id: number, durationMs: number): Promise { + const enqueueResult = enqueueMessageRequestUpdate(id, { durationMs }); + if (enqueueResult.kind === "enqueued") { + return; + } + + const patchToWrite = + enqueueResult.kind === "dropped_overflow" ? enqueueResult.patch : { durationMs }; + await writeMessageRequestUpdateToDb(id, patchToWrite); } /** @@ -101,18 +146,32 @@ export async function updateMessageRequestCost( return; } - if (getEnvConfig().MESSAGE_REQUEST_WRITE_MODE === "async") { - enqueueMessageRequestUpdate(id, { costUsd: formattedCost }); + const enqueueResult = enqueueMessageRequestUpdate(id, { costUsd: formattedCost }); + if (enqueueResult.kind === "rejected_invalid") { + logger.warn("[MessageRepository] costUsd update rejected as invalid, skipping", { + requestId: id, + costUsd: formattedCost, + }); + return; + } + if (enqueueResult.kind === "enqueued") { return; } - await db - .update(messageRequest) - .set({ - costUsd: formattedCost, - updatedAt: new Date(), - }) - .where(eq(messageRequest.id, id)); + // costUsd 非终态信息:overflow 时尽量丢弃即可,避免压力峰值下放大同步 DB 写入。 + // 但若 overflow 过程中丢失了终态信息(duration/status),则需要同步写入兜底以避免“请求中”悬挂。 + if (enqueueResult.kind === "dropped_overflow") { + const patch = enqueueResult.patch; + // 注意:patch 可能携带已合并的非终态字段;当其不含终态字段时这里仍选择丢弃, + // 避免压力峰值下放大同步 DB 写入(非终态信息允许在极端情况下丢失)。 + if (patch.durationMs === undefined && patch.statusCode === undefined) { + return; + } + await writeMessageRequestUpdateToDb(id, patch); + return; + } + + await writeMessageRequestUpdateToDb(id, { costUsd: formattedCost }); } /** @@ -134,78 +193,135 @@ export async function updateMessageRequestDetails( errorMessage?: string; errorStack?: string; // 完整堆栈信息 errorCause?: string; // 嵌套错误原因(JSON 格式) - model?: string; // ⭐ 新增:支持更新重定向后的模型名称 - providerId?: number; // ⭐ 新增:支持更新最终供应商ID(重试切换后) + model?: string; // 新增:支持更新重定向后的模型名称 + providerId?: number; // 新增:支持更新最终供应商ID(重试切换后) context1mApplied?: boolean; // 是否应用了1M上下文窗口 swapCacheTtlApplied?: boolean; // Swap Cache TTL Billing active at request time specialSettings?: CreateMessageRequestData["special_settings"]; // 特殊设置(审计/展示) } ): Promise { - if (getEnvConfig().MESSAGE_REQUEST_WRITE_MODE === "async") { - enqueueMessageRequestUpdate(id, details); + const enqueueResult = enqueueMessageRequestUpdate(id, details); + if (enqueueResult.kind === "enqueued") { + return; + } + if (enqueueResult.kind === "rejected_invalid") { + // patch 在 buffer 内已被 sanitize 并判定为空;避免重复 sanitize/重复告警 + return; + } + + // overflow 场景下:尽量丢弃非终态 patch,避免在压力峰值时反向放大 DB 写入。 + // 但如果 overflow 丢失了终态信息(duration/status),则必须同步写入兜底以避免请求长期卡在“请求中”。 + if (enqueueResult.kind === "dropped_overflow") { + const patch = enqueueResult.patch; + // 注意:patch 可能携带已合并的非终态字段;当其不含终态字段时这里仍选择丢弃, + // 避免压力峰值下放大同步 DB 写入(非终态信息允许在极端情况下丢失)。 + if (patch.durationMs === undefined && patch.statusCode === undefined) { + return; + } + await writeMessageRequestUpdateToDb(id, patch); return; } - const updateData: Record = { - updatedAt: new Date(), + await writeMessageRequestUpdateToDb(id, details); +} + +/** + * 封闭“孤儿请求”记录(防御性修复) + * + * 在 MESSAGE_REQUEST_WRITE_MODE=async 时,请求终态信息(duration/status/tokens/cost 等)会先进入内存队列, + * 再异步批量刷入数据库。若进程被 OOM Killer/SIGKILL 等非优雅方式终止,尾部更新会丢失, + * 导致 message_request 记录长期保持“请求中”(duration_ms 长期为空;status_code 也可能为空,或已写入但 duration_ms 缺失)。 + * + * 影响: + * - Dashboard/统计把这些记录当作“进行中”,导致异常展示与聚合膨胀; + * - 某些页面会高频轮询“活跃请求”,在孤儿记录持续累积时可能引发内存与性能风险。 + * + * 本函数会把超过阈值仍未落下终态的记录标记为已结束(未知失败),避免无限累积。 + * + * 约束:staleAfterMs 最小为 60s(小于会被 clamp),避免误封闭真正的长耗时请求。 + */ +export async function sealOrphanedMessageRequests(options?: { + staleAfterMs?: number; + limit?: number; +}): Promise<{ sealedCount: number }> { + const env = getEnvConfig(); + + const toFiniteInt = (value: unknown): number | null => { + if (typeof value !== "number" || !Number.isFinite(value)) { + return null; + } + return Math.trunc(value); }; - if (details.statusCode !== undefined) { - updateData.statusCode = details.statusCode; - } - if (details.inputTokens !== undefined) { - updateData.inputTokens = details.inputTokens; - } - if (details.outputTokens !== undefined) { - updateData.outputTokens = details.outputTokens; - } - if (details.ttfbMs !== undefined) { - updateData.ttfbMs = details.ttfbMs; - } - if (details.cacheCreationInputTokens !== undefined) { - updateData.cacheCreationInputTokens = details.cacheCreationInputTokens; - } - if (details.cacheReadInputTokens !== undefined) { - updateData.cacheReadInputTokens = details.cacheReadInputTokens; - } - if (details.cacheCreation5mInputTokens !== undefined) { - updateData.cacheCreation5mInputTokens = details.cacheCreation5mInputTokens; - } - if (details.cacheCreation1hInputTokens !== undefined) { - updateData.cacheCreation1hInputTokens = details.cacheCreation1hInputTokens; - } - if (details.cacheTtlApplied !== undefined) { - updateData.cacheTtlApplied = details.cacheTtlApplied; - } - if (details.providerChain !== undefined) { - updateData.providerChain = details.providerChain; - } - if (details.errorMessage !== undefined) { - updateData.errorMessage = details.errorMessage; - } - if (details.errorStack !== undefined) { - updateData.errorStack = details.errorStack; - } - if (details.errorCause !== undefined) { - updateData.errorCause = details.errorCause; - } - if (details.model !== undefined) { - updateData.model = details.model; - } - if (details.providerId !== undefined) { - updateData.providerId = details.providerId; - } - if (details.context1mApplied !== undefined) { - updateData.context1mApplied = details.context1mApplied; - } - if (details.swapCacheTtlApplied !== undefined) { - updateData.swapCacheTtlApplied = details.swapCacheTtlApplied; - } - if (details.specialSettings !== undefined) { - updateData.specialSettings = details.specialSettings; - } + const fetchBodyTimeout = toFiniteInt(env.FETCH_BODY_TIMEOUT) ?? 600_000; + const staleAfterMsCandidate = toFiniteInt(options?.staleAfterMs) ?? fetchBodyTimeout + 60_000; + const staleAfterMs = Math.max(60_000, staleAfterMsCandidate); + + const limitCandidate = toFiniteInt(options?.limit) ?? 1000; + const limit = Math.max(1, limitCandidate); + const threshold = new Date(Date.now() - staleAfterMs); + + // 注意:这里使用 raw SQL,以避免 Drizzle 在大表上的构造开销;同时直接内联 warmup 过滤条件,避免表别名导致列引用失效。 + const query = sql<{ id: number }>` + WITH candidates AS ( + SELECT id + FROM message_request + WHERE deleted_at IS NULL + AND duration_ms IS NULL + AND created_at < ${threshold} + AND (blocked_by IS NULL OR blocked_by <> 'warmup') + ORDER BY created_at ASC + LIMIT ${limit} + ) + UPDATE message_request + SET + duration_ms = CASE + WHEN status_code IS NULL THEN ( + LEAST( + 2147483647, + GREATEST(0, (EXTRACT(EPOCH FROM (NOW() - created_at)) * 1000)) + )::int + ) + ELSE ( + LEAST( + 2147483647, + GREATEST(0, COALESCE(ttfb_ms, 0)) + )::int + ) + END, + status_code = COALESCE(status_code, ${ORPHANED_MESSAGE_REQUEST_STATUS_CODE}), + error_message = CASE + WHEN status_code IS NULL THEN COALESCE(error_message, ${ORPHANED_MESSAGE_REQUEST_ERROR_CODE}) + ELSE error_message + END, + updated_at = NOW() + WHERE id IN (SELECT id FROM candidates) + AND deleted_at IS NULL + AND duration_ms IS NULL + AND created_at < ${threshold} + AND (blocked_by IS NULL OR blocked_by <> 'warmup') + RETURNING id + `; + + const result = await db.execute(query); + const resultAny = result as unknown as { + rows?: unknown[]; + rowCount?: number; + [Symbol.iterator]?: unknown; + }; - await db.update(messageRequest).set(updateData).where(eq(messageRequest.id, id)); + const sealedCount = Array.isArray(result) + ? result.length + : typeof resultAny.rowCount === "number" + ? resultAny.rowCount + : Array.isArray(resultAny.rows) + ? resultAny.rows.length + : typeof (resultAny as unknown as { [Symbol.iterator]?: unknown })[Symbol.iterator] === + "function" + ? Array.from(resultAny as unknown as Iterable).length + : 0; + + return { sealedCount }; } /** diff --git a/tests/helpers/drizzle.ts b/tests/helpers/drizzle.ts new file mode 100644 index 000000000..a70a74b83 --- /dev/null +++ b/tests/helpers/drizzle.ts @@ -0,0 +1,15 @@ +import { CasingCache } from "drizzle-orm/casing"; + +type QueryToSql = { + toQuery: (config: any) => { sql: string; params: unknown[] }; +}; + +export function toSqlText(query: QueryToSql) { + return query.toQuery({ + casing: new CasingCache(), + escapeName: (name: string) => `"${name}"`, + escapeParam: (index: number) => `$${index}`, + escapeString: (value: string) => `'${value}'`, + paramStartIndex: { value: 1 }, + }); +} diff --git a/tests/helpers/env.ts b/tests/helpers/env.ts new file mode 100644 index 000000000..ed851e3fb --- /dev/null +++ b/tests/helpers/env.ts @@ -0,0 +1,19 @@ +export type EnvSnapshot = Record; + +export function snapshotEnv(keys: string[]): EnvSnapshot { + const snapshot: EnvSnapshot = {}; + for (const key of keys) { + snapshot[key] = process.env[key]; + } + return snapshot; +} + +export function restoreEnv(snapshot: EnvSnapshot): void { + for (const [key, value] of Object.entries(snapshot)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } +} diff --git a/tests/integration/usage-ledger.test.ts b/tests/integration/usage-ledger.test.ts index 5b7204e8a..303d005cd 100644 --- a/tests/integration/usage-ledger.test.ts +++ b/tests/integration/usage-ledger.test.ts @@ -280,7 +280,9 @@ run("usage ledger integration", () => { describe("backfill", () => { test( "backfill copies non-warmup message_request rows when ledger rows are missing", - { timeout: 60_000 }, + { + timeout: 60_000, + }, async () => { const userId = nextUserId(); const providerId = nextProviderId(); diff --git a/tests/unit/lib/proxy-status-tracker.test.ts b/tests/unit/lib/proxy-status-tracker.test.ts new file mode 100644 index 000000000..7f20d9c06 --- /dev/null +++ b/tests/unit/lib/proxy-status-tracker.test.ts @@ -0,0 +1,69 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("ProxyStatusTracker", () => { + const selectMock = vi.fn(); + const warnMock = vi.fn(); + + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z")); + vi.resetModules(); + + selectMock.mockReset(); + warnMock.mockReset(); + + let callCount = 0; + selectMock.mockImplementation(() => ({ + from: () => ({ + where: async () => { + callCount++; + if (callCount === 1) { + return [{ id: 1, name: "u1" }]; + } + throw new Error("db down"); + }, + }), + })); + + vi.doMock("@/drizzle/db", () => ({ + db: { + select: selectMock, + execute: vi.fn(), + }, + })); + + vi.doMock("@/lib/logger", () => ({ + logger: { + warn: warnMock, + error: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + trace: vi.fn(), + }, + })); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("刷新失败时应返回过期缓存(避免 dashboard 轮询错误风暴)", async () => { + const { ProxyStatusTracker } = await import("@/lib/proxy-status-tracker"); + + const tracker = new ProxyStatusTracker(); + vi.spyOn(tracker as any, "loadActiveRequests").mockResolvedValue([]); + vi.spyOn(tracker as any, "loadLastRequests").mockResolvedValue([]); + + const first = await tracker.getAllUsersStatus(); + expect(first.users.map((u) => u.userName)).toEqual(["u1"]); + + // 5000ms TTL 过期后,第二次刷新失败应降级为返回缓存 + vi.advanceTimersByTime(6000); + + const second = await tracker.getAllUsersStatus(); + expect(second.users.map((u) => u.userName)).toEqual(["u1"]); + + expect(selectMock).toHaveBeenCalledTimes(2); + expect(warnMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/unit/repository/message-orphaned-requests.test.ts b/tests/unit/repository/message-orphaned-requests.test.ts new file mode 100644 index 000000000..36557db97 --- /dev/null +++ b/tests/unit/repository/message-orphaned-requests.test.ts @@ -0,0 +1,100 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { restoreEnv, snapshotEnv } from "../../helpers/env"; +import { toSqlText } from "../../helpers/drizzle"; + +describe("sealOrphanedMessageRequests", () => { + const envKeys = ["NODE_ENV", "DSN", "FETCH_BODY_TIMEOUT"]; + const originalEnv = snapshotEnv(envKeys); + + const executeMock = vi.fn(async () => [{ id: 1 }, { id: 2 }]); + + beforeEach(() => { + vi.resetModules(); + executeMock.mockClear(); + + process.env.NODE_ENV = "test"; + process.env.DSN = "postgres://postgres:postgres@localhost:5432/claude_code_hub_test"; + process.env.FETCH_BODY_TIMEOUT = "1000"; + + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z")); + + vi.doMock("@/drizzle/db", () => ({ + db: { + execute: executeMock, + // 避免 tests/setup.ts 的 afterAll 清理逻辑因 mock 缺失 select 而报错 + select: () => ({ + from: () => ({ + where: async () => [], + }), + }), + }, + })); + }); + + afterEach(() => { + vi.useRealTimers(); + restoreEnv(originalEnv); + }); + + it("应批量封闭超时仍未落终态的 message_request 并返回 sealedCount", async () => { + const { sealOrphanedMessageRequests } = await import("@/repository/message"); + const { ORPHANED_MESSAGE_REQUEST_ERROR_CODE, ORPHANED_MESSAGE_REQUEST_STATUS_CODE } = + await import("@/repository/message-orphaned-requests"); + + const result = await sealOrphanedMessageRequests({ staleAfterMs: 10, limit: 5 }); + + expect(result.sealedCount).toBe(2); + expect(executeMock).toHaveBeenCalledTimes(1); + + const query = executeMock.mock.calls[0]?.[0]; + const built = toSqlText(query); + + expect(built.sql).toContain("UPDATE message_request"); + expect(built.sql).toContain("duration_ms IS NULL"); + expect(built.sql).toContain("WHEN status_code IS NULL"); + expect(built.sql).not.toContain("AND status_code IS NULL"); + expect((built.sql.match(/created_at p instanceof Date) as Date | undefined; + expect(threshold).toBeInstanceOf(Date); + expect(threshold?.toISOString()).toBe("2025-12-31T23:59:00.000Z"); + }); + + it("应兼容 db.execute 返回 rowCount 的驱动实现", async () => { + executeMock.mockImplementationOnce(async () => ({ rowCount: 3 }) as any); + + const { sealOrphanedMessageRequests } = await import("@/repository/message"); + + const result = await sealOrphanedMessageRequests({ staleAfterMs: 10, limit: 5 }); + + expect(result.sealedCount).toBe(3); + }); + + it("默认 staleAfterMs 应基于 FETCH_BODY_TIMEOUT + 60s 且不低于 60s", async () => { + const { sealOrphanedMessageRequests } = await import("@/repository/message"); + + await sealOrphanedMessageRequests(); + + expect(executeMock).toHaveBeenCalledTimes(1); + + const query = executeMock.mock.calls[0]?.[0]; + const built = toSqlText(query); + + const threshold = built.params.find((p) => p instanceof Date) as Date | undefined; + expect(threshold).toBeInstanceOf(Date); + // FETCH_BODY_TIMEOUT=1000ms -> staleAfterMs=61000ms + expect(threshold?.toISOString()).toBe("2025-12-31T23:58:59.000Z"); + }); +}); diff --git a/tests/unit/repository/message-write-buffer.test.ts b/tests/unit/repository/message-write-buffer.test.ts index 17f5ab192..4e394e741 100644 --- a/tests/unit/repository/message-write-buffer.test.ts +++ b/tests/unit/repository/message-write-buffer.test.ts @@ -1,33 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -type EnvSnapshot = Partial>; - -function snapshotEnv(keys: string[]): EnvSnapshot { - const snapshot: EnvSnapshot = {}; - for (const key of keys) { - snapshot[key] = process.env[key]; - } - return snapshot; -} - -function restoreEnv(snapshot: EnvSnapshot) { - for (const [key, value] of Object.entries(snapshot)) { - if (value === undefined) { - delete process.env[key]; - } else { - process.env[key] = value; - } - } -} - -function toSqlText(query: { toQuery: (config: any) => { sql: string; params: unknown[] } }) { - return query.toQuery({ - escapeName: (name: string) => `"${name}"`, - escapeParam: (index: number) => `$${index}`, - escapeString: (value: string) => `'${value}'`, - paramStartIndex: { value: 1 }, - }); -} +import { restoreEnv, snapshotEnv } from "../../helpers/env"; +import { toSqlText } from "../../helpers/drizzle"; function createDeferred() { let resolve!: (value: T) => void; @@ -143,6 +116,41 @@ describe("message_request 异步批量写入", () => { expect(built.sql).toContain("::jsonb"); }); + it("遇到数据类 DB 错误时应降级写入并避免卡死队列", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + executeMock.mockImplementation(async (query) => { + const built = toSqlText(query); + if (built.sql.includes("::numeric")) { + const error = new Error("invalid input syntax for type numeric", { + cause: { code: "22P02" }, + }); + throw error; + } + return []; + }); + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + enqueueMessageRequestUpdate(1, { durationMs: 123, statusCode: 200, costUsd: "0.000123" }); + + await stopMessageRequestWriteBuffer(); + + expect(executeMock).toHaveBeenCalledTimes(3); + + const firstQuery = executeMock.mock.calls[0]?.[0]; + const thirdQuery = executeMock.mock.calls[2]?.[0]; + const firstBuilt = toSqlText(firstQuery); + const thirdBuilt = toSqlText(thirdQuery); + + expect(firstBuilt.sql).toContain("::numeric"); + expect(thirdBuilt.sql).not.toContain("::numeric"); + expect(thirdBuilt.sql).toContain("duration_ms"); + expect(thirdBuilt.sql).toContain("status_code"); + }); + it("stop 应等待 in-flight flush 完成", async () => { process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; @@ -238,6 +246,127 @@ describe("message_request 异步批量写入", () => { expect(built.sql).toContain("status_code"); }); + it("遇到暂态错误时,本次 flush 不应因 flushAgainAfterCurrent 忙等重试(交由 timer 退避)", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + const deferred = createDeferred(); + executeMock.mockImplementationOnce(async () => deferred.promise); + + const { + enqueueMessageRequestUpdate, + flushMessageRequestWriteBuffer, + stopMessageRequestWriteBuffer, + } = await import("@/repository/message-write-buffer"); + + enqueueMessageRequestUpdate(1, { durationMs: 123 }); + + const flushPromise = flushMessageRequestWriteBuffer(); + expect(executeMock).toHaveBeenCalledTimes(1); + + // flush in-flight 期间 enqueue:会把 flushAgainAfterCurrent 置为 true + enqueueMessageRequestUpdate(1, { statusCode: 200 }); + + // 触发暂态错误:flush 应结束并把重试交给 timer,而不是在同一次 flush 内立即重试 + deferred.reject(new Error("db down")); + await flushPromise; + + expect(executeMock).toHaveBeenCalledTimes(1); + + await stopMessageRequestWriteBuffer(); + }); + + it("requeue 后更新已存在 id 不应误触发 overflow 丢弃", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + process.env.MESSAGE_REQUEST_ASYNC_BATCH_SIZE = "100"; + process.env.MESSAGE_REQUEST_ASYNC_MAX_PENDING = "100"; + + const deferred = createDeferred(); + executeMock.mockImplementationOnce(async () => deferred.promise); + + const { + enqueueMessageRequestUpdate, + flushMessageRequestWriteBuffer, + stopMessageRequestWriteBuffer, + } = await import("@/repository/message-write-buffer"); + + // pending=100 后会触发一次自动 flush(batchSize=100) + for (let i = 1; i <= 100; i++) { + enqueueMessageRequestUpdate(i, { durationMs: 1 }); + } + + const flushPromise = flushMessageRequestWriteBuffer(); + expect(executeMock).toHaveBeenCalledTimes(1); + + // flush in-flight 期间继续写入(填满 pending) + for (let i = 101; i <= 200; i++) { + enqueueMessageRequestUpdate(i, { durationMs: 1 }); + } + + // 暂态错误:触发 requeue + deferred.reject(new Error("db down")); + await flushPromise; + + // 即使 requeue 后 pending 可能暂时超过 maxPending,这里更新“已存在”的 id 也不应误触发 overflow 并丢弃当前 patch。 + const result = enqueueMessageRequestUpdate(100, { durationMs: 999 }); + expect(result.kind).toBe("enqueued"); + + await stopMessageRequestWriteBuffer(); + + // 防回归:旧实现的 requeue trim 可能会静默丢弃终态 patch(duration/status),导致这些请求永久缺失完成信息。 + // 本用例在 requeue 后确保 id=101 的终态更新仍会被写入 DB。 + expect(executeMock).toHaveBeenCalledTimes(3); + const secondQuery = executeMock.mock.calls[1]?.[0]; + const thirdQuery = executeMock.mock.calls[2]?.[0]; + const secondBuilt = toSqlText(secondQuery); + const thirdBuilt = toSqlText(thirdQuery); + expect(secondBuilt.params).toContain(101); + expect(thirdBuilt.params).toContain(2); + }); + + it("enqueueMessageRequestUpdate 的返回值应反映 patch 是否被接受(sanitize 为空时返回 rejected_invalid)", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + const result = enqueueMessageRequestUpdate(1, { durationMs: Number.NaN }); + + await stopMessageRequestWriteBuffer(); + + expect(result.kind).toBe("rejected_invalid"); + expect(executeMock).not.toHaveBeenCalled(); + }); + + it("costUsd 超出范围时应被丢弃并导致 patch 为空(rejected_invalid)", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + const result = enqueueMessageRequestUpdate(1, { costUsd: "1000000" }); + + await stopMessageRequestWriteBuffer(); + + expect(result.kind).toBe("rejected_invalid"); + expect(executeMock).not.toHaveBeenCalled(); + }); + + it("token 字段应按 bigint(JS safe int)范围 sanitize(不再强制 Int32)", async () => { + const { sanitizeMessageRequestUpdatePatch } = await import("@/repository/message-write-buffer"); + + const sanitized = sanitizeMessageRequestUpdatePatch({ + inputTokens: 2147483648, // INT32_MAX + 1 + outputTokens: Number.MAX_SAFE_INTEGER + 1, // 超出 JS safe int:应 clamp + cacheCreationInputTokens: -1, // 负数:应 clamp 到 0 + }); + + expect(sanitized.inputTokens).toBe(2147483648); + expect(sanitized.outputTokens).toBe(Number.MAX_SAFE_INTEGER); + expect(sanitized.cacheCreationInputTokens).toBe(0); + }); + it("队列溢出时应优先丢弃非终态更新(尽量保留 durationMs)", async () => { process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; process.env.MESSAGE_REQUEST_ASYNC_MAX_PENDING = "100"; @@ -246,7 +375,7 @@ describe("message_request 异步批量写入", () => { "@/repository/message-write-buffer" ); - enqueueMessageRequestUpdate(1001, { statusCode: 200 }); // 非终态(无 durationMs) + enqueueMessageRequestUpdate(1001, { ttfbMs: 200 }); // 非终态(无 durationMs/statusCode) for (let i = 0; i < 100; i++) { enqueueMessageRequestUpdate(2000 + i, { durationMs: i }); } @@ -262,4 +391,74 @@ describe("message_request 异步批量写入", () => { expect(built.params).toContain(2099); expect(built.params).not.toContain(1001); }); + + it("队列溢出且全部为终态更新时应丢弃当前 patch(避免误删已有终态)", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + process.env.MESSAGE_REQUEST_ASYNC_MAX_PENDING = "100"; + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + for (let i = 0; i < 100; i++) { + enqueueMessageRequestUpdate(2000 + i, { durationMs: i }); + } + + const overflowId = 9999; + const result = enqueueMessageRequestUpdate(overflowId, { durationMs: 123 }); + + await stopMessageRequestWriteBuffer(); + + expect(result.kind).toBe("dropped_overflow"); + expect(executeMock).toHaveBeenCalledTimes(1); + + const query = executeMock.mock.calls[0]?.[0]; + const built = toSqlText(query); + + expect(built.params).toContain(2000); + expect(built.params).toContain(2099); + expect(built.params).not.toContain(overflowId); + }); + + it("应接受常见数字字符串输入(避免 patch 被误判为空)", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + const result = enqueueMessageRequestUpdate(1, { + durationMs: "123" as unknown as number, + statusCode: "200" as unknown as number, + }); + + await stopMessageRequestWriteBuffer(); + + expect(result.kind).toBe("enqueued"); + expect(executeMock).toHaveBeenCalledTimes(1); + + const query = executeMock.mock.calls[0]?.[0]; + const built = toSqlText(query); + expect(built.sql).toContain("duration_ms"); + expect(built.sql).toContain("status_code"); + }); + + it("应接受 costUsd number 输入并转换为 numeric", async () => { + process.env.MESSAGE_REQUEST_WRITE_MODE = "async"; + + const { enqueueMessageRequestUpdate, stopMessageRequestWriteBuffer } = await import( + "@/repository/message-write-buffer" + ); + + const result = enqueueMessageRequestUpdate(7, { costUsd: 0.000123 as unknown as string }); + + await stopMessageRequestWriteBuffer(); + + expect(result.kind).toBe("enqueued"); + expect(executeMock).toHaveBeenCalledTimes(1); + + const query = executeMock.mock.calls[0]?.[0]; + const built = toSqlText(query); + expect(built.sql).toContain("::numeric"); + }); });