diff --git a/drizzle/0049_shocking_ultimatum.sql b/drizzle/0049_shocking_ultimatum.sql new file mode 100644 index 000000000..6a37f923f --- /dev/null +++ b/drizzle/0049_shocking_ultimatum.sql @@ -0,0 +1 @@ +ALTER TABLE "message_request" ADD COLUMN "special_settings" jsonb; \ No newline at end of file diff --git a/drizzle/meta/0049_snapshot.json b/drizzle/meta/0049_snapshot.json new file mode 100644 index 000000000..95d9cc796 --- /dev/null +++ b/drizzle/meta/0049_snapshot.json @@ -0,0 +1,2338 @@ +{ + "id": "9805fe46-f6bb-4a4e-8948-7f0785cdc3b4", + "prevId": "ed2dfa70-d3c8-47a7-8bfb-b20034410e06", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(50)", + "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_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "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": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "integer", + "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 + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_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_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_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_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": {} + } + }, + "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 + }, + "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": {} + } + }, + "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 + }, + "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, + "default": "'Asia/Shanghai'" + }, + "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.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 + }, + "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 + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "join_claude_pool": { + "name": "join_claude_pool", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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_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": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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.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(50)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "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_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" + ] + }, + "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 8285f1318..9f11c2090 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -344,6 +344,13 @@ "when": 1767758201870, "tag": "0048_unknown_bushwacker", "breakpoints": true + }, + { + "idx": 49, + "version": "7", + "when": 1767764802281, + "tag": "0049_shocking_ultimatum", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 1ebb1eb90..5a71dc3b9 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -128,6 +128,7 @@ "blocked": "Blocked", "nonBilling": "Non-Billing", "skipped": "Skipped", + "specialSettings": "Special", "times": "times", "loadedCount": "Loaded {count} records", "loadingMore": "Loading more...", @@ -153,6 +154,9 @@ "success": "Request completed successfully", "error": "Request failed, here are the detailed error messages and provider decision chain", "processing": "Request is in progress, not yet completed", + "specialSettings": { + "title": "Special settings" + }, "skipped": { "title": "Skipped", "reason": "Reason", @@ -381,6 +385,7 @@ "requestHeaders": "Request Headers", "requestBody": "Request Body", "requestMessages": "Request Messages", + "specialSettings": "Special", "requestMessagesDescription": "Message content sent by the client", "responseHeaders": "Response Headers", "responseBody": "Response Body", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index 7c96ae6ef..a3ab599bd 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -128,6 +128,7 @@ "blocked": "ブロック済み", "nonBilling": "非課金", "skipped": "スキップ", + "specialSettings": "特殊設定", "times": "回", "loadedCount": "{count} 件のレコードを読み込みました", "loadingMore": "読み込み中...", @@ -153,6 +154,9 @@ "success": "リクエストが正常に完了しました", "error": "リクエスト失敗、詳細なエラー情報とプロバイダー決定チェーンは以下の通りです", "processing": "リクエストは処理中であり、まだ完了していません", + "specialSettings": { + "title": "特殊設定" + }, "skipped": { "title": "スキップ情報", "reason": "理由", @@ -380,6 +384,7 @@ "requestHeaders": "リクエストヘッダー", "requestBody": "リクエストボディ", "requestMessages": "リクエスト メッセージ", + "specialSettings": "特殊設定", "requestMessagesDescription": "クライアントが送信したメッセージ内容", "responseHeaders": "レスポンスヘッダー", "responseBody": "レスポンスボディ", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 040d8c55a..55cdcf837 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -128,6 +128,7 @@ "blocked": "Заблокировано", "nonBilling": "Не тарифицируется", "skipped": "Пропущено", + "specialSettings": "Особые", "times": "раз", "loadedCount": "Загружено {count} записей", "loadingMore": "Загрузка...", @@ -153,6 +154,9 @@ "success": "Запрос успешно выполнен", "error": "Запрос не выполнен, ниже подробная информация об ошибке и цепочке решений поставщика", "processing": "Запрос находится в процессе выполнения и еще не завершен", + "specialSettings": { + "title": "Особые настройки" + }, "skipped": { "title": "Пропущено", "reason": "Причина", @@ -380,6 +384,7 @@ "requestHeaders": "Заголовки запроса", "requestBody": "Тело запроса", "requestMessages": "Сообщения запроса", + "specialSettings": "Особые", "requestMessagesDescription": "Содержимое сообщения, отправленного клиентом", "responseHeaders": "Заголовки ответа", "responseBody": "Тело ответа", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index 21533edf1..426652833 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -128,6 +128,7 @@ "blocked": "被拦截", "nonBilling": "非计费", "skipped": "已跳过", + "specialSettings": "特殊设置", "times": "次", "loadedCount": "已加载 {count} 条记录", "loadingMore": "加载更多中...", @@ -153,6 +154,9 @@ "success": "请求成功完成", "error": "请求失败,以下是详细的错误信息和供应商决策链", "processing": "请求正在进行中,尚未完成", + "specialSettings": { + "title": "特殊设置" + }, "skipped": { "title": "跳过信息", "reason": "原因", @@ -381,6 +385,7 @@ "requestHeaders": "请求头", "requestBody": "请求体", "requestMessages": "请求 Messages", + "specialSettings": "特殊设置", "requestMessagesDescription": "客户端发送的消息内容", "responseHeaders": "响应头", "responseBody": "响应体", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index 95e85ec3b..f2d4e9975 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -128,6 +128,7 @@ "blocked": "已攔截", "nonBilling": "非計費", "skipped": "已跳過", + "specialSettings": "特殊設定", "times": "次", "loadedCount": "已載入 {count} 筆記錄", "loadingMore": "載入更多中...", @@ -153,6 +154,9 @@ "success": "請求成功完成", "error": "請求失敗,以下是詳細的錯誤訊息和供應商決策鏈", "processing": "請求正在進行中,尚未完成", + "specialSettings": { + "title": "特殊設定" + }, "skipped": { "title": "跳過資訊", "reason": "原因", @@ -381,6 +385,7 @@ "requestHeaders": "請求頭", "requestBody": "請求體", "requestMessages": "請求訊息", + "specialSettings": "特殊設定", "requestMessagesDescription": "用戶端傳送的訊息內容", "responseHeaders": "響應頭", "responseBody": "響應體", diff --git a/src/actions/active-sessions.ts b/src/actions/active-sessions.ts index bbecaf672..ba1cf03ae 100644 --- a/src/actions/active-sessions.ts +++ b/src/actions/active-sessions.ts @@ -10,6 +10,7 @@ import { import { logger } from "@/lib/logger"; import { normalizeRequestSequence } from "@/lib/utils/request-sequence"; import type { ActiveSessionInfo } from "@/types/session"; +import type { SpecialSetting } from "@/types/special-settings"; import { summarizeTerminateSessionsBatch } from "./active-sessions-utils"; import type { ActionResult } from "./types"; @@ -518,6 +519,7 @@ export async function getSessionDetails( responseHeaders: Record | null; requestMeta: { clientUrl: string | null; upstreamUrl: string | null; method: string | null }; responseMeta: { upstreamUrl: string | null; statusCode: number | null }; + specialSettings: SpecialSetting[] | null; sessionStats: Awaited< ReturnType > | null; @@ -616,6 +618,7 @@ export async function getSessionDetails( clientReqMeta, upstreamReqMeta, upstreamResMeta, + specialSettings, ] = await Promise.all([ SessionManager.getSessionRequestBody(sessionId, effectiveSequence), SessionManager.getSessionMessages(sessionId, effectiveSequence), @@ -625,6 +628,7 @@ export async function getSessionDetails( SessionManager.getSessionClientRequestMeta(sessionId, effectiveSequence), SessionManager.getSessionUpstreamRequestMeta(sessionId, effectiveSequence), SessionManager.getSessionUpstreamResponseMeta(sessionId, effectiveSequence), + SessionManager.getSessionSpecialSettings(sessionId, effectiveSequence), ]); // 兼容:历史/异常数据可能是 JSON 字符串(前端需要根级对象/数组) @@ -652,6 +656,7 @@ export async function getSessionDetails( responseHeaders, requestMeta, responseMeta, + specialSettings, sessionStats, currentSequence: effectiveSequence ?? null, prevSequence: adjacent.prevSequence, diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx index 6d665edf4..fa113cf57 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx @@ -28,6 +28,7 @@ import { cn, formatTokenAmount } from "@/lib/utils"; import { formatCurrency } from "@/lib/utils/currency"; import { formatProviderTimeline } from "@/lib/utils/provider-chain-formatter"; import type { ProviderChainItem } from "@/types/message"; +import type { SpecialSetting } from "@/types/special-settings"; import type { BillingModelSource } from "@/types/system-config"; interface ErrorDetailsDialogProps { @@ -44,6 +45,7 @@ interface ErrorDetailsDialogProps { messagesCount?: number | null; // Messages 数量 endpoint?: string | null; // API 端点 billingModelSource?: BillingModelSource; // 计费模型来源 + specialSettings?: SpecialSetting[] | null; // 特殊设置(审计/展示) // 计费详情 inputTokens?: number | null; outputTokens?: number | null; @@ -76,6 +78,7 @@ export function ErrorDetailsDialog({ messagesCount, endpoint, billingModelSource = "original", + specialSettings, inputTokens, outputTokens, cacheCreationInputTokens, @@ -114,6 +117,9 @@ export function ErrorDetailsDialog({ const isWarmupSkipped = blockedBy === "warmup"; const isBlocked = !!blockedBy && !isWarmupSkipped; // 是否被拦截(不含 warmup 跳过) + const specialSettingsContent = + specialSettings && specialSettings.length > 0 ? JSON.stringify(specialSettings, null, 2) : null; + const outputTokensPerSecond = (() => { if ( outputTokens === null || @@ -412,6 +418,18 @@ export function ErrorDetailsDialog({ )} + {/* 特殊设置(审计) */} + {specialSettingsContent && ( +
+

