diff --git a/drizzle/0106_stale_demogoblin.sql b/drizzle/0106_stale_demogoblin.sql new file mode 100644 index 000000000..e10780891 --- /dev/null +++ b/drizzle/0106_stale_demogoblin.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS "keyword_routing_rules" ( + "id" serial PRIMARY KEY NOT NULL, + "keyword" varchar(500) NOT NULL, + "source_model" varchar(128), + "target_model" varchar(128) NOT NULL, + "case_sensitive" boolean DEFAULT true NOT NULL, + "priority" integer DEFAULT 0 NOT NULL, + "description" text, + "is_enabled" boolean DEFAULT true NOT NULL, + "created_at" timestamp with time zone DEFAULT now(), + "updated_at" timestamp with time zone DEFAULT now() +); +--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "enable_keyword_model_routing" boolean DEFAULT false NOT NULL;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "idx_keyword_routing_rules_enabled" ON "keyword_routing_rules" USING btree ("is_enabled","priority");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "idx_keyword_routing_rules_created_at" ON "keyword_routing_rules" USING btree ("created_at"); \ No newline at end of file diff --git a/drizzle/meta/0106_snapshot.json b/drizzle/meta/0106_snapshot.json new file mode 100644 index 000000000..8e1bb4132 --- /dev/null +++ b/drizzle/meta/0106_snapshot.json @@ -0,0 +1,4657 @@ +{ + "id": "5e9ad529-5bc5-40f2-9110-c99c39e25485", + "prevId": "e61c4e85-4edf-4d73-b2e3-5326af94eba0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keyword_routing_rules": { + "name": "keyword_routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "keyword": { + "name": "keyword", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "source_model": { + "name": "source_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "target_model": { + "name": "target_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "case_sensitive": { + "name": "case_sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_keyword_routing_rules_enabled": { + "name": "idx_keyword_routing_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": {} + }, + "idx_keyword_routing_rules_created_at": { + "name": "idx_keyword_routing_rules_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.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hedge_losers": { + "name": "hedge_losers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_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": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'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_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'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'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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 + }, + "enable_keyword_model_routing": { + "name": "enable_keyword_model_routing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "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" + }, + { + "expression": "endpoint", + "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" + }, + { + "expression": "endpoint", + "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" + }, + { + "expression": "endpoint", + "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_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 4d57a874a..83ddaa022 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -743,6 +743,13 @@ "when": 1781156586163, "tag": "0105_chief_rocket_racer", "breakpoints": true + }, + { + "idx": 106, + "version": "7", + "when": 1781273211629, + "tag": "0106_stale_demogoblin", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/auditLogs.json b/messages/en/auditLogs.json index 4bb77e766..62930c43d 100644 --- a/messages/en/auditLogs.json +++ b/messages/en/auditLogs.json @@ -18,6 +18,7 @@ "key": "Keys", "notification": "Notifications", "sensitive_word": "Sensitive words", + "keyword_routing_rule": "Keyword routing", "model_price": "Model prices" }, "columns": { @@ -70,6 +71,11 @@ "update": "Update sensitive word", "delete": "Delete sensitive word" }, + "keyword_routing_rule": { + "create": "Create keyword routing rule", + "update": "Update keyword routing rule", + "delete": "Delete keyword routing rule" + }, "model_price": { "bulk_upload": "Bulk upload model prices", "sync_litellm": "Sync LiteLLM model prices", diff --git a/messages/en/provider-chain.json b/messages/en/provider-chain.json index 3b596c093..354e5ec40 100644 --- a/messages/en/provider-chain.json +++ b/messages/en/provider-chain.json @@ -119,7 +119,10 @@ "afterHealthCheck": "After Health Check", "filteredProviders": "Filtered Providers", "priorityLevels": "Priority Levels", - "candidates": "Candidates" + "candidates": "Candidates", + "keywordRouting": "Keyword Routing", + "keywordRoutingMatchedInSystem": "Matched in system prompt", + "keywordRoutingMatchedInUser": "Matched in user message" }, "technicalTimeline": "Technical Timeline", "timeline": { @@ -243,7 +246,13 @@ "hedgeLoserBilled": "Hedge Race Loser (response reclaimed & billed)", "clientAbort": "Client Disconnected (request aborted)", "hedgeRace": "Hedge Race", - "hedgeThresholdExceeded": "First-byte timeout exceeded, alternative provider launched" + "hedgeThresholdExceeded": "First-byte timeout exceeded, alternative provider launched", + "keywordRouting": "Keyword Routing", + "keywordRoutingFrom": " Requested Model: {model}", + "keywordRoutingTo": " Routed To: {model}", + "keywordRoutingKeyword": " Matched Keyword: {keyword}", + "keywordRoutingMatchedInSystem": " Matched In: system prompt", + "keywordRoutingMatchedInUser": " Matched In: last user message" }, "selectionMethods": { "session_reuse": "Session Reuse", diff --git a/messages/en/settings/index.ts b/messages/en/settings/index.ts index cfa249145..9811c2bac 100644 --- a/messages/en/settings/index.ts +++ b/messages/en/settings/index.ts @@ -4,6 +4,7 @@ import config from "./config.json"; import data from "./data.json"; import errorRules from "./errorRules.json"; import errors from "./errors.json"; +import keywordRouting from "./keywordRouting.json"; import logs from "./logs.json"; import nav from "./nav.json"; import notifications from "./notifications.json"; @@ -107,6 +108,7 @@ export default { providerTypes: providersFormProviderTypes, prices, sensitiveWords, + keywordRouting, requestFilters, logs, data, diff --git a/messages/en/settings/keywordRouting.json b/messages/en/settings/keywordRouting.json new file mode 100644 index 000000000..43bfd9c7d --- /dev/null +++ b/messages/en/settings/keywordRouting.json @@ -0,0 +1,87 @@ +{ + "add": "Add Rule", + "addFailed": "Failed to create routing rule", + "addSuccess": "Routing rule created successfully", + "cacheStats": "Cache stats: {count} rules loaded", + "confirmDelete": "Are you sure you want to delete the rule \"{keyword}\"?", + "delete": "Delete Rule", + "deleteFailed": "Delete failed", + "deleteSuccess": "Routing rule deleted successfully", + "description": "Route requests to a different model when prompts contain specific keywords.", + "dialog": { + "addDescription": "Configure a keyword routing rule. Requests that hit the keyword will be forwarded to the target model.", + "addTitle": "Add Routing Rule", + "caseSensitiveLabel": "Case Sensitive", + "creating": "Creating...", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Optional: Add description...", + "editDescription": "Modify the routing rule configuration. Changes will automatically refresh the cache.", + "editTitle": "Edit Routing Rule", + "keywordLabel": "Keyword *", + "keywordPlaceholder": "Enter keyword...", + "keywordRequired": "Please enter a keyword", + "priorityHint": "Lower values are evaluated first. Default is 0.", + "priorityLabel": "Priority", + "priorityPlaceholder": "0", + "saving": "Saving...", + "sourceModelLabel": "Source Model", + "sourceModelPlaceholder": "Any model", + "targetModelLabel": "Target Model *", + "targetModelPlaceholder": "e.g. claude-opus-4-1", + "targetModelRequired": "Please enter a target model" + }, + "disable": "Routing rule disabled", + "edit": "Edit Rule", + "editFailed": "Failed to update routing rule", + "editSuccess": "Routing rule updated successfully", + "emptyState": "No routing rules yet. Click 'Add Rule' in the top right to start configuration.", + "enable": "Routing rule enabled", + "masterToggle": { + "description": "When enabled, requests that hit a keyword are routed to the configured target model", + "disabled": "Keyword routing disabled", + "enabled": "Keyword routing enabled", + "label": "Enable Keyword Routing", + "saveFailed": "Failed to save setting", + "section": { + "description": "Master switch for keyword-based model routing. The rules below only take effect while this switch is on.", + "title": "Keyword Routing Switch" + } + }, + "refreshCache": "Refresh Cache", + "refreshCacheFailed": "Failed to refresh cache", + "refreshCacheSuccess": "Cache refreshed successfully, loaded {count} rules", + "section": { + "description": "Rules are evaluated in priority order (lower first). The first matched rule rewrites the requested model before provider selection.", + "title": "Routing Rules" + }, + "table": { + "actions": "Actions", + "anyModel": "Any", + "caseSensitive": "Case Sensitive", + "caseSensitiveNo": "Insensitive", + "caseSensitiveYes": "Sensitive", + "createdAt": "Created At", + "keyword": "Keyword", + "priority": "Priority", + "sourceModel": "Source Model", + "status": "Status", + "targetModel": "Target Model" + }, + "title": "Keyword Routing", + "validation": { + "keywordRequired": "Keyword cannot be empty", + "keywordMaxLength": "Keyword must not exceed {max} characters", + "targetModelRequired": "Target model cannot be empty", + "targetModelMaxLength": "Target model must not exceed {max} characters", + "sourceModelMaxLength": "Source model must not exceed {max} characters", + "descriptionMaxLength": "Description must not exceed {max} characters", + "priorityInteger": "Priority must be an integer", + "priorityRange": "Priority must be between -{limit} and {limit}", + "permissionDenied": "Permission denied", + "ruleNotFound": "Keyword routing rule not found", + "createFailed": "Failed to create keyword routing rule", + "updateFailed": "Failed to update keyword routing rule", + "deleteFailed": "Failed to delete keyword routing rule", + "refreshCacheFailed": "Failed to refresh cache" + } +} diff --git a/messages/en/settings/nav.json b/messages/en/settings/nav.json index f3b77757f..31349827c 100644 --- a/messages/en/settings/nav.json +++ b/messages/en/settings/nav.json @@ -13,5 +13,6 @@ "prices": "Pricing", "providers": "Providers", "requestFilters": "Requests", - "sensitiveWords": "Filters" + "sensitiveWords": "Filters", + "keywordRouting": "Keyword Routing" } diff --git a/messages/ja/auditLogs.json b/messages/ja/auditLogs.json index 6e21cdf4e..afaed9fd6 100644 --- a/messages/ja/auditLogs.json +++ b/messages/ja/auditLogs.json @@ -18,6 +18,7 @@ "key": "キー", "notification": "通知", "sensitive_word": "機密ワード", + "keyword_routing_rule": "キーワードルーティング", "model_price": "モデル価格" }, "columns": { @@ -70,6 +71,11 @@ "update": "機密ワード更新", "delete": "機密ワード削除" }, + "keyword_routing_rule": { + "create": "キーワードルーティングルール作成", + "update": "キーワードルーティングルール更新", + "delete": "キーワードルーティングルール削除" + }, "model_price": { "bulk_upload": "モデル価格の一括アップロード", "sync_litellm": "LiteLLM モデル価格の同期", diff --git a/messages/ja/provider-chain.json b/messages/ja/provider-chain.json index 58dcb8e4d..ce2e8a24c 100644 --- a/messages/ja/provider-chain.json +++ b/messages/ja/provider-chain.json @@ -119,7 +119,10 @@ "afterHealthCheck": "ヘルスチェック後", "filteredProviders": "フィルタされたプロバイダー", "priorityLevels": "優先度レベル", - "candidates": "候補プロバイダー" + "candidates": "候補プロバイダー", + "keywordRouting": "キーワードルーティング", + "keywordRoutingMatchedInSystem": "システムプロンプトで一致", + "keywordRoutingMatchedInUser": "ユーザーメッセージで一致" }, "technicalTimeline": "技術タイムライン", "timeline": { @@ -243,7 +246,13 @@ "hedgeLoserBilled": "競争の敗者(応答を回収し課金)", "clientAbort": "クライアント切断(リクエスト中断)", "hedgeRace": "Hedge 競争", - "hedgeThresholdExceeded": "ファーストバイトタイムアウト超過、代替プロバイダーを起動" + "hedgeThresholdExceeded": "ファーストバイトタイムアウト超過、代替プロバイダーを起動", + "keywordRouting": "キーワードルーティング", + "keywordRoutingFrom": " リクエストモデル: {model}", + "keywordRoutingTo": " ルーティング先: {model}", + "keywordRoutingKeyword": " 一致キーワード: {keyword}", + "keywordRoutingMatchedInSystem": " 一致箇所: システムプロンプト", + "keywordRoutingMatchedInUser": " 一致箇所: 最後のユーザーメッセージ" }, "selectionMethods": { "session_reuse": "セッション再利用", diff --git a/messages/ja/settings/index.ts b/messages/ja/settings/index.ts index cfa249145..9811c2bac 100644 --- a/messages/ja/settings/index.ts +++ b/messages/ja/settings/index.ts @@ -4,6 +4,7 @@ import config from "./config.json"; import data from "./data.json"; import errorRules from "./errorRules.json"; import errors from "./errors.json"; +import keywordRouting from "./keywordRouting.json"; import logs from "./logs.json"; import nav from "./nav.json"; import notifications from "./notifications.json"; @@ -107,6 +108,7 @@ export default { providerTypes: providersFormProviderTypes, prices, sensitiveWords, + keywordRouting, requestFilters, logs, data, diff --git a/messages/ja/settings/keywordRouting.json b/messages/ja/settings/keywordRouting.json new file mode 100644 index 000000000..6f301cf58 --- /dev/null +++ b/messages/ja/settings/keywordRouting.json @@ -0,0 +1,87 @@ +{ + "add": "ルールを追加", + "addFailed": "ルーティングルールの作成に失敗しました", + "addSuccess": "ルーティングルールが正常に作成されました", + "cacheStats": "キャッシュ統計:{count}件のルールを読み込み済み", + "confirmDelete": "ルール「{keyword}」を削除してもよろしいですか?", + "delete": "ルールを削除", + "deleteFailed": "削除に失敗しました", + "deleteSuccess": "ルーティングルールが正常に削除されました", + "description": "リクエスト内容に特定のキーワードが含まれている場合、リクエストを別のモデルにルーティングします。", + "dialog": { + "addDescription": "キーワードルーティングルールを設定します。キーワードにマッチしたリクエストは転送先モデルに転送されます。", + "addTitle": "ルーティングルールを追加", + "caseSensitiveLabel": "大文字と小文字を区別", + "creating": "作成中...", + "descriptionLabel": "説明", + "descriptionPlaceholder": "オプション:説明を追加...", + "editDescription": "ルーティングルールの設定を変更します。変更後、自動的にキャッシュがリフレッシュされます。", + "editTitle": "ルーティングルールを編集", + "keywordLabel": "キーワード *", + "keywordPlaceholder": "キーワードを入力...", + "keywordRequired": "キーワードを入力してください", + "priorityHint": "値が小さいほど先に評価されます。デフォルトは0です。", + "priorityLabel": "優先度", + "priorityPlaceholder": "0", + "saving": "保存しています...", + "sourceModelLabel": "ソースモデル", + "sourceModelPlaceholder": "任意のモデル", + "targetModelLabel": "転送先モデル *", + "targetModelPlaceholder": "例:claude-opus-4-1", + "targetModelRequired": "転送先モデルを入力してください" + }, + "disable": "ルーティングルールが無効になりました", + "edit": "ルールを編集", + "editFailed": "ルーティングルールの更新に失敗しました", + "editSuccess": "ルーティングルールが正常に更新されました", + "emptyState": "ルーティングルールがありません。右上の「ルールを追加」をクリックして設定を開始してください。", + "enable": "ルーティングルールが有効になりました", + "masterToggle": { + "description": "有効にすると、キーワードにマッチしたリクエストは設定された転送先モデルにルーティングされます", + "disabled": "キーワードルーティングが無効になりました", + "enabled": "キーワードルーティングが有効になりました", + "label": "キーワードルーティングを有効にする", + "saveFailed": "設定の保存に失敗しました", + "section": { + "description": "キーワードベースのモデルルーティングのマスタースイッチです。オフの場合、以下のルールは適用されません。", + "title": "キーワードルーティングスイッチ" + } + }, + "refreshCache": "キャッシュを更新", + "refreshCacheFailed": "キャッシュのリフレッシュに失敗しました", + "refreshCacheSuccess": "キャッシュのリフレッシュに成功しました。{count}件のルールを読み込みました", + "section": { + "description": "ルールは優先度の小さい順に評価されます。最初にマッチしたルールが、供給元の選択前にリクエストモデルを書き換えます。", + "title": "ルーティングルール一覧" + }, + "table": { + "actions": "アクション", + "anyModel": "任意", + "caseSensitive": "大文字小文字", + "caseSensitiveNo": "区別しない", + "caseSensitiveYes": "区別する", + "createdAt": "作成日時", + "keyword": "キーワード", + "priority": "優先度", + "sourceModel": "ソースモデル", + "status": "ステータス", + "targetModel": "転送先モデル" + }, + "title": "キーワードルーティング", + "validation": { + "keywordRequired": "キーワードは空にできません", + "keywordMaxLength": "キーワードは{max}文字以内である必要があります", + "targetModelRequired": "転送先モデルは空にできません", + "targetModelMaxLength": "転送先モデルは{max}文字以内である必要があります", + "sourceModelMaxLength": "ソースモデルは{max}文字以内である必要があります", + "descriptionMaxLength": "説明は{max}文字以内である必要があります", + "priorityInteger": "優先度は整数である必要があります", + "priorityRange": "優先度は-{limit}から{limit}の間である必要があります", + "permissionDenied": "権限が不足しています", + "ruleNotFound": "キーワードルーティングルールが存在しません", + "createFailed": "キーワードルーティングルールの作成に失敗しました", + "updateFailed": "キーワードルーティングルールの更新に失敗しました", + "deleteFailed": "キーワードルーティングルールの削除に失敗しました", + "refreshCacheFailed": "キャッシュのリフレッシュに失敗しました" + } +} diff --git a/messages/ja/settings/nav.json b/messages/ja/settings/nav.json index 2506d5997..7b7fa9e5a 100644 --- a/messages/ja/settings/nav.json +++ b/messages/ja/settings/nav.json @@ -13,5 +13,6 @@ "prices": "価格表", "providers": "供給元", "requestFilters": "リクエスト", - "sensitiveWords": "フィルター" + "sensitiveWords": "フィルター", + "keywordRouting": "キーワードルーティング" } diff --git a/messages/ru/auditLogs.json b/messages/ru/auditLogs.json index e05922d7c..1d16d072d 100644 --- a/messages/ru/auditLogs.json +++ b/messages/ru/auditLogs.json @@ -18,6 +18,7 @@ "key": "Ключи", "notification": "Уведомления", "sensitive_word": "Запрещённые слова", + "keyword_routing_rule": "Маршрутизация по ключевым словам", "model_price": "Цены моделей" }, "columns": { @@ -70,6 +71,11 @@ "update": "Обновление запрещённого слова", "delete": "Удаление запрещённого слова" }, + "keyword_routing_rule": { + "create": "Создание правила маршрутизации по ключевым словам", + "update": "Обновление правила маршрутизации по ключевым словам", + "delete": "Удаление правила маршрутизации по ключевым словам" + }, "model_price": { "bulk_upload": "Массовая загрузка цен моделей", "sync_litellm": "Синхронизация цен моделей LiteLLM", diff --git a/messages/ru/provider-chain.json b/messages/ru/provider-chain.json index bbc34ad9d..61d7cadaf 100644 --- a/messages/ru/provider-chain.json +++ b/messages/ru/provider-chain.json @@ -119,7 +119,10 @@ "afterHealthCheck": "После проверки состояния", "filteredProviders": "Отфильтрованные провайдеры", "priorityLevels": "Уровни приоритета", - "candidates": "Кандидаты провайдеров" + "candidates": "Кандидаты провайдеров", + "keywordRouting": "Маршрутизация по ключевым словам", + "keywordRoutingMatchedInSystem": "Совпадение в системном промпте", + "keywordRoutingMatchedInUser": "Совпадение в сообщении пользователя" }, "technicalTimeline": "Техническая временная шкала", "timeline": { @@ -243,7 +246,13 @@ "hedgeLoserBilled": "Проигравший в гонке (ответ получен и тарифицирован)", "clientAbort": "Клиент отключился (запрос прерван)", "hedgeRace": "Hedge-гонка", - "hedgeThresholdExceeded": "Тайм-аут первого байта превышен, запущен альтернативный провайдер" + "hedgeThresholdExceeded": "Тайм-аут первого байта превышен, запущен альтернативный провайдер", + "keywordRouting": "Маршрутизация по ключевым словам", + "keywordRoutingFrom": " Запрошенная модель: {model}", + "keywordRoutingTo": " Перенаправлено на: {model}", + "keywordRoutingKeyword": " Ключевое слово: {keyword}", + "keywordRoutingMatchedInSystem": " Совпадение: системный промпт", + "keywordRoutingMatchedInUser": " Совпадение: последнее сообщение пользователя" }, "selectionMethods": { "session_reuse": "Повторное использование сессии", diff --git a/messages/ru/settings/index.ts b/messages/ru/settings/index.ts index cfa249145..9811c2bac 100644 --- a/messages/ru/settings/index.ts +++ b/messages/ru/settings/index.ts @@ -4,6 +4,7 @@ import config from "./config.json"; import data from "./data.json"; import errorRules from "./errorRules.json"; import errors from "./errors.json"; +import keywordRouting from "./keywordRouting.json"; import logs from "./logs.json"; import nav from "./nav.json"; import notifications from "./notifications.json"; @@ -107,6 +108,7 @@ export default { providerTypes: providersFormProviderTypes, prices, sensitiveWords, + keywordRouting, requestFilters, logs, data, diff --git a/messages/ru/settings/keywordRouting.json b/messages/ru/settings/keywordRouting.json new file mode 100644 index 000000000..bff02d754 --- /dev/null +++ b/messages/ru/settings/keywordRouting.json @@ -0,0 +1,87 @@ +{ + "add": "Добавить правило", + "addFailed": "Ошибка создания правила маршрутизации", + "addSuccess": "Правило маршрутизации создано успешно", + "cacheStats": "Статистика кэша: загружено правил: {count}", + "confirmDelete": "Вы уверены, что хотите удалить правило \"{keyword}\"?", + "delete": "Удалить правило", + "deleteFailed": "Ошибка удаления", + "deleteSuccess": "Правило маршрутизации удалено успешно", + "description": "Маршрутизация запросов на другую модель, если содержимое запроса содержит заданные ключевые слова.", + "dialog": { + "addDescription": "Настройте правило маршрутизации по ключевым словам. Запросы с совпадением будут перенаправлены на целевую модель.", + "addTitle": "Добавить правило маршрутизации", + "caseSensitiveLabel": "Учитывать регистр", + "creating": "Создание...", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Необязательно: Добавить описание...", + "editDescription": "Изменить конфигурацию правила маршрутизации. Изменения автоматически обновят кэш.", + "editTitle": "Редактировать правило маршрутизации", + "keywordLabel": "Ключевое слово *", + "keywordPlaceholder": "Введите ключевое слово...", + "keywordRequired": "Пожалуйста, введите ключевое слово", + "priorityHint": "Меньшие значения проверяются первыми. По умолчанию 0.", + "priorityLabel": "Приоритет", + "priorityPlaceholder": "0", + "saving": "Сохранение...", + "sourceModelLabel": "Исходная модель", + "sourceModelPlaceholder": "Любая модель", + "targetModelLabel": "Целевая модель *", + "targetModelPlaceholder": "напр. claude-opus-4-1", + "targetModelRequired": "Пожалуйста, введите целевую модель" + }, + "disable": "Правило маршрутизации отключено", + "edit": "Редактировать правило", + "editFailed": "Ошибка обновления правила маршрутизации", + "editSuccess": "Правило маршрутизации обновлено успешно", + "emptyState": "Пока нет правил маршрутизации. Нажмите 'Добавить правило' в правом верхнем углу для начала настройки.", + "enable": "Правило маршрутизации включено", + "masterToggle": { + "description": "Если включено, запросы с совпадением ключевого слова перенаправляются на настроенную целевую модель", + "disabled": "Маршрутизация по ключевым словам отключена", + "enabled": "Маршрутизация по ключевым словам включена", + "label": "Включить маршрутизацию по ключевым словам", + "saveFailed": "Не удалось сохранить настройку", + "section": { + "description": "Главный переключатель маршрутизации моделей по ключевым словам. Правила ниже действуют только при включенном переключателе.", + "title": "Переключатель маршрутизации" + } + }, + "refreshCache": "Обновить кэш", + "refreshCacheFailed": "Не удалось обновить кэш", + "refreshCacheSuccess": "Кэш успешно обновлен, загружено правил: {count}", + "section": { + "description": "Правила проверяются в порядке приоритета (меньшие значения первыми). Первое совпавшее правило перезаписывает запрошенную модель до выбора поставщика.", + "title": "Список правил маршрутизации" + }, + "table": { + "actions": "Действия", + "anyModel": "Любая", + "caseSensitive": "Регистр", + "caseSensitiveNo": "Не учитывается", + "caseSensitiveYes": "Учитывается", + "createdAt": "Создано", + "keyword": "Ключевое слово", + "priority": "Приоритет", + "sourceModel": "Исходная модель", + "status": "Статус", + "targetModel": "Целевая модель" + }, + "title": "Маршрутизация по ключевым словам", + "validation": { + "keywordRequired": "Ключевое слово не может быть пустым", + "keywordMaxLength": "Ключевое слово не может превышать {max} символов", + "targetModelRequired": "Целевая модель не может быть пустой", + "targetModelMaxLength": "Целевая модель не может превышать {max} символов", + "sourceModelMaxLength": "Исходная модель не может превышать {max} символов", + "descriptionMaxLength": "Описание не может превышать {max} символов", + "priorityInteger": "Приоритет должен быть целым числом", + "priorityRange": "Приоритет должен быть между -{limit} и {limit}", + "permissionDenied": "Недостаточно прав", + "ruleNotFound": "Правило маршрутизации по ключевому слову не найдено", + "createFailed": "Не удалось создать правило маршрутизации по ключевому слову", + "updateFailed": "Не удалось обновить правило маршрутизации по ключевому слову", + "deleteFailed": "Не удалось удалить правило маршрутизации по ключевому слову", + "refreshCacheFailed": "Не удалось обновить кэш" + } +} diff --git a/messages/ru/settings/nav.json b/messages/ru/settings/nav.json index d90e990e9..56696e80d 100644 --- a/messages/ru/settings/nav.json +++ b/messages/ru/settings/nav.json @@ -13,5 +13,6 @@ "prices": "Цены", "providers": "Поставщики", "requestFilters": "Запросы", - "sensitiveWords": "Фильтры" + "sensitiveWords": "Фильтры", + "keywordRouting": "Маршрутизация" } diff --git a/messages/zh-CN/auditLogs.json b/messages/zh-CN/auditLogs.json index 8fad548e4..bb75f579b 100644 --- a/messages/zh-CN/auditLogs.json +++ b/messages/zh-CN/auditLogs.json @@ -18,6 +18,7 @@ "key": "密钥", "notification": "通知", "sensitive_word": "敏感词", + "keyword_routing_rule": "关键词路由", "model_price": "模型价格" }, "columns": { @@ -70,6 +71,11 @@ "update": "更新敏感词", "delete": "删除敏感词" }, + "keyword_routing_rule": { + "create": "创建关键词路由规则", + "update": "更新关键词路由规则", + "delete": "删除关键词路由规则" + }, "model_price": { "bulk_upload": "批量上传模型价格", "sync_litellm": "同步 LiteLLM 模型价格", diff --git a/messages/zh-CN/provider-chain.json b/messages/zh-CN/provider-chain.json index d136eed35..840e421a0 100644 --- a/messages/zh-CN/provider-chain.json +++ b/messages/zh-CN/provider-chain.json @@ -119,7 +119,10 @@ "afterHealthCheck": "健康检查后", "filteredProviders": "被过滤供应商", "priorityLevels": "优先级层级", - "candidates": "候选供应商" + "candidates": "候选供应商", + "keywordRouting": "关键词路由", + "keywordRoutingMatchedInSystem": "命中系统提示词", + "keywordRoutingMatchedInUser": "命中用户消息" }, "technicalTimeline": "技术时间线", "timeline": { @@ -243,7 +246,13 @@ "hedgeLoserBilled": "竞速落败(已拿回响应并计费)", "clientAbort": "客户端已断开连接(请求中断)", "hedgeRace": "Hedge 竞速", - "hedgeThresholdExceeded": "首字节超时,已启动备选供应商" + "hedgeThresholdExceeded": "首字节超时,已启动备选供应商", + "keywordRouting": "关键词路由", + "keywordRoutingFrom": " 请求模型: {model}", + "keywordRoutingTo": " 路由至: {model}", + "keywordRoutingKeyword": " 命中关键词: {keyword}", + "keywordRoutingMatchedInSystem": " 命中位置: 系统提示词", + "keywordRoutingMatchedInUser": " 命中位置: 最后一条用户消息" }, "selectionMethods": { "session_reuse": "会话复用", diff --git a/messages/zh-CN/settings/index.ts b/messages/zh-CN/settings/index.ts index cfa249145..9811c2bac 100644 --- a/messages/zh-CN/settings/index.ts +++ b/messages/zh-CN/settings/index.ts @@ -4,6 +4,7 @@ import config from "./config.json"; import data from "./data.json"; import errorRules from "./errorRules.json"; import errors from "./errors.json"; +import keywordRouting from "./keywordRouting.json"; import logs from "./logs.json"; import nav from "./nav.json"; import notifications from "./notifications.json"; @@ -107,6 +108,7 @@ export default { providerTypes: providersFormProviderTypes, prices, sensitiveWords, + keywordRouting, requestFilters, logs, data, diff --git a/messages/zh-CN/settings/keywordRouting.json b/messages/zh-CN/settings/keywordRouting.json new file mode 100644 index 000000000..9f1daeb42 --- /dev/null +++ b/messages/zh-CN/settings/keywordRouting.json @@ -0,0 +1,87 @@ +{ + "add": "添加规则", + "addFailed": "创建路由规则失败", + "addSuccess": "路由规则创建成功", + "cacheStats": "缓存统计: 已加载 {count} 条规则", + "confirmDelete": "确定要删除规则\"{keyword}\"吗?", + "delete": "删除规则", + "deleteFailed": "删除失败", + "deleteSuccess": "路由规则删除成功", + "description": "当请求内容包含指定关键词时,将请求路由到其他模型。", + "dialog": { + "addDescription": "配置关键词路由规则,命中关键词的请求将被转发到目标模型。", + "addTitle": "添加路由规则", + "caseSensitiveLabel": "区分大小写", + "creating": "创建中...", + "descriptionLabel": "说明", + "descriptionPlaceholder": "可选:添加说明...", + "editDescription": "修改路由规则配置,更改后将自动刷新缓存。", + "editTitle": "编辑路由规则", + "keywordLabel": "关键词 *", + "keywordPlaceholder": "输入关键词...", + "keywordRequired": "请输入关键词", + "priorityHint": "数值越小越先匹配,默认为 0。", + "priorityLabel": "优先级", + "priorityPlaceholder": "0", + "saving": "保存中...", + "sourceModelLabel": "来源模型", + "sourceModelPlaceholder": "任意模型", + "targetModelLabel": "目标模型 *", + "targetModelPlaceholder": "例如 claude-opus-4-1", + "targetModelRequired": "请输入目标模型" + }, + "disable": "路由规则已禁用", + "edit": "编辑规则", + "editFailed": "更新路由规则失败", + "editSuccess": "路由规则更新成功", + "emptyState": "暂无路由规则,点击右上角\"添加规则\"开始配置。", + "enable": "路由规则已启用", + "masterToggle": { + "description": "启用后,命中关键词的请求将被路由到配置的目标模型", + "disabled": "关键词路由已禁用", + "enabled": "关键词路由已启用", + "label": "启用关键词路由", + "saveFailed": "保存设置失败", + "section": { + "description": "关键词模型路由的总开关,关闭后下方规则不会生效。", + "title": "关键词路由开关" + } + }, + "refreshCache": "刷新缓存", + "refreshCacheFailed": "刷新缓存失败", + "refreshCacheSuccess": "缓存刷新成功,已加载 {count} 条规则", + "section": { + "description": "规则按优先级从小到大依次匹配,命中的第一条规则会在供应商选择前改写请求模型。", + "title": "路由规则列表" + }, + "table": { + "actions": "操作", + "anyModel": "任意", + "caseSensitive": "大小写", + "caseSensitiveNo": "不区分", + "caseSensitiveYes": "区分", + "createdAt": "创建时间", + "keyword": "关键词", + "priority": "优先级", + "sourceModel": "来源模型", + "status": "状态", + "targetModel": "目标模型" + }, + "title": "关键词路由", + "validation": { + "keywordRequired": "关键词不能为空", + "keywordMaxLength": "关键词长度不能超过 {max} 个字符", + "targetModelRequired": "目标模型不能为空", + "targetModelMaxLength": "目标模型长度不能超过 {max} 个字符", + "sourceModelMaxLength": "来源模型长度不能超过 {max} 个字符", + "descriptionMaxLength": "描述长度不能超过 {max} 个字符", + "priorityInteger": "优先级必须为整数", + "priorityRange": "优先级必须在 -{limit} 到 {limit} 之间", + "permissionDenied": "权限不足", + "ruleNotFound": "关键词路由规则不存在", + "createFailed": "创建关键词路由规则失败", + "updateFailed": "更新关键词路由规则失败", + "deleteFailed": "删除关键词路由规则失败", + "refreshCacheFailed": "刷新缓存失败" + } +} diff --git a/messages/zh-CN/settings/nav.json b/messages/zh-CN/settings/nav.json index 74dd37815..f9e75a314 100644 --- a/messages/zh-CN/settings/nav.json +++ b/messages/zh-CN/settings/nav.json @@ -13,5 +13,6 @@ "apiDocs": "API 文档", "errorRules": "错误规则", "feedback": "反馈问题", - "docs": "使用文档" + "docs": "使用文档", + "keywordRouting": "关键词路由" } diff --git a/messages/zh-TW/auditLogs.json b/messages/zh-TW/auditLogs.json index c63323140..712618655 100644 --- a/messages/zh-TW/auditLogs.json +++ b/messages/zh-TW/auditLogs.json @@ -18,6 +18,7 @@ "key": "金鑰", "notification": "通知", "sensitive_word": "敏感詞", + "keyword_routing_rule": "關鍵詞路由", "model_price": "模型價格" }, "columns": { @@ -70,6 +71,11 @@ "update": "更新敏感詞", "delete": "刪除敏感詞" }, + "keyword_routing_rule": { + "create": "建立關鍵詞路由規則", + "update": "更新關鍵詞路由規則", + "delete": "刪除關鍵詞路由規則" + }, "model_price": { "bulk_upload": "批次上傳模型價格", "sync_litellm": "同步 LiteLLM 模型價格", diff --git a/messages/zh-TW/provider-chain.json b/messages/zh-TW/provider-chain.json index de200a133..93169d5a3 100644 --- a/messages/zh-TW/provider-chain.json +++ b/messages/zh-TW/provider-chain.json @@ -119,7 +119,10 @@ "afterHealthCheck": "健康檢查後", "filteredProviders": "被過濾供應商", "priorityLevels": "優先級層級", - "candidates": "候選供應商" + "candidates": "候選供應商", + "keywordRouting": "關鍵詞路由", + "keywordRoutingMatchedInSystem": "命中系統提示詞", + "keywordRoutingMatchedInUser": "命中用戶訊息" }, "technicalTimeline": "技術時間線", "timeline": { @@ -243,7 +246,13 @@ "hedgeLoserBilled": "競速落敗(已取回回應並計費)", "clientAbort": "客戶端已斷開連接(請求中斷)", "hedgeRace": "Hedge 競速", - "hedgeThresholdExceeded": "首位元組逾時,已啟動備選供應商" + "hedgeThresholdExceeded": "首位元組逾時,已啟動備選供應商", + "keywordRouting": "關鍵詞路由", + "keywordRoutingFrom": " 請求模型: {model}", + "keywordRoutingTo": " 路由至: {model}", + "keywordRoutingKeyword": " 命中關鍵詞: {keyword}", + "keywordRoutingMatchedInSystem": " 命中位置: 系統提示詞", + "keywordRoutingMatchedInUser": " 命中位置: 最後一條用戶訊息" }, "selectionMethods": { "session_reuse": "會話複用", diff --git a/messages/zh-TW/settings/index.ts b/messages/zh-TW/settings/index.ts index cfa249145..9811c2bac 100644 --- a/messages/zh-TW/settings/index.ts +++ b/messages/zh-TW/settings/index.ts @@ -4,6 +4,7 @@ import config from "./config.json"; import data from "./data.json"; import errorRules from "./errorRules.json"; import errors from "./errors.json"; +import keywordRouting from "./keywordRouting.json"; import logs from "./logs.json"; import nav from "./nav.json"; import notifications from "./notifications.json"; @@ -107,6 +108,7 @@ export default { providerTypes: providersFormProviderTypes, prices, sensitiveWords, + keywordRouting, requestFilters, logs, data, diff --git a/messages/zh-TW/settings/keywordRouting.json b/messages/zh-TW/settings/keywordRouting.json new file mode 100644 index 000000000..b04a07740 --- /dev/null +++ b/messages/zh-TW/settings/keywordRouting.json @@ -0,0 +1,87 @@ +{ + "add": "新增規則", + "addFailed": "建立路由規則失敗", + "addSuccess": "路由規則建立成功", + "cacheStats": "快取統計:已載入 {count} 條規則", + "confirmDelete": "確定要刪除規則「{keyword}」嗎?", + "delete": "刪除規則", + "deleteFailed": "刪除失敗", + "deleteSuccess": "路由規則刪除成功", + "description": "當請求內容包含指定關鍵詞時,將請求路由到其他模型。", + "dialog": { + "addDescription": "配置關鍵詞路由規則,命中關鍵詞的請求將被轉發到目標模型。", + "addTitle": "新增路由規則", + "caseSensitiveLabel": "區分大小寫", + "creating": "建立中...", + "descriptionLabel": "說明", + "descriptionPlaceholder": "選填:新增說明...", + "editDescription": "修改路由規則配置,更改後將自動刷新快取。", + "editTitle": "編輯路由規則", + "keywordLabel": "關鍵詞 *", + "keywordPlaceholder": "輸入關鍵詞...", + "keywordRequired": "請輸入關鍵詞", + "priorityHint": "數值越小越先比對,預設為 0。", + "priorityLabel": "優先級", + "priorityPlaceholder": "0", + "saving": "儲存中...", + "sourceModelLabel": "來源模型", + "sourceModelPlaceholder": "任意模型", + "targetModelLabel": "目標模型 *", + "targetModelPlaceholder": "例如 claude-opus-4-1", + "targetModelRequired": "請輸入目標模型" + }, + "disable": "路由規則已停用", + "edit": "編輯規則", + "editFailed": "更新路由規則失敗", + "editSuccess": "路由規則更新成功", + "emptyState": "暫無路由規則,點選右上角「新增規則」開始配置。", + "enable": "路由規則已啟用", + "masterToggle": { + "description": "啟用後,命中關鍵詞的請求將被路由到配置的目標模型", + "disabled": "關鍵詞路由已停用", + "enabled": "關鍵詞路由已啟用", + "label": "啟用關鍵詞路由", + "saveFailed": "儲存設定失敗", + "section": { + "description": "關鍵詞模型路由的總開關,關閉後下方規則不會生效。", + "title": "關鍵詞路由開關" + } + }, + "refreshCache": "重新整理快取", + "refreshCacheFailed": "刷新快取失敗", + "refreshCacheSuccess": "快取刷新成功,已載入 {count} 條規則", + "section": { + "description": "規則按優先級由小到大依次比對,命中的第一條規則會在供應商選擇前改寫請求模型。", + "title": "路由規則列表" + }, + "table": { + "actions": "動作", + "anyModel": "任意", + "caseSensitive": "大小寫", + "caseSensitiveNo": "不區分", + "caseSensitiveYes": "區分", + "createdAt": "建立時間", + "keyword": "關鍵詞", + "priority": "優先級", + "sourceModel": "來源模型", + "status": "狀態", + "targetModel": "目標模型" + }, + "title": "關鍵詞路由", + "validation": { + "keywordRequired": "關鍵詞不能為空", + "keywordMaxLength": "關鍵詞長度不能超過 {max} 個字元", + "targetModelRequired": "目標模型不能為空", + "targetModelMaxLength": "目標模型長度不能超過 {max} 個字元", + "sourceModelMaxLength": "來源模型長度不能超過 {max} 個字元", + "descriptionMaxLength": "說明長度不能超過 {max} 個字元", + "priorityInteger": "優先級必須為整數", + "priorityRange": "優先級必須在 -{limit} 到 {limit} 之間", + "permissionDenied": "權限不足", + "ruleNotFound": "關鍵詞路由規則不存在", + "createFailed": "建立關鍵詞路由規則失敗", + "updateFailed": "更新關鍵詞路由規則失敗", + "deleteFailed": "刪除關鍵詞路由規則失敗", + "refreshCacheFailed": "刷新快取失敗" + } +} diff --git a/messages/zh-TW/settings/nav.json b/messages/zh-TW/settings/nav.json index 9bf38b42a..da3f6a546 100644 --- a/messages/zh-TW/settings/nav.json +++ b/messages/zh-TW/settings/nav.json @@ -13,5 +13,6 @@ "prices": "價格表", "providers": "供應商", "requestFilters": "請求過濾", - "sensitiveWords": "敏感詞" + "sensitiveWords": "敏感詞", + "keywordRouting": "關鍵詞路由" } diff --git a/src/actions/audit-logs.ts b/src/actions/audit-logs.ts index 36ac6fd3c..1ff57498a 100644 --- a/src/actions/audit-logs.ts +++ b/src/actions/audit-logs.ts @@ -21,6 +21,7 @@ const AUDIT_CATEGORY_VALUES = [ "key", "notification", "sensitive_word", + "keyword_routing_rule", "model_price", ] as const satisfies readonly AuditCategory[]; diff --git a/src/actions/keyword-routing.ts b/src/actions/keyword-routing.ts new file mode 100644 index 000000000..94904a3a1 --- /dev/null +++ b/src/actions/keyword-routing.ts @@ -0,0 +1,403 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { getTranslations } from "next-intl/server"; +import { emitActionAudit } from "@/lib/audit/emit"; +import { getSession } from "@/lib/auth"; +import { keywordRoutingEngine } from "@/lib/keyword-routing/engine"; +import { logger } from "@/lib/logger"; +import { + DESCRIPTION_MAX_LENGTH, + KEYWORD_MAX_LENGTH, + MODEL_MAX_LENGTH, + PRIORITY_ABS_LIMIT, +} from "@/lib/validation/keyword-routing-constants"; +import * as repo from "@/repository/keyword-routing-rules"; +import type { ActionResult } from "./types"; + +type TranslationFunction = Awaited< + ReturnType> +>; + +/** + * 校验创建/更新规则的字段,返回错误信息(合法时返回 null) + * + * 使用从 @/lib/validation/keyword-routing-constants 导入的共享常量, + * 确保与 API 层 Zod schema 验证保持一致。 + */ +async function validateRuleFields( + fields: { + keyword?: string; + sourceModel?: string | null; + targetModel?: string; + description?: string | null; + priority?: number; + }, + t: TranslationFunction +): Promise { + if (fields.keyword !== undefined) { + const keyword = fields.keyword?.trim() ?? ""; + if (keyword.length === 0) { + return t("keywordRequired"); + } + if (keyword.length > KEYWORD_MAX_LENGTH) { + return t("keywordMaxLength", { max: KEYWORD_MAX_LENGTH }); + } + } + + if (fields.targetModel !== undefined) { + const targetModel = fields.targetModel?.trim() ?? ""; + if (targetModel.length === 0) { + return t("targetModelRequired"); + } + if (targetModel.length > MODEL_MAX_LENGTH) { + return t("targetModelMaxLength", { max: MODEL_MAX_LENGTH }); + } + } + + if (fields.sourceModel != null && fields.sourceModel.trim().length > MODEL_MAX_LENGTH) { + return t("sourceModelMaxLength", { max: MODEL_MAX_LENGTH }); + } + + if (fields.description != null && fields.description.length > DESCRIPTION_MAX_LENGTH) { + return t("descriptionMaxLength", { max: DESCRIPTION_MAX_LENGTH }); + } + + if (fields.priority !== undefined) { + if (!Number.isInteger(fields.priority)) { + return t("priorityInteger"); + } + if (fields.priority < -PRIORITY_ABS_LIMIT || fields.priority > PRIORITY_ABS_LIMIT) { + return t("priorityRange", { limit: PRIORITY_ABS_LIMIT }); + } + } + + return null; +} + +/** + * 获取所有关键词路由规则列表 + */ +export async function listKeywordRoutingRules(): Promise { + try { + const session = await getSession(); + if (session?.user.role !== "admin") { + logger.warn("[KeywordRoutingAction] Unauthorized access attempt"); + return []; + } + + return await repo.getAllKeywordRoutingRules(); + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to list keyword routing rules:", error); + return []; + } +} + +/** + * 创建关键词路由规则 + */ +export async function createKeywordRoutingRuleAction(data: { + keyword: string; + sourceModel?: string | null; + targetModel: string; + caseSensitive?: boolean; + priority?: number; + description?: string | null; +}): Promise> { + try { + const t = await getTranslations("settings.keywordRouting.validation"); + const session = await getSession(); + if (session?.user.role !== "admin") { + return { + ok: false, + error: t("permissionDenied"), + errorCode: "PERMISSION_DENIED", + }; + } + + // 验证必填字段与长度限制 + const validationError = await validateRuleFields( + { + keyword: data.keyword ?? "", + targetModel: data.targetModel ?? "", + sourceModel: data.sourceModel, + description: data.description, + priority: data.priority, + }, + t + ); + if (validationError) { + return { + ok: false, + error: validationError, + errorCode: "VALIDATION_ERROR", + }; + } + + const result = await repo.createKeywordRoutingRule(data); + + revalidatePath("/settings/keyword-routing"); + + logger.info("[KeywordRoutingAction] Created keyword routing rule", { + keyword: data.keyword, + targetModel: data.targetModel, + userId: session.user.id, + }); + + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.create", + targetType: "keyword_routing_rule", + targetId: String(result.id), + targetName: result.keyword, + after: { + id: result.id, + keyword: result.keyword, + sourceModel: result.sourceModel, + targetModel: result.targetModel, + caseSensitive: result.caseSensitive, + priority: result.priority, + description: result.description, + isEnabled: result.isEnabled, + }, + success: true, + }); + + return { + ok: true, + data: result, + }; + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to create keyword routing rule:", error); + const t = await getTranslations("settings.keywordRouting.validation"); + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.create", + targetType: "keyword_routing_rule", + targetName: data.keyword ?? null, + success: false, + errorMessage: "CREATE_FAILED", + }); + return { + ok: false, + error: t("createFailed"), + errorCode: "OPERATION_FAILED", + }; + } +} + +/** + * 更新关键词路由规则 + */ +export async function updateKeywordRoutingRuleAction( + id: number, + updates: Partial<{ + keyword: string; + sourceModel: string | null; + targetModel: string; + caseSensitive: boolean; + priority: number; + description: string | null; + isEnabled: boolean; + }> +): Promise> { + try { + const t = await getTranslations("settings.keywordRouting.validation"); + const session = await getSession(); + if (session?.user.role !== "admin") { + return { + ok: false, + error: t("permissionDenied"), + errorCode: "PERMISSION_DENIED", + }; + } + + // 仅校验本次提供的字段 + const validationError = await validateRuleFields(updates, t); + if (validationError) { + return { + ok: false, + error: validationError, + errorCode: "VALIDATION_ERROR", + }; + } + + const result = await repo.updateKeywordRoutingRule(id, updates); + + if (!result) { + return { + ok: false, + error: t("ruleNotFound"), + errorCode: "NOT_FOUND", + }; + } + + revalidatePath("/settings/keyword-routing"); + + logger.info("[KeywordRoutingAction] Updated keyword routing rule", { + id, + updates, + userId: session.user.id, + }); + + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.update", + targetType: "keyword_routing_rule", + targetId: String(id), + targetName: result.keyword, + after: { + id: result.id, + keyword: result.keyword, + sourceModel: result.sourceModel, + targetModel: result.targetModel, + caseSensitive: result.caseSensitive, + priority: result.priority, + description: result.description, + isEnabled: result.isEnabled, + }, + success: true, + }); + + return { + ok: true, + data: result, + }; + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to update keyword routing rule:", error); + const t = await getTranslations("settings.keywordRouting.validation"); + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.update", + targetType: "keyword_routing_rule", + targetId: String(id), + success: false, + errorMessage: "UPDATE_FAILED", + }); + return { + ok: false, + error: t("updateFailed"), + errorCode: "OPERATION_FAILED", + }; + } +} + +/** + * 删除关键词路由规则 + */ +export async function deleteKeywordRoutingRuleAction(id: number): Promise { + try { + const t = await getTranslations("settings.keywordRouting.validation"); + const session = await getSession(); + if (session?.user.role !== "admin") { + return { + ok: false, + error: t("permissionDenied"), + errorCode: "PERMISSION_DENIED", + }; + } + + const deleted = await repo.deleteKeywordRoutingRule(id); + + if (!deleted) { + return { + ok: false, + error: t("ruleNotFound"), + errorCode: "NOT_FOUND", + }; + } + + revalidatePath("/settings/keyword-routing"); + + logger.info("[KeywordRoutingAction] Deleted keyword routing rule", { + id, + userId: session.user.id, + }); + + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.delete", + targetType: "keyword_routing_rule", + targetId: String(id), + success: true, + }); + + return { + ok: true, + }; + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to delete keyword routing rule:", error); + const t = await getTranslations("settings.keywordRouting.validation"); + emitActionAudit({ + category: "keyword_routing_rule", + action: "keyword_routing_rule.delete", + targetType: "keyword_routing_rule", + targetId: String(id), + success: false, + errorMessage: "DELETE_FAILED", + }); + return { + ok: false, + error: t("deleteFailed"), + errorCode: "OPERATION_FAILED", + }; + } +} + +/** + * 手动刷新缓存 + */ +export async function refreshKeywordRoutingCacheAction(): Promise< + ActionResult<{ stats: ReturnType }> +> { + try { + const t = await getTranslations("settings.keywordRouting.validation"); + const session = await getSession(); + if (session?.user.role !== "admin") { + return { + ok: false, + error: t("permissionDenied"), + errorCode: "PERMISSION_DENIED", + }; + } + + await keywordRoutingEngine.reload(); + + const stats = keywordRoutingEngine.getStats(); + + logger.info("[KeywordRoutingAction] Cache refreshed", { + stats, + userId: session.user.id, + }); + + return { + ok: true, + data: { stats }, + }; + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to refresh cache:", error); + const t = await getTranslations("settings.keywordRouting.validation"); + return { + ok: false, + error: t("refreshCacheFailed"), + errorCode: "OPERATION_FAILED", + }; + } +} + +/** + * 获取缓存统计信息 + */ +export async function getKeywordRoutingCacheStats() { + try { + const session = await getSession(); + if (session?.user.role !== "admin") { + return null; + } + + return keywordRoutingEngine.getStats(); + } catch (error) { + logger.error("[KeywordRoutingAction] Failed to get cache stats:", error); + return null; + } +} diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index c40a7302d..2f571355c 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -65,6 +65,7 @@ export async function saveSystemSettings(formData: { cleanupSchedule?: string; cleanupBatchSize?: number; enableClientVersionCheck?: boolean; + enableKeywordModelRouting?: boolean; verboseProviderError?: boolean; passThroughUpstreamErrorMessage?: boolean; enableHttp2?: boolean; @@ -118,6 +119,7 @@ export async function saveSystemSettings(formData: { cleanupSchedule: validated.cleanupSchedule, cleanupBatchSize: validated.cleanupBatchSize, enableClientVersionCheck: validated.enableClientVersionCheck, + enableKeywordModelRouting: validated.enableKeywordModelRouting, verboseProviderError: validated.verboseProviderError, passThroughUpstreamErrorMessage: validated.passThroughUpstreamErrorMessage, enableHttp2: validated.enableHttp2, diff --git a/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx b/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx index 58807e4dd..8e8928e28 100644 --- a/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx +++ b/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx @@ -38,6 +38,7 @@ const CATEGORIES: AuditCategory[] = [ "key", "notification", "sensitive_word", + "keyword_routing_rule", "model_price", ]; diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx index 90c1ece95..6c089ece0 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx @@ -983,6 +983,33 @@ export function LogicTraceTab({ )} + {/* Keyword Routing */} + {item.keywordRouting && ( +
+
+ + {tChain("details.keywordRouting")} +
+
+ + {item.keywordRouting.userRequestedModel} + + + + {item.keywordRouting.routedModel} + + + {item.keywordRouting.keyword} + + + {item.keywordRouting.matchedIn === "system" + ? tChain("details.keywordRoutingMatchedInSystem") + : tChain("details.keywordRoutingMatchedInUser")} + +
+
+ )} + {/* Model Redirect */} {item.modelRedirect && (
diff --git a/src/app/[locale]/settings/_lib/nav-items.ts b/src/app/[locale]/settings/_lib/nav-items.ts index 0d73167e0..aae27048f 100644 --- a/src/app/[locale]/settings/_lib/nav-items.ts +++ b/src/app/[locale]/settings/_lib/nav-items.ts @@ -52,6 +52,12 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [ label: "Sensitive Words", iconName: "shield-alert", }, + { + href: "/settings/keyword-routing", + labelKey: "nav.keywordRouting", + label: "Keyword Routing", + iconName: "filter", + }, { href: "/settings/error-rules", labelKey: "nav.errorRules", diff --git a/src/app/[locale]/settings/keyword-routing/_components/add-rule-dialog.tsx b/src/app/[locale]/settings/keyword-routing/_components/add-rule-dialog.tsx new file mode 100644 index 000000000..e66dc9c05 --- /dev/null +++ b/src/app/[locale]/settings/keyword-routing/_components/add-rule-dialog.tsx @@ -0,0 +1,188 @@ +"use client"; + +import { Plus } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { createKeywordRoutingRuleAction } from "@/lib/api-client/v1/actions/keyword-routing"; + +export function AddRuleDialog() { + const t = useTranslations("settings"); + const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [keyword, setKeyword] = useState(""); + const [sourceModel, setSourceModel] = useState(""); + const [targetModel, setTargetModel] = useState(""); + const [caseSensitive, setCaseSensitive] = useState(true); + const [priority, setPriority] = useState("0"); + const [description, setDescription] = useState(""); + + const resetForm = () => { + setKeyword(""); + setSourceModel(""); + setTargetModel(""); + setCaseSensitive(true); + setPriority("0"); + setDescription(""); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!keyword.trim()) { + toast.error(t("keywordRouting.dialog.keywordRequired")); + return; + } + + if (!targetModel.trim()) { + toast.error(t("keywordRouting.dialog.targetModelRequired")); + return; + } + + setIsSubmitting(true); + + try { + const parsedPriority = Number.parseInt(priority, 10); + const result = await createKeywordRoutingRuleAction({ + keyword: keyword.trim(), + sourceModel: sourceModel.trim() || null, + targetModel: targetModel.trim(), + caseSensitive, + priority: Number.isNaN(parsedPriority) ? 0 : parsedPriority, + description: description.trim() || undefined, + }); + + if (result.ok) { + toast.success(t("keywordRouting.addSuccess")); + setOpen(false); + resetForm(); + } else { + toast.error(result.error); + } + } catch { + toast.error(t("keywordRouting.addFailed")); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + + + + +
+ + {t("keywordRouting.dialog.addTitle")} + {t("keywordRouting.dialog.addDescription")} + + +
+
+ + setKeyword(e.target.value)} + placeholder={t("keywordRouting.dialog.keywordPlaceholder")} + className="bg-muted/50 border border-border rounded-lg focus:border-[#E25706]/50 focus:ring-[#E25706]/20" + required + /> +
+ +
+ + setSourceModel(e.target.value)} + placeholder={t("keywordRouting.dialog.sourceModelPlaceholder")} + className="bg-muted/50 border border-border rounded-lg focus:border-[#E25706]/50 focus:ring-[#E25706]/20" + /> +
+ +
+ + setTargetModel(e.target.value)} + placeholder={t("keywordRouting.dialog.targetModelPlaceholder")} + className="bg-muted/50 border border-border rounded-lg focus:border-[#E25706]/50 focus:ring-[#E25706]/20" + required + /> +
+ +
+ + +
+ +
+ + setPriority(e.target.value)} + placeholder={t("keywordRouting.dialog.priorityPlaceholder")} + className="bg-muted/50 border border-border rounded-lg focus:border-[#E25706]/50 focus:ring-[#E25706]/20" + /> +

+ {t("keywordRouting.dialog.priorityHint")} +

+
+ +
+ +