{t("logs.details.specialSettings.title")}

+
+
+                  {specialSettingsContent}
+                
+
+
+ )} + {/* 计费详情 + 性能数据并排布局 */} {(() => { const showBilling = !!costUsd; diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx index d42a94be3..55fc1cf86 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx @@ -234,6 +234,14 @@ export function UsageLogsTable({ setDialogState({ logId: log.id, scrollToRedirect: true }) } /> + {log.specialSettings && log.specialSettings.length > 0 ? ( + + {t("logs.table.specialSettings")} + + ) : null} @@ -486,6 +494,7 @@ export function UsageLogsTable({ messagesCount={log.messagesCount} endpoint={log.endpoint} billingModelSource={billingModelSource} + specialSettings={log.specialSettings} inputTokens={log.inputTokens} outputTokens={log.outputTokens} cacheCreationInputTokens={log.cacheCreationInputTokens} diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx index ab560ae67..d9ac0c1dc 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx @@ -32,6 +32,7 @@ function formatHeaders( interface SessionMessagesDetailsTabsProps { requestBody: unknown | null; messages: SessionMessages | null; + specialSettings: unknown | null; requestHeaders: Record | null; responseHeaders: Record | null; response: string | null; @@ -42,6 +43,7 @@ interface SessionMessagesDetailsTabsProps { export function SessionMessagesDetailsTabs({ requestBody, messages, + specialSettings, response, requestHeaders, responseHeaders, @@ -61,6 +63,11 @@ export function SessionMessagesDetailsTabs({ return JSON.stringify(messages, null, 2); }, [messages]); + const specialSettingsContent = useMemo(() => { + if (specialSettings === null) return null; + return JSON.stringify(specialSettings, null, 2); + }, [specialSettings]); + const requestHeadersPreamble = useMemo(() => { const lines: string[] = []; const method = requestMeta.method?.trim() || null; @@ -112,7 +119,7 @@ export function SessionMessagesDetailsTabs({ return ( - + {t("details.requestHeaders")} @@ -122,6 +129,9 @@ export function SessionMessagesDetailsTabs({ {t("details.requestMessages")} + + {t("details.specialSettings")} + {t("details.responseHeaders")} @@ -181,6 +191,23 @@ export function SessionMessagesDetailsTabs({ )} + + {specialSettingsContent === null ? ( +
{t("details.noData")}
+ ) : ( + + )} +
+ {formattedResponseHeaders === null ? (
{t("details.noHeaders")}
diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx index 1f388d8cc..d5f257b2e 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx @@ -16,6 +16,7 @@ const messages = { requestHeaders: "Request Headers", requestBody: "Request Body", requestMessages: "Request Messages", + specialSettings: "Special", responseHeaders: "Response Headers", responseBody: "Response Body", noHeaders: "No data", @@ -88,6 +89,7 @@ describe("SessionMessagesDetailsTabs", () => { { { { click(requestHeadersTrigger); expect(container.textContent).toContain("No data"); + const specialSettingsTrigger = container.querySelector( + "[data-testid='session-tab-trigger-special-settings']" + ) as HTMLElement; + click(specialSettingsTrigger); + expect(container.textContent).toContain("No Data"); + unmount(); }); @@ -203,6 +213,7 @@ describe("SessionMessagesDetailsTabs", () => { { (null); const [requestHeaders, setRequestHeaders] = useState | null>(null); const [responseHeaders, setResponseHeaders] = useState | null>(null); + const [specialSettings, setSpecialSettings] = + useState< + Extract< + Awaited>, + { ok: true } + >["data"]["specialSettings"] + >(null); const [requestMeta, setRequestMeta] = useState<{ clientUrl: string | null; upstreamUrl: string | null; @@ -90,6 +97,7 @@ export function SessionMessagesClient() { setResponse(null); setRequestHeaders(null); setResponseHeaders(null); + setSpecialSettings(null); setRequestMeta({ clientUrl: null, upstreamUrl: null, method: null }); setResponseMeta({ upstreamUrl: null, statusCode: null }); setSessionStats(null); @@ -134,6 +142,7 @@ export function SessionMessagesClient() { setResponse(result.data.response); setRequestHeaders(result.data.requestHeaders); setResponseHeaders(result.data.responseHeaders); + setSpecialSettings(result.data.specialSettings); setRequestMeta(result.data.requestMeta); setResponseMeta(result.data.responseMeta); setSessionStats(result.data.sessionStats); @@ -173,6 +182,7 @@ export function SessionMessagesClient() { meta: requestMeta, headers: requestHeaders, body: requestBody, + specialSettings, }, null, 2 @@ -373,6 +383,7 @@ export function SessionMessagesClient() { ); + session.request.message = overridden; + + if (audit) { + session.addSpecialSetting(audit); + const specialSettings = session.getSpecialSettings(); + + if (session.sessionId) { + void SessionManager.storeSessionSpecialSettings( + session.sessionId, + specialSettings, + session.requestSequence + ).catch((err) => { + logger.error("[ProxyForwarder] Failed to store special settings", { + error: err, + sessionId: session.sessionId, + }); + }); + } + + if (session.messageContext?.id) { + void updateMessageRequestDetails(session.messageContext.id, { + specialSettings, + }).catch((err) => { + logger.error("[ProxyForwarder] Failed to persist special settings", { + error: err, + messageRequestId: session.messageContext?.id, + }); + }); + } + } } if ( diff --git a/src/app/v1/_lib/proxy/session.ts b/src/app/v1/_lib/proxy/session.ts index 85a3619a6..35bb67267 100644 --- a/src/app/v1/_lib/proxy/session.ts +++ b/src/app/v1/_lib/proxy/session.ts @@ -9,6 +9,7 @@ import type { Key } from "@/types/key"; import type { ProviderChainItem } from "@/types/message"; import type { ModelPriceData } from "@/types/model-price"; import type { Provider, ProviderType } from "@/types/provider"; +import type { SpecialSetting } from "@/types/special-settings"; import type { User } from "@/types/user"; import { ProxyError } from "./errors"; import type { ClientFormat } from "./format-mapper"; @@ -94,6 +95,9 @@ export class ProxySession { // 1M Context Window applied (resolved) private context1mApplied: boolean = false; + // 特殊设置(用于审计/展示,可扩展) + private specialSettings: SpecialSetting[] = []; + // Cached price data (lazy loaded: undefined=not loaded, null=no data) private cachedPriceData?: ModelPriceData | null; @@ -264,6 +268,14 @@ export class ProxySession { return this.context1mApplied; } + addSpecialSetting(setting: SpecialSetting): void { + this.specialSettings.push(setting); + } + + getSpecialSettings(): SpecialSetting[] | null { + return this.specialSettings.length > 0 ? this.specialSettings : null; + } + /** * Check if client requests 1M context (based on anthropic-beta header) */ diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index 675ff0092..44e3d788d 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -13,6 +13,7 @@ import { pgEnum, } from 'drizzle-orm/pg-core'; import { relations, sql } from 'drizzle-orm'; +import type { SpecialSetting } from '@/types/special-settings'; // Enums export const dailyResetModeEnum = pgEnum('daily_reset_mode', ['fixed', 'rolling']); @@ -317,6 +318,9 @@ export const messageRequest = pgTable('message_request', { // 1M Context Window 应用状态 context1mApplied: boolean('context_1m_applied').default(false), + // 特殊设置(用于记录各类“特殊行为/覆写”的命中与生效情况,便于审计与展示) + specialSettings: jsonb('special_settings').$type(), + // 错误信息 errorMessage: text('error_message'), errorStack: text('error_stack'), // 完整堆栈信息,用于排查 TypeError: terminated 等流错误 diff --git a/src/lib/codex/provider-overrides.ts b/src/lib/codex/provider-overrides.ts index 22f9950f2..fa06f27fd 100644 --- a/src/lib/codex/provider-overrides.ts +++ b/src/lib/codex/provider-overrides.ts @@ -4,8 +4,11 @@ import type { CodexReasoningSummaryPreference, CodexTextVerbosityPreference, } from "@/types/provider"; +import type { ProviderParameterOverrideSpecialSetting } from "@/types/special-settings"; type CodexProviderOverrideConfig = { + id?: number; + name?: string; providerType?: string; codexReasoningEffortPreference?: CodexReasoningEffortPreference | null; codexReasoningSummaryPreference?: CodexReasoningSummaryPreference | null; @@ -17,6 +20,14 @@ function isPlainObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } +function toAuditValue(value: unknown): string | number | boolean | null { + if (value === undefined || value === null) return null; + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return value; + } + return null; +} + function normalizeStringPreference(value: string | null | undefined): string | null { if (!value || value === "inherit") return null; return value; @@ -88,3 +99,85 @@ export function applyCodexProviderOverrides( return output; } + +export function applyCodexProviderOverridesWithAudit( + provider: CodexProviderOverrideConfig, + request: Record +): { request: Record; audit: ProviderParameterOverrideSpecialSetting | null } { + if (provider.providerType !== "codex") { + return { request, audit: null }; + } + + const parallelToolCalls = normalizeParallelToolCallsPreference( + provider.codexParallelToolCallsPreference + ); + const reasoningEffort = normalizeStringPreference(provider.codexReasoningEffortPreference); + const reasoningSummary = normalizeStringPreference(provider.codexReasoningSummaryPreference); + const textVerbosity = normalizeStringPreference(provider.codexTextVerbosityPreference); + + const hit = + parallelToolCalls !== null || + reasoningEffort !== null || + reasoningSummary !== null || + textVerbosity !== null; + + if (!hit) { + return { request, audit: null }; + } + + const beforeParallelToolCalls = toAuditValue(request.parallel_tool_calls); + const beforeReasoning = isPlainObject(request.reasoning) ? request.reasoning : null; + const beforeReasoningEffort = toAuditValue(beforeReasoning?.effort); + const beforeReasoningSummary = toAuditValue(beforeReasoning?.summary); + const beforeText = isPlainObject(request.text) ? request.text : null; + const beforeTextVerbosity = toAuditValue(beforeText?.verbosity); + + const nextRequest = applyCodexProviderOverrides(provider, request); + + const afterParallelToolCalls = toAuditValue(nextRequest.parallel_tool_calls); + const afterReasoning = isPlainObject(nextRequest.reasoning) ? nextRequest.reasoning : null; + const afterReasoningEffort = toAuditValue(afterReasoning?.effort); + const afterReasoningSummary = toAuditValue(afterReasoning?.summary); + const afterText = isPlainObject(nextRequest.text) ? nextRequest.text : null; + const afterTextVerbosity = toAuditValue(afterText?.verbosity); + + const changes: ProviderParameterOverrideSpecialSetting["changes"] = [ + { + path: "parallel_tool_calls", + before: beforeParallelToolCalls, + after: afterParallelToolCalls, + changed: !Object.is(beforeParallelToolCalls, afterParallelToolCalls), + }, + { + path: "reasoning.effort", + before: beforeReasoningEffort, + after: afterReasoningEffort, + changed: !Object.is(beforeReasoningEffort, afterReasoningEffort), + }, + { + path: "reasoning.summary", + before: beforeReasoningSummary, + after: afterReasoningSummary, + changed: !Object.is(beforeReasoningSummary, afterReasoningSummary), + }, + { + path: "text.verbosity", + before: beforeTextVerbosity, + after: afterTextVerbosity, + changed: !Object.is(beforeTextVerbosity, afterTextVerbosity), + }, + ]; + + const audit: ProviderParameterOverrideSpecialSetting = { + type: "provider_parameter_override", + scope: "provider", + providerId: provider.id ?? null, + providerName: provider.name ?? null, + providerType: provider.providerType ?? null, + hit: true, + changed: changes.some((c) => c.changed), + changes, + }; + + return { request: nextRequest, audit }; +} diff --git a/src/lib/session-manager.ts b/src/lib/session-manager.ts index b2315c201..7633e3ac5 100644 --- a/src/lib/session-manager.ts +++ b/src/lib/session-manager.ts @@ -11,6 +11,7 @@ import type { SessionStoreInfo, SessionUsageUpdate, } from "@/types/session"; +import type { SpecialSetting } from "@/types/special-settings"; import { getRedisClient } from "./redis"; import { SessionTracker } from "./session-tracker"; @@ -1426,6 +1427,58 @@ export class SessionManager { } } + /** + * 存储特殊设置(审计字段,临时存储,5分钟过期) + * + * @param sessionId - Session ID + * @param specialSettings - 特殊设置(可为空) + * @param requestSequence - 请求序号 + */ + static async storeSessionSpecialSettings( + sessionId: string, + specialSettings: SpecialSetting[] | null, + requestSequence?: number + ): Promise { + if (!specialSettings || specialSettings.length === 0) { + return; + } + + const redis = getRedisClient(); + if (!redis || redis.status !== "ready") return; + + try { + const sequence = normalizeRequestSequence(requestSequence) ?? 1; + const key = `session:${sessionId}:req:${sequence}:specialSettings`; + const payload = JSON.stringify(specialSettings); + await redis.setex(key, SessionManager.SESSION_TTL, payload); + } catch (error) { + logger.error("SessionManager: Failed to store special settings", { error, sessionId }); + } + } + + static async getSessionSpecialSettings( + sessionId: string, + requestSequence?: number + ): Promise { + const redis = getRedisClient(); + if (!redis || redis.status !== "ready") return null; + + try { + const sequence = normalizeRequestSequence(requestSequence); + if (!sequence) return null; + const key = `session:${sessionId}:req:${sequence}:specialSettings`; + const value = await redis.get(key); + if (!value) return null; + + const parsed: unknown = JSON.parse(value); + if (!Array.isArray(parsed)) return null; + return parsed as SpecialSetting[]; + } catch (error) { + logger.error("SessionManager: Failed to get special settings", { error, sessionId }); + return null; + } + } + /** * 存储客户端请求元信息(端点/方法,临时存储,5分钟过期) * diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index dae13710c..4232270e1 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -133,6 +133,7 @@ export function toMessageRequest(dbMessage: any): MessageRequest { cacheCreation1hInputTokens: dbMessage?.cacheCreation1hInputTokens ?? undefined, cacheTtlApplied: dbMessage?.cacheTtlApplied ?? null, context1mApplied: dbMessage?.context1mApplied ?? false, + specialSettings: dbMessage?.specialSettings ?? null, }; } diff --git a/src/repository/message-write-buffer.ts b/src/repository/message-write-buffer.ts index b1b0a6288..e7f946bb1 100644 --- a/src/repository/message-write-buffer.ts +++ b/src/repository/message-write-buffer.ts @@ -26,6 +26,7 @@ export type MessageRequestUpdatePatch = { model?: string; providerId?: number; context1mApplied?: boolean; + specialSettings?: CreateMessageRequestData["special_settings"]; }; type MessageRequestUpdateRecord = { @@ -58,6 +59,7 @@ const COLUMN_MAP: Record = { model: "model", providerId: "provider_id", context1mApplied: "context_1m_applied", + specialSettings: "special_settings", }; function loadWriterConfig(): WriterConfig { @@ -99,7 +101,7 @@ function buildBatchUpdateSql(updates: MessageRequestUpdateRecord[]): SQL | null continue; } - if (key === "providerChain") { + if (key === "providerChain" || key === "specialSettings") { if (value === null) { cases.push(sql`WHEN ${update.id} THEN NULL`); continue; diff --git a/src/repository/message.ts b/src/repository/message.ts index 948140b2b..0d4a2c137 100644 --- a/src/repository/message.ts +++ b/src/repository/message.ts @@ -132,6 +132,7 @@ export async function updateMessageRequestDetails( model?: string; // ⭐ 新增:支持更新重定向后的模型名称 providerId?: number; // ⭐ 新增:支持更新最终供应商ID(重试切换后) context1mApplied?: boolean; // 是否应用了1M上下文窗口 + specialSettings?: CreateMessageRequestData["special_settings"]; // 特殊设置(审计/展示) } ): Promise { if (getEnvConfig().MESSAGE_REQUEST_WRITE_MODE === "async") { @@ -191,6 +192,9 @@ export async function updateMessageRequestDetails( if (details.context1mApplied !== undefined) { updateData.context1mApplied = details.context1mApplied; } + if (details.specialSettings !== undefined) { + updateData.specialSettings = details.specialSettings; + } await db.update(messageRequest).set(updateData).where(eq(messageRequest.id, id)); } diff --git a/src/repository/usage-logs.ts b/src/repository/usage-logs.ts index 638239b40..7d9f8dcde 100644 --- a/src/repository/usage-logs.ts +++ b/src/repository/usage-logs.ts @@ -4,6 +4,7 @@ import { and, desc, eq, isNull, sql } from "drizzle-orm"; import { db } from "@/drizzle/db"; import { keys as keysTable, messageRequest, providers, users } from "@/drizzle/schema"; import type { ProviderChainItem } from "@/types/message"; +import type { SpecialSetting } from "@/types/special-settings"; import { EXCLUDE_WARMUP_CONDITION } from "./_shared/message-request-conditions"; export interface UsageLogFilters { @@ -56,6 +57,7 @@ export interface UsageLogRow { userAgent: string | null; // User-Agent(客户端信息) messagesCount: number | null; // Messages 数量 context1mApplied: boolean | null; // 是否应用了1M上下文窗口 + specialSettings: SpecialSetting[] | null; // 特殊设置(审计/展示) } export interface UsageLogSummary { @@ -213,6 +215,7 @@ export async function findUsageLogsBatch( userAgent: messageRequest.userAgent, messagesCount: messageRequest.messagesCount, context1mApplied: messageRequest.context1mApplied, + specialSettings: messageRequest.specialSettings, }) .from(messageRequest) .innerJoin(users, eq(messageRequest.userId, users.id)) @@ -426,6 +429,7 @@ export async function findUsageLogsWithDetails(filters: UsageLogFilters): Promis userAgent: messageRequest.userAgent, // User-Agent messagesCount: messageRequest.messagesCount, // Messages 数量 context1mApplied: messageRequest.context1mApplied, // 1M上下文窗口 + specialSettings: messageRequest.specialSettings, // 特殊设置(审计/展示) }) .from(messageRequest) .innerJoin(users, eq(messageRequest.userId, users.id)) diff --git a/src/types/message.ts b/src/types/message.ts index 16bb098aa..3be02d63a 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,5 +1,6 @@ import type { Numeric } from "decimal.js-light"; import type { CacheTtlApplied } from "./cache"; +import type { SpecialSetting } from "./special-settings"; /** * 供应商信息(用于决策链) @@ -226,6 +227,9 @@ export interface MessageRequest { // 1M 上下文窗口是否已应用 context1mApplied?: boolean; + // 特殊设置(用于记录各类“特殊行为/覆写”的命中与生效情况,便于审计与展示) + specialSettings?: SpecialSetting[] | null; + createdAt: Date; updatedAt: Date; deletedAt?: Date; @@ -280,6 +284,9 @@ export interface CreateMessageRequestData { // Messages 数量(用于短请求检测和分析) messages_count?: number; + + // 特殊设置(用于审计与展示;JSONB) + special_settings?: SpecialSetting[] | null; } /** diff --git a/src/types/special-settings.ts b/src/types/special-settings.ts new file mode 100644 index 000000000..6b17995cb --- /dev/null +++ b/src/types/special-settings.ts @@ -0,0 +1,26 @@ +/** + * 特殊设置(通用审计字段) + * + * 用于记录请求在代理链路中发生的“特殊行为/特殊覆写”的命中与生效情况, + * 便于在请求记录与请求详情中展示,支持后续扩展更多类型。 + */ + +export type SpecialSetting = ProviderParameterOverrideSpecialSetting; + +export type SpecialSettingChangeValue = string | number | boolean | null; + +export type ProviderParameterOverrideSpecialSetting = { + type: "provider_parameter_override"; + scope: "provider"; + providerId: number | null; + providerName: string | null; + providerType: string | null; + hit: boolean; + changed: boolean; + changes: Array<{ + path: string; + before: SpecialSettingChangeValue; + after: SpecialSettingChangeValue; + changed: boolean; + }>; +}; diff --git a/tests/unit/proxy/codex-provider-overrides.test.ts b/tests/unit/proxy/codex-provider-overrides.test.ts index 485f535bf..b86f65305 100644 --- a/tests/unit/proxy/codex-provider-overrides.test.ts +++ b/tests/unit/proxy/codex-provider-overrides.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { applyCodexProviderOverrides } from "@/lib/codex/provider-overrides"; +import { + applyCodexProviderOverrides, + applyCodexProviderOverridesWithAudit, +} from "@/lib/codex/provider-overrides"; describe("Codex 供应商级参数覆写", () => { it("当 providerType 不是 codex 时,应直接返回原对象且不做任何处理", () => { @@ -121,4 +124,108 @@ describe("Codex 供应商级参数覆写", () => { expect(output.reasoning).toEqual({ effort: "minimal", summary: "auto" }); expect(output.text).toEqual({ verbosity: "high" }); }); + + it("审计:当 providerType 不是 codex 时,应返回 audit=null 且保持引用不变", () => { + const provider = { + id: 123, + name: "P", + providerType: "claude", + codexParallelToolCallsPreference: "false", + }; + + const input: Record = { + model: "gpt-5-codex", + input: [], + parallel_tool_calls: true, + }; + + const result = applyCodexProviderOverridesWithAudit(provider as any, input); + expect(result.request).toBe(input); + expect(result.audit).toBeNull(); + }); + + it("审计:当所有偏好均为 inherit/null 时,应返回 audit=null 且不做覆写", () => { + const provider = { + providerType: "codex", + codexReasoningEffortPreference: "inherit", + codexReasoningSummaryPreference: null, + codexTextVerbosityPreference: "inherit", + codexParallelToolCallsPreference: null, + }; + + const input: Record = { + model: "gpt-5-codex", + input: [], + parallel_tool_calls: false, + reasoning: { effort: "low", summary: "auto" }, + text: { verbosity: "medium" }, + }; + + const result = applyCodexProviderOverridesWithAudit(provider as any, input); + expect(result.request).toBe(input); + expect(result.audit).toBeNull(); + }); + + it("审计:当偏好命中但值未变化时,应标记 changed=false 并记录 before/after", () => { + const provider = { + id: 1, + name: "codex-provider", + providerType: "codex", + codexParallelToolCallsPreference: "false", + }; + + const input: Record = { + model: "gpt-5-codex", + input: [], + parallel_tool_calls: false, + }; + + const result = applyCodexProviderOverridesWithAudit(provider as any, input); + + expect(result.audit?.hit).toBe(true); + expect(result.audit?.changed).toBe(false); + expect(result.audit?.providerId).toBe(1); + expect(result.audit?.providerName).toBe("codex-provider"); + + const parallelChange = result.audit?.changes.find((c) => c.path === "parallel_tool_calls"); + expect(parallelChange).toEqual({ + path: "parallel_tool_calls", + before: false, + after: false, + changed: false, + }); + }); + + it("审计:当偏好命中且值变化时,应标记 changed=true 并记录变化明细", () => { + const provider = { + id: 2, + name: "codex-provider", + providerType: "codex", + codexReasoningEffortPreference: "high", + codexReasoningSummaryPreference: "detailed", + codexTextVerbosityPreference: "high", + codexParallelToolCallsPreference: "true", + }; + + const input: Record = { + model: "gpt-5-codex", + input: [], + parallel_tool_calls: false, + reasoning: { effort: "low", summary: "auto" }, + text: { verbosity: "low" }, + }; + + const result = applyCodexProviderOverridesWithAudit(provider as any, input); + + expect(result.audit?.hit).toBe(true); + expect(result.audit?.changed).toBe(true); + + const changedPaths = (result.audit?.changes ?? []).filter((c) => c.changed).map((c) => c.path); + expect(changedPaths).toEqual([ + "parallel_tool_calls", + "reasoning.effort", + "reasoning.summary", + "text.verbosity", + ]); + }); });