diff --git a/drizzle/0060_bored_gertrude_yorkes.sql b/drizzle/0060_bored_gertrude_yorkes.sql new file mode 100644 index 000000000..1d49895c3 --- /dev/null +++ b/drizzle/0060_bored_gertrude_yorkes.sql @@ -0,0 +1,3 @@ +ALTER TABLE "notification_target_bindings" ALTER COLUMN "schedule_timezone" DROP DEFAULT;--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN "anthropic_max_tokens_preference" varchar(20);--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN "anthropic_thinking_budget_preference" varchar(20); \ No newline at end of file diff --git a/drizzle/0061_exotic_trauma.sql b/drizzle/0061_exotic_trauma.sql new file mode 100644 index 000000000..b53c379cc --- /dev/null +++ b/drizzle/0061_exotic_trauma.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN "enable_thinking_budget_rectifier" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0060_snapshot.json b/drizzle/meta/0060_snapshot.json new file mode 100644 index 000000000..4d523d03a --- /dev/null +++ b/drizzle/meta/0060_snapshot.json @@ -0,0 +1,2948 @@ +{ + "id": "c7b0107b-226c-4247-b953-7ce437107172", + "prevId": "5fd37dcd-8e23-4450-9177-cea694050745", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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_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_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 + }, + "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 + }, + "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, + "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_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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 + }, + "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 + }, + "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": {} + }, + "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": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "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/0061_snapshot.json b/drizzle/meta/0061_snapshot.json new file mode 100644 index 000000000..4755563ef --- /dev/null +++ b/drizzle/meta/0061_snapshot.json @@ -0,0 +1,2955 @@ +{ + "id": "ae09efb5-f924-4c72-b41f-baccc1765fcb", + "prevId": "c7b0107b-226c-4247-b953-7ce437107172", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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_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_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 + }, + "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 + }, + "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, + "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_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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 + }, + "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 + }, + "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": {} + }, + "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": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "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 82949325e..b6a6acf44 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -421,6 +421,20 @@ "when": 1769539222210, "tag": "0059_safe_xorn", "breakpoints": true + }, + { + "idx": 60, + "version": "7", + "when": 1769850054508, + "tag": "0060_bored_gertrude_yorkes", + "breakpoints": true + }, + { + "idx": 61, + "version": "7", + "when": 1769852342867, + "tag": "0061_exotic_trauma", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 63c71c240..32a73e825 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -51,6 +51,8 @@ "enableResponseFixerDesc": "Automatically repairs common upstream response issues (encoding, SSE, truncated JSON). Enabled by default.", "enableThinkingSignatureRectifier": "Enable Thinking Signature Rectifier", "enableThinkingSignatureRectifierDesc": "When Anthropic providers return thinking signature incompatibility or invalid request errors, automatically removes incompatible thinking blocks and retries once against the same provider (enabled by default).", + "enableThinkingBudgetRectifier": "Enable Thinking Budget Rectifier", + "enableThinkingBudgetRectifierDesc": "When Anthropic providers return budget_tokens < 1024 errors, automatically sets thinking budget to maximum (32000) and max_tokens to 64000 if needed, then retries once (enabled by default).", "enableCodexSessionIdCompletion": "Enable Codex Session ID Completion", "enableCodexSessionIdCompletionDesc": "When Codex requests provide only one of session_id (header) or prompt_cache_key (body), automatically completes the other. If both are missing, generates a UUID v7 session id and reuses it stably within the same conversation.", "interceptAnthropicWarmupRequests": "Intercept Warmup Requests (Anthropic)", diff --git a/messages/en/settings/providers/form/sections.json b/messages/en/settings/providers/form/sections.json index 75a90f64b..c4921cc0a 100644 --- a/messages/en/settings/providers/form/sections.json +++ b/messages/en/settings/providers/form/sections.json @@ -238,6 +238,27 @@ } } }, + "anthropicOverrides": { + "maxTokens": { + "label": "Max Tokens Override", + "help": "Override max_tokens in request body. Range: 1-64000. 'inherit' follows the client request.", + "options": { + "inherit": "No override (follow client)", + "custom": "Custom" + }, + "placeholder": "e.g. 32000" + }, + "thinkingBudget": { + "label": "Thinking Budget Override", + "help": "Override thinking.budget_tokens in request body. Range: 1024-32000. Forces thinking.type to 'enabled' when set.", + "options": { + "inherit": "No override (follow client)", + "custom": "Custom" + }, + "placeholder": "e.g. 10240", + "maxOutButton": "Max Out (32000)" + } + }, "context1m": { "desc": "Configure 1M context window support. Only affects Sonnet models (claude-sonnet-4-5, claude-sonnet-4). Tiered pricing applies when enabled.", "label": "1M Context Window", diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index 2133d0072..52a57ed6e 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -51,6 +51,8 @@ "enableResponseFixerDesc": "上流応答の一般的な形式問題(エンコーディング、SSE、途切れた JSON)を自動修復します(既定で有効)。", "enableThinkingSignatureRectifier": "thinking 署名整流を有効化", "enableThinkingSignatureRectifierDesc": "Anthropic プロバイダーで thinking 署名の不整合や不正なリクエストエラーが発生した場合、thinking 関連ブロックを削除して同一プロバイダーへ1回だけ再試行します(既定で有効)。", + "enableThinkingBudgetRectifier": "thinking 予算整流を有効化", + "enableThinkingBudgetRectifierDesc": "Anthropic プロバイダーで budget_tokens < 1024 エラーが発生した場合、thinking 予算を最大値(32000)に設定し、必要に応じて max_tokens を 64000 に設定して1回だけ再試行します(既定で有効)。", "enableCodexSessionIdCompletion": "Codex セッションID補完を有効化", "enableCodexSessionIdCompletionDesc": "Codex リクエストで session_id(ヘッダー)または prompt_cache_key(ボディ)のどちらか一方しか提供されない場合に、欠けている方を自動補完します。両方ない場合は UUID v7 のセッションIDを生成し、同一対話内で安定して再利用します。", "interceptAnthropicWarmupRequests": "Warmup リクエストを遮断(Anthropic)", diff --git a/messages/ja/settings/providers/form/sections.json b/messages/ja/settings/providers/form/sections.json index e5a64d1d6..be69f64ca 100644 --- a/messages/ja/settings/providers/form/sections.json +++ b/messages/ja/settings/providers/form/sections.json @@ -239,6 +239,27 @@ } } }, + "anthropicOverrides": { + "maxTokens": { + "label": "Max Tokens オーバーライド", + "help": "リクエストボディの max_tokens を上書きします。範囲:1-64000。「継承」はクライアントリクエストに従います。", + "options": { + "inherit": "上書きなし(クライアントに従う)", + "custom": "カスタム" + }, + "placeholder": "例: 32000" + }, + "thinkingBudget": { + "label": "思考予算オーバーライド", + "help": "リクエストボディの thinking.budget_tokens を上書きします。範囲:1024-32000。設定すると thinking.type が 'enabled' に強制されます。", + "options": { + "inherit": "上書きなし(クライアントに従う)", + "custom": "カスタム" + }, + "placeholder": "例: 10240", + "maxOutButton": "最大化 (32000)" + } + }, "context1m": { "desc": "1M コンテキストウィンドウのサポートを設定します。Sonnet モデル(claude-sonnet-4-5、claude-sonnet-4)にのみ適用されます。有効時は段階的料金が適用されます。", "label": "1M コンテキストウィンドウ", diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 63f7e37fa..98fe27ee3 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -51,6 +51,8 @@ "enableResponseFixerDesc": "Автоматически исправляет распространённые проблемы ответа у провайдеров (кодировка, SSE, обрезанный JSON). Включено по умолчанию.", "enableThinkingSignatureRectifier": "Включить исправление thinking-signature", "enableThinkingSignatureRectifierDesc": "Если Anthropic-провайдер возвращает ошибку несовместимой подписи thinking или некорректного запроса, автоматически удаляет несовместимые thinking-блоки и повторяет запрос один раз к тому же провайдеру (включено по умолчанию).", + "enableThinkingBudgetRectifier": "Включить исправление thinking-budget", + "enableThinkingBudgetRectifierDesc": "Если Anthropic-провайдер возвращает ошибку budget_tokens < 1024, автоматически устанавливает thinking budget на максимум (32000) и при необходимости max_tokens на 64000, затем повторяет запрос один раз (включено по умолчанию).", "enableCodexSessionIdCompletion": "Включить дополнение Session ID для Codex", "enableCodexSessionIdCompletionDesc": "Если в Codex-запросе присутствует только session_id (в заголовках) или prompt_cache_key (в теле), автоматически дополняет отсутствующее поле. Если оба отсутствуют, генерирует UUID v7 и стабильно переиспользует его в рамках одного диалога.", "interceptAnthropicWarmupRequests": "Перехватывать Warmup-запросы (Anthropic)", diff --git a/messages/ru/settings/providers/form/sections.json b/messages/ru/settings/providers/form/sections.json index 46ed44ebd..b83e4cc21 100644 --- a/messages/ru/settings/providers/form/sections.json +++ b/messages/ru/settings/providers/form/sections.json @@ -239,6 +239,27 @@ } } }, + "anthropicOverrides": { + "maxTokens": { + "label": "Переопределение Max Tokens", + "help": "Переопределяет max_tokens в теле запроса. Диапазон: 1-64000. 'Наследовать' следует запросу клиента.", + "options": { + "inherit": "Без переопределения (следовать клиенту)", + "custom": "Пользовательское" + }, + "placeholder": "напр. 32000" + }, + "thinkingBudget": { + "label": "Переопределение бюджета размышлений", + "help": "Переопределяет thinking.budget_tokens в теле запроса. Диапазон: 1024-32000. При установке принудительно включает thinking.type = 'enabled'.", + "options": { + "inherit": "Без переопределения (следовать клиенту)", + "custom": "Пользовательское" + }, + "placeholder": "напр. 10240", + "maxOutButton": "Максимум (32000)" + } + }, "context1m": { "desc": "Настройка поддержки контекстного окна 1M. Применяется только к моделям Sonnet (claude-sonnet-4-5, claude-sonnet-4). При включении применяется многоуровневая тарификация.", "label": "Контекстное окно 1M", diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index d03249bde..a8bc315aa 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -40,6 +40,8 @@ "interceptAnthropicWarmupRequestsDesc": "开启后,识别到 Claude Code 的 Warmup 探测请求将由 CCH 直接抢答短响应,避免访问上游供应商;该请求会记录在日志中,但不计费、不限流、不计入统计。", "enableThinkingSignatureRectifier": "启用 thinking 签名整流器", "enableThinkingSignatureRectifierDesc": "当 Anthropic 类型供应商返回 thinking 签名不兼容或非法请求等错误时,自动移除不兼容的 thinking 相关块并对同一供应商重试一次(默认开启)。", + "enableThinkingBudgetRectifier": "启用 thinking 预算整流器", + "enableThinkingBudgetRectifierDesc": "当 Anthropic 类型供应商返回 budget_tokens < 1024 错误时,自动将 thinking 预算设为最大值(32000),并在需要时将 max_tokens 设为 64000,然后重试一次(默认开启)。", "enableCodexSessionIdCompletion": "启用 Codex Session ID 补全", "enableCodexSessionIdCompletionDesc": "当 Codex 请求仅提供 session_id(请求头)或 prompt_cache_key(请求体)之一时,自动补全另一个;若两者均缺失,则生成 UUID v7 会话 ID,并在同一对话内稳定复用。", "enableResponseFixer": "启用响应整流", diff --git a/messages/zh-CN/settings/providers/form/sections.json b/messages/zh-CN/settings/providers/form/sections.json index bb250af48..2404df00a 100644 --- a/messages/zh-CN/settings/providers/form/sections.json +++ b/messages/zh-CN/settings/providers/form/sections.json @@ -126,13 +126,34 @@ }, "parallelToolCalls": { "label": "并行工具调用覆写", - "help": "控制是否允许并行 tool calls。关闭可能降低工具调用并发能力;“跟随客户端”不改写 parallel_tool_calls。", + "help": "控制是否允许并行 tool calls。关闭可能降低工具调用并发能力;'跟随客户端'不改写 parallel_tool_calls。", "options": { "inherit": "不覆写(跟随客户端)", "true": "强制开启", "false": "强制关闭" } } + }, + "anthropicOverrides": { + "maxTokens": { + "label": "Max Tokens 覆写", + "help": "覆写请求体中的 max_tokens。范围:1-64000。'继承' 表示遵循客户端请求。", + "options": { + "inherit": "不覆写(遵循客户端)", + "custom": "自定义" + }, + "placeholder": "如 32000" + }, + "thinkingBudget": { + "label": "思考预算覆写", + "help": "覆写请求体中的 thinking.budget_tokens。范围:1024-32000。设置后会强制 thinking.type 为 'enabled'。", + "options": { + "inherit": "不覆写(遵循客户端)", + "custom": "自定义" + }, + "placeholder": "如 10240", + "maxOutButton": "拉满 (32000)" + } } }, "rateLimit": { diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index cd1919eb0..5c21919e4 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -51,6 +51,8 @@ "enableResponseFixerDesc": "自動修復上游回應中常見的編碼、SSE 與 JSON 格式問題(預設開啟)。", "enableThinkingSignatureRectifier": "啟用 thinking 簽名整流器", "enableThinkingSignatureRectifierDesc": "當 Anthropic 類型供應商返回 thinking 簽名不相容或非法請求等錯誤時,自動移除不相容的 thinking 相關區塊並對同一供應商重試一次(預設開啟)。", + "enableThinkingBudgetRectifier": "啟用 thinking 預算整流器", + "enableThinkingBudgetRectifierDesc": "當 Anthropic 類型供應商返回 budget_tokens < 1024 錯誤時,自動將 thinking 預算設為最大值(32000),並在需要時將 max_tokens 設為 64000,然後重試一次(預設開啟)。", "enableCodexSessionIdCompletion": "啟用 Codex Session ID 補全", "enableCodexSessionIdCompletionDesc": "當 Codex 請求僅提供 session_id(請求頭)或 prompt_cache_key(請求體)之一時,自動補全另一個;若兩者皆缺失,則產生 UUID v7 會話 ID,並在同一對話內穩定複用。", "interceptAnthropicWarmupRequests": "攔截 Warmup 請求(Anthropic)", diff --git a/messages/zh-TW/settings/providers/form/sections.json b/messages/zh-TW/settings/providers/form/sections.json index d5e0e72b9..356991b28 100644 --- a/messages/zh-TW/settings/providers/form/sections.json +++ b/messages/zh-TW/settings/providers/form/sections.json @@ -239,6 +239,27 @@ } } }, + "anthropicOverrides": { + "maxTokens": { + "label": "Max Tokens 覆寫", + "help": "覆寫請求體中的 max_tokens。範圍:1-64000。'繼承' 表示遵循客戶端請求。", + "options": { + "inherit": "不覆寫(遵循客戶端)", + "custom": "自訂" + }, + "placeholder": "如 32000" + }, + "thinkingBudget": { + "label": "思考預算覆寫", + "help": "覆寫請求體中的 thinking.budget_tokens。範圍:1024-32000。設置後會強制 thinking.type 為 'enabled'。", + "options": { + "inherit": "不覆寫(遵循客戶端)", + "custom": "自訂" + }, + "placeholder": "如 10240", + "maxOutButton": "拉滿 (32000)" + } + }, "context1m": { "desc": "設定 1M 上下文視窗支援。僅對 Sonnet 模型生效(claude-sonnet-4-5、claude-sonnet-4)。啟用後將套用階梯定價。", "label": "1M 上下文視窗", diff --git a/src/actions/providers.ts b/src/actions/providers.ts index 3a3693b05..989cf6970 100644 --- a/src/actions/providers.ts +++ b/src/actions/providers.ts @@ -280,6 +280,8 @@ export async function getProviders(): Promise { codexReasoningSummaryPreference: provider.codexReasoningSummaryPreference, codexTextVerbosityPreference: provider.codexTextVerbosityPreference, codexParallelToolCallsPreference: provider.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: provider.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: provider.anthropicThinkingBudgetPreference, tpm: provider.tpm, rpm: provider.rpm, rpd: provider.rpd, diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index 9d6206701..85b137b3c 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -57,6 +57,7 @@ export async function saveSystemSettings(formData: { enableHttp2?: boolean; interceptAnthropicWarmupRequests?: boolean; enableThinkingSignatureRectifier?: boolean; + enableThinkingBudgetRectifier?: boolean; enableCodexSessionIdCompletion?: boolean; enableResponseFixer?: boolean; responseFixerConfig?: Partial; @@ -90,6 +91,7 @@ export async function saveSystemSettings(formData: { enableHttp2: validated.enableHttp2, interceptAnthropicWarmupRequests: validated.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: validated.enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier: validated.enableThinkingBudgetRectifier, enableCodexSessionIdCompletion: validated.enableCodexSessionIdCompletion, enableResponseFixer: validated.enableResponseFixer, responseFixerConfig: validated.responseFixerConfig, diff --git a/src/app/[locale]/settings/config/_components/system-settings-form.tsx b/src/app/[locale]/settings/config/_components/system-settings-form.tsx index b1b65a1ae..a14a07cca 100644 --- a/src/app/[locale]/settings/config/_components/system-settings-form.tsx +++ b/src/app/[locale]/settings/config/_components/system-settings-form.tsx @@ -48,6 +48,7 @@ interface SystemSettingsFormProps { | "enableHttp2" | "interceptAnthropicWarmupRequests" | "enableThinkingSignatureRectifier" + | "enableThinkingBudgetRectifier" | "enableCodexSessionIdCompletion" | "enableResponseFixer" | "responseFixerConfig" @@ -85,6 +86,9 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) const [enableThinkingSignatureRectifier, setEnableThinkingSignatureRectifier] = useState( initialSettings.enableThinkingSignatureRectifier ); + const [enableThinkingBudgetRectifier, setEnableThinkingBudgetRectifier] = useState( + initialSettings.enableThinkingBudgetRectifier + ); const [enableCodexSessionIdCompletion, setEnableCodexSessionIdCompletion] = useState( initialSettings.enableCodexSessionIdCompletion ); @@ -135,6 +139,7 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) enableHttp2, interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier, enableCodexSessionIdCompletion, enableResponseFixer, responseFixerConfig, @@ -161,6 +166,7 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) setEnableHttp2(result.data.enableHttp2); setInterceptAnthropicWarmupRequests(result.data.interceptAnthropicWarmupRequests); setEnableThinkingSignatureRectifier(result.data.enableThinkingSignatureRectifier); + setEnableThinkingBudgetRectifier(result.data.enableThinkingBudgetRectifier); setEnableCodexSessionIdCompletion(result.data.enableCodexSessionIdCompletion); setEnableResponseFixer(result.data.enableResponseFixer); setResponseFixerConfig(result.data.responseFixerConfig); @@ -386,6 +392,29 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) /> + {/* Enable Thinking Budget Rectifier */} +
+
+
+ +
+
+

+ {t("enableThinkingBudgetRectifier")} +

+

+ {t("enableThinkingBudgetRectifierDesc")} +

+
+
+ setEnableThinkingBudgetRectifier(checked)} + disabled={isPending} + /> +
+ {/* Enable Codex Session ID Completion */}
diff --git a/src/app/[locale]/settings/config/page.tsx b/src/app/[locale]/settings/config/page.tsx index 52c4c78d6..90ce29a01 100644 --- a/src/app/[locale]/settings/config/page.tsx +++ b/src/app/[locale]/settings/config/page.tsx @@ -49,6 +49,7 @@ async function SettingsConfigContent() { enableHttp2: settings.enableHttp2, interceptAnthropicWarmupRequests: settings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: settings.enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier: settings.enableThinkingBudgetRectifier, enableCodexSessionIdCompletion: settings.enableCodexSessionIdCompletion, enableResponseFixer: settings.enableResponseFixer, responseFixerConfig: settings.responseFixerConfig, diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx index b39d8cffb..2edbddf55 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx @@ -207,6 +207,8 @@ function ProviderFormContent({ codex_reasoning_summary_preference: state.routing.codexReasoningSummaryPreference, codex_text_verbosity_preference: state.routing.codexTextVerbosityPreference, codex_parallel_tool_calls_preference: state.routing.codexParallelToolCallsPreference, + anthropic_max_tokens_preference: state.routing.anthropicMaxTokensPreference, + anthropic_thinking_budget_preference: state.routing.anthropicThinkingBudgetPreference, limit_5h_usd: state.rateLimit.limit5hUsd, limit_daily_usd: state.rateLimit.limitDailyUsd, daily_reset_mode: state.rateLimit.dailyResetMode, diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx index c8a187f1d..2904f8c57 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx @@ -59,6 +59,9 @@ export function createInitialState( codexTextVerbosityPreference: sourceProvider?.codexTextVerbosityPreference ?? "inherit", codexParallelToolCallsPreference: sourceProvider?.codexParallelToolCallsPreference ?? "inherit", + anthropicMaxTokensPreference: sourceProvider?.anthropicMaxTokensPreference ?? "inherit", + anthropicThinkingBudgetPreference: + sourceProvider?.anthropicThinkingBudgetPreference ?? "inherit", }, rateLimit: { limit5hUsd: sourceProvider?.limit5hUsd ?? null, @@ -168,6 +171,16 @@ export function providerFormReducer( ...state, routing: { ...state.routing, codexParallelToolCallsPreference: action.payload }, }; + case "SET_ANTHROPIC_MAX_TOKENS": + return { + ...state, + routing: { ...state.routing, anthropicMaxTokensPreference: action.payload }, + }; + case "SET_ANTHROPIC_THINKING_BUDGET": + return { + ...state, + routing: { ...state.routing, anthropicThinkingBudgetPreference: action.payload }, + }; // Rate limit case "SET_LIMIT_5H_USD": diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts index 5beb3c2c0..b58614747 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts @@ -1,5 +1,7 @@ import type { Dispatch } from "react"; import type { + AnthropicMaxTokensPreference, + AnthropicThinkingBudgetPreference, CodexParallelToolCallsPreference, CodexReasoningEffortPreference, CodexReasoningSummaryPreference, @@ -47,6 +49,9 @@ export interface RoutingState { codexReasoningSummaryPreference: CodexReasoningSummaryPreference; codexTextVerbosityPreference: CodexTextVerbosityPreference; codexParallelToolCallsPreference: CodexParallelToolCallsPreference; + // Anthropic-specific + anthropicMaxTokensPreference: AnthropicMaxTokensPreference; + anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference; } export interface RateLimitState { @@ -120,6 +125,8 @@ export type ProviderFormAction = | { type: "SET_CODEX_REASONING_SUMMARY"; payload: CodexReasoningSummaryPreference } | { type: "SET_CODEX_TEXT_VERBOSITY"; payload: CodexTextVerbosityPreference } | { type: "SET_CODEX_PARALLEL_TOOL_CALLS"; payload: CodexParallelToolCallsPreference } + | { type: "SET_ANTHROPIC_MAX_TOKENS"; payload: AnthropicMaxTokensPreference } + | { type: "SET_ANTHROPIC_THINKING_BUDGET"; payload: AnthropicThinkingBudgetPreference } // Rate limit actions | { type: "SET_LIMIT_5H_USD"; payload: number | null } | { type: "SET_LIMIT_DAILY_USD"; payload: number | null } diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx index 9ba18bff7..e5dd70e6e 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx @@ -512,6 +512,171 @@ export function RoutingSection() {
)} + + {/* Anthropic Overrides - Claude type only */} + {(state.routing.providerType === "claude" || + state.routing.providerType === "claude-auth") && ( + +
+ +
+ + {state.routing.anthropicMaxTokensPreference !== "inherit" && ( + { + const val = e.target.value; + if (val === "") { + dispatch({ type: "SET_ANTHROPIC_MAX_TOKENS", payload: "inherit" }); + } else { + dispatch({ type: "SET_ANTHROPIC_MAX_TOKENS", payload: val }); + } + }} + placeholder={t("sections.routing.anthropicOverrides.maxTokens.placeholder")} + disabled={state.ui.isPending} + min="1" + max="64000" + className="flex-1" + /> + )} +
+
+ + + + +
+ + {state.routing.anthropicThinkingBudgetPreference !== "inherit" && ( + <> + { + const val = e.target.value; + if (val === "") { + dispatch({ + type: "SET_ANTHROPIC_THINKING_BUDGET", + payload: "inherit", + }); + } else { + dispatch({ + type: "SET_ANTHROPIC_THINKING_BUDGET", + payload: val, + }); + } + }} + placeholder={t( + "sections.routing.anthropicOverrides.thinkingBudget.placeholder" + )} + disabled={state.ui.isPending} + min="1024" + max="32000" + className="flex-1" + /> + + + )} + +
+
+ +

+ {t("sections.routing.anthropicOverrides.thinkingBudget.help")} +

+
+
+
+
+
+ )} ); diff --git a/src/app/v1/_lib/proxy/forwarder.ts b/src/app/v1/_lib/proxy/forwarder.ts index 3cc83cb87..38952524e 100644 --- a/src/app/v1/_lib/proxy/forwarder.ts +++ b/src/app/v1/_lib/proxy/forwarder.ts @@ -3,6 +3,7 @@ import type { Readable } from "node:stream"; import { createGunzip, constants as zlibConstants } from "node:zlib"; import type { Dispatcher } from "undici"; import { Agent, request as undiciRequest } from "undici"; +import { applyAnthropicProviderOverridesWithAudit } from "@/lib/anthropic/provider-overrides"; import { getCircuitState, getProviderHealthInfo, @@ -53,6 +54,10 @@ import { mapClientFormatToTransformer, mapProviderTypeToTransformer } from "./fo import { ModelRedirector } from "./model-redirector"; import { ProxyProviderResolver } from "./provider-selector"; import type { ProxySession } from "./session"; +import { + detectThinkingBudgetRectifierTrigger, + rectifyThinkingBudget, +} from "./thinking-budget-rectifier"; import { detectThinkingSignatureRectifierTrigger, rectifyAnthropicRequestMessage, @@ -222,6 +227,7 @@ export class ProxyForwarder { envDefaultMaxAttempts ); let thinkingSignatureRectifierRetried = false; + let thinkingBudgetRectifierRetried = false; const requestPath = session.requestUrl.pathname; const isMcpRequest = @@ -692,6 +698,137 @@ export class ProxyForwarder { } } + // 2.6 Thinking budget rectifier: fix budget_tokens < 1024 errors and retry once + const budgetRectifierTrigger = isAnthropicProvider + ? detectThinkingBudgetRectifierTrigger(errorMessage) + : null; + + if (budgetRectifierTrigger) { + const settings = await getCachedSystemSettings(); + const budgetRectifierEnabled = settings.enableThinkingBudgetRectifier ?? true; + + if (budgetRectifierEnabled) { + if (thinkingBudgetRectifierRetried) { + errorCategory = ErrorCategory.NON_RETRYABLE_CLIENT_ERROR; + } else { + const requestDetailsBeforeRectify = buildRequestDetails(session); + + const budgetRectified = rectifyThinkingBudget( + session.request.message as Record + ); + + session.addSpecialSetting({ + type: "thinking_budget_rectifier", + scope: "request", + hit: budgetRectified.applied, + providerId: currentProvider.id, + providerName: currentProvider.name, + trigger: budgetRectifierTrigger, + attemptNumber: attemptCount, + retryAttemptNumber: attemptCount + 1, + before: budgetRectified.before, + after: budgetRectified.after, + }); + + const specialSettings = session.getSpecialSettings(); + if (specialSettings && session.sessionId) { + try { + await SessionManager.storeSessionSpecialSettings( + session.sessionId, + specialSettings, + session.requestSequence + ); + } catch (persistError) { + logger.error("[ProxyForwarder] Failed to store special settings", { + error: persistError, + sessionId: session.sessionId, + }); + } + } + + if (specialSettings && session.messageContext?.id) { + try { + await updateMessageRequestDetails(session.messageContext.id, { + specialSettings, + }); + } catch (persistError) { + logger.error("[ProxyForwarder] Failed to persist special settings", { + error: persistError, + messageRequestId: session.messageContext.id, + }); + } + } + + if (!budgetRectified.applied) { + logger.info( + "ProxyForwarder: Thinking budget rectifier not applicable, skipping retry", + { + providerId: currentProvider.id, + providerName: currentProvider.name, + trigger: budgetRectifierTrigger, + attemptNumber: attemptCount, + } + ); + errorCategory = ErrorCategory.NON_RETRYABLE_CLIENT_ERROR; + } else { + logger.info("ProxyForwarder: Thinking budget rectifier applied, retrying", { + providerId: currentProvider.id, + providerName: currentProvider.name, + trigger: budgetRectifierTrigger, + attemptNumber: attemptCount, + willRetryAttemptNumber: attemptCount + 1, + before: budgetRectified.before, + after: budgetRectified.after, + }); + + thinkingBudgetRectifierRetried = true; + + if (lastError instanceof ProxyError) { + session.addProviderToChain(currentProvider, { + ...endpointAudit, + reason: "retry_failed", + circuitState: getCircuitState(currentProvider.id), + attemptNumber: attemptCount, + errorMessage, + statusCode: lastError.statusCode, + errorDetails: { + provider: { + id: currentProvider.id, + name: currentProvider.name, + statusCode: lastError.statusCode, + statusText: lastError.message, + upstreamBody: lastError.upstreamError?.body, + upstreamParsed: lastError.upstreamError?.parsed, + }, + request: requestDetailsBeforeRectify, + }, + }); + } else { + session.addProviderToChain(currentProvider, { + ...endpointAudit, + reason: "retry_failed", + circuitState: getCircuitState(currentProvider.id), + attemptNumber: attemptCount, + errorMessage, + errorDetails: { + system: { + errorType: lastError.constructor.name, + errorName: lastError.name, + errorMessage: lastError.message || lastError.name || "Unknown error", + errorStack: lastError.stack?.split("\n").slice(0, 3).join("\n"), + }, + request: requestDetailsBeforeRectify, + }, + }); + } + + maxAttemptsPerProvider = Math.max(maxAttemptsPerProvider, attemptCount + 1); + continue; + } + } + } + } + // ⭐ 3. 不可重试的客户端输入错误处理(不计入熔断器,不重试,立即返回) if (errorCategory === ErrorCategory.NON_RETRYABLE_CLIENT_ERROR) { const proxyError = lastError as ProxyError; @@ -1338,7 +1475,7 @@ export class ProxyForwarder { } if (session.messageContext?.id) { - // 同上:确保 special_settings 的“旧值”不会在并发下覆盖“新值” + // 同上:确保 special_settings 的"旧值"不会在并发下覆盖"新值" await updateMessageRequestDetails(session.messageContext.id, { specialSettings, }).catch((err) => { @@ -1351,6 +1488,46 @@ export class ProxyForwarder { } } + // Anthropic 供应商级参数覆写(默认 inherit=遵循客户端) + // 说明:允许管理员在供应商层面强制覆写 max_tokens 和 thinking.budget_tokens + if (provider.providerType === "claude" || provider.providerType === "claude-auth") { + const { request: anthropicOverridden, audit: anthropicAudit } = + applyAnthropicProviderOverridesWithAudit( + provider, + session.request.message as Record + ); + session.request.message = anthropicOverridden; + + if (anthropicAudit) { + session.addSpecialSetting(anthropicAudit); + const specialSettings = session.getSpecialSettings(); + + if (session.sessionId) { + await SessionManager.storeSessionSpecialSettings( + session.sessionId, + specialSettings, + session.requestSequence + ).catch((err) => { + logger.error("[ProxyForwarder] Failed to store Anthropic special settings", { + error: err, + sessionId: session.sessionId, + }); + }); + } + + if (session.messageContext?.id) { + await updateMessageRequestDetails(session.messageContext.id, { + specialSettings, + }).catch((err) => { + logger.error("[ProxyForwarder] Failed to persist Anthropic special settings", { + error: err, + messageRequestId: session.messageContext?.id, + }); + }); + } + } + } + if ( resolvedCacheTtl && (provider.providerType === "claude" || provider.providerType === "claude-auth") diff --git a/src/app/v1/_lib/proxy/thinking-budget-rectifier.test.ts b/src/app/v1/_lib/proxy/thinking-budget-rectifier.test.ts new file mode 100644 index 000000000..bb7eaf358 --- /dev/null +++ b/src/app/v1/_lib/proxy/thinking-budget-rectifier.test.ts @@ -0,0 +1,209 @@ +import { describe, expect, it } from "vitest"; +import { + detectThinkingBudgetRectifierTrigger, + rectifyThinkingBudget, +} from "./thinking-budget-rectifier"; + +describe("Thinking Budget Rectifier", () => { + describe("detectThinkingBudgetRectifierTrigger", () => { + it("should return null for null/undefined input", () => { + expect(detectThinkingBudgetRectifierTrigger(null)).toBeNull(); + expect(detectThinkingBudgetRectifierTrigger(undefined)).toBeNull(); + }); + + it("should return null for empty string", () => { + expect(detectThinkingBudgetRectifierTrigger("")).toBeNull(); + }); + + it("should detect exact error message from Anthropic API", () => { + const errorMessage = + "thinking.enabled.budget_tokens: Input should be greater than or equal to 1024"; + expect(detectThinkingBudgetRectifierTrigger(errorMessage)).toBe("budget_tokens_too_low"); + }); + + it("should detect error message wrapped in JSON", () => { + const jsonError = JSON.stringify({ + error: { + type: "invalid_request_error", + message: "thinking.enabled.budget_tokens: Input should be greater than or equal to 1024", + }, + }); + expect(detectThinkingBudgetRectifierTrigger(jsonError)).toBe("budget_tokens_too_low"); + }); + + it("should detect case-insensitive variations", () => { + const upperCase = + "THINKING.ENABLED.BUDGET_TOKENS: INPUT SHOULD BE GREATER THAN OR EQUAL TO 1024"; + expect(detectThinkingBudgetRectifierTrigger(upperCase)).toBe("budget_tokens_too_low"); + }); + + it("should detect with >= 1024 format", () => { + const errorMessage = "thinking budget_tokens must be >= 1024"; + expect(detectThinkingBudgetRectifierTrigger(errorMessage)).toBe("budget_tokens_too_low"); + }); + + it("should return null for unrelated 400 errors", () => { + expect( + detectThinkingBudgetRectifierTrigger("invalid_request_error: model not found") + ).toBeNull(); + expect(detectThinkingBudgetRectifierTrigger("max_tokens must be greater than 0")).toBeNull(); + }); + + it("should return null for thinking signature errors (different rectifier)", () => { + expect( + detectThinkingBudgetRectifierTrigger("invalid signature in thinking block") + ).toBeNull(); + expect( + detectThinkingBudgetRectifierTrigger("assistant message must start with a thinking block") + ).toBeNull(); + }); + + it("should return null when only partial match (missing 1024)", () => { + expect( + detectThinkingBudgetRectifierTrigger( + "thinking.enabled.budget_tokens: Input should be greater than 0" + ) + ).toBeNull(); + }); + + it("should return null when only partial match (missing thinking)", () => { + expect( + detectThinkingBudgetRectifierTrigger( + "budget_tokens: Input should be greater than or equal to 1024" + ) + ).toBeNull(); + }); + }); + + describe("rectifyThinkingBudget", () => { + it("should set thinking.budget_tokens to 32000 when missing", () => { + const message: Record = { max_tokens: 50000 }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(result.before.thinkingBudgetTokens).toBeNull(); + expect(result.after.thinkingBudgetTokens).toBe(32000); + expect((message.thinking as Record).budget_tokens).toBe(32000); + }); + + it("should set thinking.budget_tokens to 32000 when below 1024", () => { + const message: Record = { + max_tokens: 50000, + thinking: { type: "enabled", budget_tokens: 500 }, + }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(result.before.thinkingBudgetTokens).toBe(500); + expect(result.after.thinkingBudgetTokens).toBe(32000); + }); + + it("should set thinking.type to enabled", () => { + const message: Record = { + max_tokens: 50000, + thinking: { type: "disabled", budget_tokens: 500 }, + }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(result.before.thinkingType).toBe("disabled"); + expect(result.after.thinkingType).toBe("enabled"); + expect((message.thinking as Record).type).toBe("enabled"); + }); + + it("should set max_tokens to 64000 when missing", () => { + const message: Record = {}; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(result.before.maxTokens).toBeNull(); + expect(result.after.maxTokens).toBe(64000); + expect(message.max_tokens).toBe(64000); + }); + + it("should set max_tokens to 64000 when below 32001", () => { + const message: Record = { max_tokens: 1000 }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(result.before.maxTokens).toBe(1000); + expect(result.after.maxTokens).toBe(64000); + }); + + it("should NOT change max_tokens when already >= 32001", () => { + const message: Record = { max_tokens: 50000 }; + const result = rectifyThinkingBudget(message); + + expect(result.after.maxTokens).toBe(50000); + expect(message.max_tokens).toBe(50000); + }); + + it("should handle non-object thinking value by replacing it", () => { + const message: Record = { + max_tokens: 50000, + thinking: "invalid", + }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(true); + expect(typeof message.thinking).toBe("object"); + expect((message.thinking as Record).budget_tokens).toBe(32000); + }); + + it("should preserve other thinking properties", () => { + const message: Record = { + max_tokens: 50000, + thinking: { type: "enabled", budget_tokens: 500, custom_field: "preserved" }, + }; + rectifyThinkingBudget(message); + + expect((message.thinking as Record).custom_field).toBe("preserved"); + }); + + it("should return applied=false when values already at target", () => { + const message: Record = { + max_tokens: 64000, + thinking: { type: "enabled", budget_tokens: 32000 }, + }; + const result = rectifyThinkingBudget(message); + + expect(result.applied).toBe(false); + expect(result.before).toEqual(result.after); + }); + + it("should handle edge case: max_tokens exactly 32001", () => { + const message: Record = { max_tokens: 32001 }; + const result = rectifyThinkingBudget(message); + + expect(result.after.maxTokens).toBe(32001); + expect(message.max_tokens).toBe(32001); + }); + + it("should handle edge case: max_tokens exactly 32000 (needs upgrade)", () => { + const message: Record = { max_tokens: 32000 }; + const result = rectifyThinkingBudget(message); + + expect(result.after.maxTokens).toBe(64000); + expect(message.max_tokens).toBe(64000); + }); + + it("should track before/after values correctly for audit", () => { + const message: Record = { + max_tokens: 1000, + thinking: { type: "disabled", budget_tokens: 100 }, + }; + const result = rectifyThinkingBudget(message); + + expect(result.before).toEqual({ + maxTokens: 1000, + thinkingType: "disabled", + thinkingBudgetTokens: 100, + }); + expect(result.after).toEqual({ + maxTokens: 64000, + thinkingType: "enabled", + thinkingBudgetTokens: 32000, + }); + }); + }); +}); diff --git a/src/app/v1/_lib/proxy/thinking-budget-rectifier.ts b/src/app/v1/_lib/proxy/thinking-budget-rectifier.ts new file mode 100644 index 000000000..7a0a34873 --- /dev/null +++ b/src/app/v1/_lib/proxy/thinking-budget-rectifier.ts @@ -0,0 +1,101 @@ +/** + * Thinking Budget Rectifier - Reactive rectifier for Anthropic API budget_tokens < 1024 errors. + * Trigger: "thinking.enabled.budget_tokens: Input should be greater than or equal to 1024" + * Action: Set thinking.budget_tokens=32000, thinking.type="enabled", max_tokens=64000 (if needed) + */ + +export type ThinkingBudgetRectifierTrigger = "budget_tokens_too_low"; + +export type ThinkingBudgetRectifierResult = { + applied: boolean; + before: { + maxTokens: number | null; + thinkingType: string | null; + thinkingBudgetTokens: number | null; + }; + after: { + maxTokens: number | null; + thinkingType: string | null; + thinkingBudgetTokens: number | null; + }; +}; + +const MAX_THINKING_BUDGET = 32000; +const MAX_TOKENS_VALUE = 64000; +const MIN_MAX_TOKENS_FOR_BUDGET = MAX_THINKING_BUDGET + 1; + +/** + * Detect if error message indicates thinking budget validation failure. + * Does NOT rely on error rules - only string matching. + */ +export function detectThinkingBudgetRectifierTrigger( + errorMessage: string | null | undefined +): ThinkingBudgetRectifierTrigger | null { + if (!errorMessage) return null; + + const lower = errorMessage.toLowerCase(); + + const hasBudgetTokensReference = + lower.includes("budget_tokens") || lower.includes("budget tokens"); + const hasThinkingReference = lower.includes("thinking"); + const has1024Constraint = + lower.includes("greater than or equal to 1024") || + lower.includes(">= 1024") || + (lower.includes("1024") && lower.includes("input should be")); + + if (hasBudgetTokensReference && hasThinkingReference && has1024Constraint) { + return "budget_tokens_too_low"; + } + + return null; +} + +/** + * Rectify request body by setting thinking budget and max_tokens to maximum values. + * Modifies message object in place. + */ +export function rectifyThinkingBudget( + message: Record +): ThinkingBudgetRectifierResult { + const currentMaxTokens = typeof message.max_tokens === "number" ? message.max_tokens : null; + + const thinking = message.thinking as Record | undefined; + const currentThinkingType = thinking && typeof thinking.type === "string" ? thinking.type : null; + const currentThinkingBudgetTokens = + thinking && typeof thinking.budget_tokens === "number" ? thinking.budget_tokens : null; + + const before = { + maxTokens: currentMaxTokens, + thinkingType: currentThinkingType, + thinkingBudgetTokens: currentThinkingBudgetTokens, + }; + + if (!message.thinking || typeof message.thinking !== "object") { + message.thinking = {}; + } + + const thinkingObj = message.thinking as Record; + thinkingObj.type = "enabled"; + thinkingObj.budget_tokens = MAX_THINKING_BUDGET; + + if (currentMaxTokens === null || currentMaxTokens < MIN_MAX_TOKENS_FOR_BUDGET) { + message.max_tokens = MAX_TOKENS_VALUE; + } + + const afterMaxTokens = typeof message.max_tokens === "number" ? message.max_tokens : null; + const afterThinking = message.thinking as Record; + + const after = { + maxTokens: afterMaxTokens, + thinkingType: typeof afterThinking.type === "string" ? afterThinking.type : null, + thinkingBudgetTokens: + typeof afterThinking.budget_tokens === "number" ? afterThinking.budget_tokens : null, + }; + + const applied = + before.maxTokens !== after.maxTokens || + before.thinkingType !== after.thinkingType || + before.thinkingBudgetTokens !== after.thinkingBudgetTokens; + + return { applied, before, after }; +} diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index de3fe4b95..800f1a288 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -276,6 +276,12 @@ export const providers = pgTable('providers', { codexTextVerbosityPreference: varchar('codex_text_verbosity_preference', { length: 10 }), codexParallelToolCallsPreference: varchar('codex_parallel_tool_calls_preference', { length: 10 }), + // Anthropic (Messages API) parameter overrides (only for claude/claude-auth providers) + // - 'inherit' or null: follow client request + // - numeric string: force override to that value + anthropicMaxTokensPreference: varchar('anthropic_max_tokens_preference', { length: 20 }), + anthropicThinkingBudgetPreference: varchar('anthropic_thinking_budget_preference', { length: 20 }), + // 废弃(保留向后兼容,但不再使用) tpm: integer('tpm').default(0), rpm: integer('rpm').default(0), @@ -595,6 +601,12 @@ export const systemSettings = pgTable('system_settings', { .notNull() .default(true), + // thinking budget 整流器(默认开启) + // 开启后:当 Anthropic 类型供应商出现 budget_tokens < 1024 错误时,自动整流并重试一次 + enableThinkingBudgetRectifier: boolean('enable_thinking_budget_rectifier') + .notNull() + .default(true), + // Codex Session ID 补全(默认开启) // 开启后:当 Codex 请求缺少 session_id / prompt_cache_key 时,自动补全或生成稳定的会话标识 enableCodexSessionIdCompletion: boolean('enable_codex_session_id_completion') diff --git a/src/lib/anthropic/provider-overrides.ts b/src/lib/anthropic/provider-overrides.ts new file mode 100644 index 000000000..4aeeec4d5 --- /dev/null +++ b/src/lib/anthropic/provider-overrides.ts @@ -0,0 +1,150 @@ +import type { ProviderParameterOverrideSpecialSetting } from "@/types/special-settings"; + +type AnthropicProviderOverrideConfig = { + id?: number; + name?: string; + providerType?: string; + anthropicMaxTokensPreference?: string | null; + anthropicThinkingBudgetPreference?: string | null; +}; + +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 normalizeNumericPreference(value: string | null | undefined): number | null { + if (!value || value === "inherit") return null; + const parsed = Number.parseInt(value, 10); + if (Number.isNaN(parsed)) return null; + return parsed; +} + +/** + * Apply Anthropic provider overrides to request body. + * + * Conventions: + * - providerType !== "claude" && providerType !== "claude-auth" -> no processing + * - Preference value null/undefined/"inherit" means "follow client" + * - Overrides only affect: + * - max_tokens + * - thinking.type / thinking.budget_tokens + */ +export function applyAnthropicProviderOverrides( + provider: AnthropicProviderOverrideConfig, + request: Record +): Record { + if (provider.providerType !== "claude" && provider.providerType !== "claude-auth") { + return request; + } + + let output: Record = request; + const ensureCloned = () => { + if (output === request) { + output = { ...request }; + } + }; + + const maxTokens = normalizeNumericPreference(provider.anthropicMaxTokensPreference); + if (maxTokens !== null) { + ensureCloned(); + output.max_tokens = maxTokens; + } + + const thinkingBudget = normalizeNumericPreference(provider.anthropicThinkingBudgetPreference); + if (thinkingBudget !== null) { + ensureCloned(); + const existingThinking = isPlainObject(output.thinking) ? output.thinking : {}; + let budgetTokens = thinkingBudget; + const currentMaxTokens = typeof output.max_tokens === "number" ? output.max_tokens : null; + // Anthropic API requires budget_tokens >= 1024 + const MIN_BUDGET_TOKENS = 1024; + if (currentMaxTokens !== null && budgetTokens >= currentMaxTokens) { + budgetTokens = currentMaxTokens - 1; + } + // If clamping would result in budget_tokens < 1024, skip thinking override entirely + // to avoid invalid API requests + if (budgetTokens < MIN_BUDGET_TOKENS) { + return output; + } + const nextThinking: Record = { + ...existingThinking, + type: "enabled", + budget_tokens: budgetTokens, + }; + output.thinking = nextThinking; + } + + return output; +} + +export function applyAnthropicProviderOverridesWithAudit( + provider: AnthropicProviderOverrideConfig, + request: Record +): { request: Record; audit: ProviderParameterOverrideSpecialSetting | null } { + if (provider.providerType !== "claude" && provider.providerType !== "claude-auth") { + return { request, audit: null }; + } + + const maxTokens = normalizeNumericPreference(provider.anthropicMaxTokensPreference); + const thinkingBudget = normalizeNumericPreference(provider.anthropicThinkingBudgetPreference); + + const hit = maxTokens !== null || thinkingBudget !== null; + + if (!hit) { + return { request, audit: null }; + } + + const beforeMaxTokens = toAuditValue(request.max_tokens); + const beforeThinking = isPlainObject(request.thinking) ? request.thinking : null; + const beforeThinkingType = toAuditValue(beforeThinking?.type); + const beforeThinkingBudgetTokens = toAuditValue(beforeThinking?.budget_tokens); + + const nextRequest = applyAnthropicProviderOverrides(provider, request); + + const afterMaxTokens = toAuditValue(nextRequest.max_tokens); + const afterThinking = isPlainObject(nextRequest.thinking) ? nextRequest.thinking : null; + const afterThinkingType = toAuditValue(afterThinking?.type); + const afterThinkingBudgetTokens = toAuditValue(afterThinking?.budget_tokens); + + const changes: ProviderParameterOverrideSpecialSetting["changes"] = [ + { + path: "max_tokens", + before: beforeMaxTokens, + after: afterMaxTokens, + changed: !Object.is(beforeMaxTokens, afterMaxTokens), + }, + { + path: "thinking.type", + before: beforeThinkingType, + after: afterThinkingType, + changed: !Object.is(beforeThinkingType, afterThinkingType), + }, + { + path: "thinking.budget_tokens", + before: beforeThinkingBudgetTokens, + after: afterThinkingBudgetTokens, + changed: !Object.is(beforeThinkingBudgetTokens, afterThinkingBudgetTokens), + }, + ]; + + 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/config/system-settings-cache.ts b/src/lib/config/system-settings-cache.ts index 9749ab81b..3643f7caa 100644 --- a/src/lib/config/system-settings-cache.ts +++ b/src/lib/config/system-settings-cache.ts @@ -29,6 +29,7 @@ const DEFAULT_SETTINGS: Pick< | "enableHttp2" | "interceptAnthropicWarmupRequests" | "enableThinkingSignatureRectifier" + | "enableThinkingBudgetRectifier" | "enableCodexSessionIdCompletion" | "enableResponseFixer" | "responseFixerConfig" @@ -36,6 +37,7 @@ const DEFAULT_SETTINGS: Pick< enableHttp2: false, interceptAnthropicWarmupRequests: false, enableThinkingSignatureRectifier: true, + enableThinkingBudgetRectifier: true, enableCodexSessionIdCompletion: true, enableResponseFixer: true, responseFixerConfig: { @@ -106,6 +108,7 @@ export async function getCachedSystemSettings(): Promise { enableHttp2: DEFAULT_SETTINGS.enableHttp2, interceptAnthropicWarmupRequests: DEFAULT_SETTINGS.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: DEFAULT_SETTINGS.enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier: DEFAULT_SETTINGS.enableThinkingBudgetRectifier, enableCodexSessionIdCompletion: DEFAULT_SETTINGS.enableCodexSessionIdCompletion, enableResponseFixer: DEFAULT_SETTINGS.enableResponseFixer, responseFixerConfig: DEFAULT_SETTINGS.responseFixerConfig, diff --git a/src/lib/utils/special-settings.ts b/src/lib/utils/special-settings.ts index 74803da9a..322df538c 100644 --- a/src/lib/utils/special-settings.ts +++ b/src/lib/utils/special-settings.ts @@ -75,6 +75,19 @@ function buildSettingKey(setting: SpecialSetting): string { setting.source, setting.sessionId, ]); + case "thinking_budget_rectifier": + return JSON.stringify([ + setting.type, + setting.hit, + setting.providerId ?? null, + setting.trigger, + setting.attemptNumber, + setting.retryAttemptNumber, + setting.before.maxTokens, + setting.before.thinkingBudgetTokens, + setting.after.maxTokens, + setting.after.thinkingBudgetTokens, + ]); default: { // 兜底:保证即使未来扩展类型也不会导致运行时崩溃 const _exhaustive: never = setting; diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index 4c5b4d35b..39803b4b6 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -25,6 +25,36 @@ const CODEX_REASONING_SUMMARY_PREFERENCE = z.enum(["inherit", "auto", "detailed" const CODEX_TEXT_VERBOSITY_PREFERENCE = z.enum(["inherit", "low", "medium", "high"]); const CODEX_PARALLEL_TOOL_CALLS_PREFERENCE = z.enum(["inherit", "true", "false"]); +// Anthropic preference schemas for max_tokens and thinking.budget_tokens +// Values stored as JSON string: "inherit" or numeric string like "32000" +const ANTHROPIC_MAX_TOKENS_PREFERENCE = z.union([ + z.literal("inherit"), + z + .string() + .regex(/^\d+$/, "max_tokens must be 'inherit' or a numeric string") + .refine( + (val) => { + const num = Number.parseInt(val, 10); + return num >= 1 && num <= 64000; + }, + { message: "max_tokens must be between 1 and 64000" } + ), +]); + +const ANTHROPIC_THINKING_BUDGET_PREFERENCE = z.union([ + z.literal("inherit"), + z + .string() + .regex(/^\d+$/, "thinking.budget_tokens must be 'inherit' or a numeric string") + .refine( + (val) => { + const num = Number.parseInt(val, 10); + return num >= 1024 && num <= 32000; + }, + { message: "thinking.budget_tokens must be between 1024 and 32000" } + ), +]); + /** * 用户创建数据验证schema */ @@ -352,184 +382,206 @@ export const KeyFormSchema = z.object({ /** * 服务商创建数据验证schema */ -export const CreateProviderSchema = z.object({ - name: z.string().min(1, "服务商名称不能为空").max(64, "服务商名称不能超过64个字符"), - url: z.string().url("请输入有效的URL地址").max(255, "URL长度不能超过255个字符"), - key: z.string().min(1, "API密钥不能为空").max(1024, "API密钥长度不能超过1024个字符"), - // 数据库字段命名:下划线 - is_enabled: z.boolean().optional().default(PROVIDER_DEFAULTS.IS_ENABLED), - weight: z - .number() - .int("权重必须是整数") - .min(PROVIDER_LIMITS.WEIGHT.MIN, "权重不能小于 1") - .max(PROVIDER_LIMITS.WEIGHT.MAX, "权重不能超过 100") - .optional() - .default(PROVIDER_DEFAULTS.WEIGHT), - priority: z - .number() - .int("优先级必须是整数") - .min(0, "优先级不能为负数") - .max(2147483647, "优先级超出整数范围") - .optional() - .default(0), - cost_multiplier: z.coerce.number().min(0, "成本倍率不能为负数").optional().default(1.0), - group_tag: z.string().max(50, "分组标签不能超过50个字符").nullable().optional(), - // Codex 支持:供应商类型和模型重定向 - provider_type: z - .enum(["claude", "claude-auth", "codex", "gemini", "gemini-cli", "openai-compatible"]) - .optional() - .default("claude"), - preserve_client_ip: z.boolean().optional().default(false), - model_redirects: z.record(z.string(), z.string()).nullable().optional(), - allowed_models: z.array(z.string()).nullable().optional(), - join_claude_pool: z.boolean().optional().default(false), - // MCP 透传配置 - mcp_passthrough_type: z.enum(["none", "minimax", "glm", "custom"]).optional().default("none"), - mcp_passthrough_url: z - .string() - .max(512, "MCP透传URL长度不能超过512个字符") - .url("请输入有效的URL地址") - .nullable() - .optional(), - // 金额限流配置 - limit_5h_usd: z.coerce - .number() - .min(0, "5小时消费上限不能为负数") - .max(10000, "5小时消费上限不能超过10000美元") - .nullable() - .optional(), - limit_daily_usd: z.coerce - .number() - .min(0, "每日消费上限不能为负数") - .max(10000, "每日消费上限不能超过10000美元") - .nullable() - .optional(), - daily_reset_mode: z.enum(["fixed", "rolling"]).optional().default("fixed"), - daily_reset_time: z - .string() - .regex(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/, "重置时间格式必须为 HH:mm") - .optional() - .default("00:00"), - limit_weekly_usd: z.coerce - .number() - .min(0, "周消费上限不能为负数") - .max(50000, "周消费上限不能超过50000美元") - .nullable() - .optional(), - limit_monthly_usd: z.coerce - .number() - .min(0, "月消费上限不能为负数") - .max(200000, "月消费上限不能超过200000美元") - .nullable() - .optional(), - limit_total_usd: z.coerce - .number() - .min(0, "总消费上限不能为负数") - .max(10000000, "总消费上限不能超过10000000美元") - .nullable() - .optional(), - limit_concurrent_sessions: z.coerce - .number() - .int("并发Session上限必须是整数") - .min(0, "并发Session上限不能为负数") - .max(1000, "并发Session上限不能超过1000") - .optional() - .default(0), - cache_ttl_preference: CACHE_TTL_PREFERENCE.optional().default("inherit"), - context_1m_preference: CONTEXT_1M_PREFERENCE.nullable().optional(), - codex_reasoning_effort_preference: - CODEX_REASONING_EFFORT_PREFERENCE.optional().default("inherit"), - codex_reasoning_summary_preference: - CODEX_REASONING_SUMMARY_PREFERENCE.optional().default("inherit"), - codex_text_verbosity_preference: CODEX_TEXT_VERBOSITY_PREFERENCE.optional().default("inherit"), - codex_parallel_tool_calls_preference: - CODEX_PARALLEL_TOOL_CALLS_PREFERENCE.optional().default("inherit"), - max_retry_attempts: z.coerce - .number() - .int("重试次数必须是整数") - .min(PROVIDER_LIMITS.MAX_RETRY_ATTEMPTS.MIN, "重试次数不能少于1次") - .max(PROVIDER_LIMITS.MAX_RETRY_ATTEMPTS.MAX, "重试次数不能超过10次") - .nullable() - .optional(), - // 熔断器配置 - circuit_breaker_failure_threshold: z.coerce - .number() - .int("失败阈值必须是整数") - .min(0, "失败阈值不能为负数") - .optional(), - circuit_breaker_open_duration: z.coerce - .number() - .int("熔断时长必须是整数") - .min(1000, "熔断时长不能少于1秒") - .max(86400000, "熔断时长不能超过24小时") - .optional(), - circuit_breaker_half_open_success_threshold: z.coerce - .number() - .int("恢复阈值必须是整数") - .min(1, "恢复阈值不能少于1次") - .max(10, "恢复阈值不能超过10次") - .optional(), - // 代理配置 - proxy_url: z.string().max(512, "代理地址长度不能超过512个字符").nullable().optional(), - proxy_fallback_to_direct: z.boolean().optional().default(false), - // 超时配置(毫秒) - // 注意:0 表示禁用超时(Infinity) - first_byte_timeout_streaming_ms: z - .union([ - z.literal(0), // 0 = 禁用超时 - z.coerce - .number() - .int("流式首字节超时必须是整数") - .min( - PROVIDER_TIMEOUT_LIMITS.FIRST_BYTE_TIMEOUT_STREAMING_MS.MIN, - "流式首字节超时不能少于1秒" - ) - .max( - PROVIDER_TIMEOUT_LIMITS.FIRST_BYTE_TIMEOUT_STREAMING_MS.MAX, - "流式首字节超时不能超过180秒" - ), - ]) - .optional(), - streaming_idle_timeout_ms: z - .union([ - z.literal(0), // 0 = 禁用超时 - z.coerce - .number() - .int("流式静默期超时必须是整数") - .min(PROVIDER_TIMEOUT_LIMITS.STREAMING_IDLE_TIMEOUT_MS.MIN, "流式静默期超时不能少于60秒") - .max(PROVIDER_TIMEOUT_LIMITS.STREAMING_IDLE_TIMEOUT_MS.MAX, "流式静默期超时不能超过600秒"), - ]) - .optional(), - request_timeout_non_streaming_ms: z - .union([ - z.literal(0), // 0 = 禁用超时 - z.coerce - .number() - .int("非流式总超时必须是整数") - .min( - PROVIDER_TIMEOUT_LIMITS.REQUEST_TIMEOUT_NON_STREAMING_MS.MIN, - "非流式总超时不能少于60秒" - ) - .max( - PROVIDER_TIMEOUT_LIMITS.REQUEST_TIMEOUT_NON_STREAMING_MS.MAX, - "非流式总超时不能超过1800秒" - ), - ]) - .optional(), - // 供应商官网地址 - website_url: z - .string() - .url("请输入有效的URL地址") - .max(512, "URL长度不能超过512个字符") - .nullable() - .optional(), - favicon_url: z.string().max(512, "Favicon URL长度不能超过512个字符").nullable().optional(), - // 废弃字段(保留向后兼容,不再验证范围) - tpm: z.number().int().nullable().optional(), - rpm: z.number().int().nullable().optional(), - rpd: z.number().int().nullable().optional(), - cc: z.number().int().nullable().optional(), -}); +export const CreateProviderSchema = z + .object({ + name: z.string().min(1, "服务商名称不能为空").max(64, "服务商名称不能超过64个字符"), + url: z.string().url("请输入有效的URL地址").max(255, "URL长度不能超过255个字符"), + key: z.string().min(1, "API密钥不能为空").max(1024, "API密钥长度不能超过1024个字符"), + // 数据库字段命名:下划线 + is_enabled: z.boolean().optional().default(PROVIDER_DEFAULTS.IS_ENABLED), + weight: z + .number() + .int("权重必须是整数") + .min(PROVIDER_LIMITS.WEIGHT.MIN, "权重不能小于 1") + .max(PROVIDER_LIMITS.WEIGHT.MAX, "权重不能超过 100") + .optional() + .default(PROVIDER_DEFAULTS.WEIGHT), + priority: z + .number() + .int("优先级必须是整数") + .min(0, "优先级不能为负数") + .max(2147483647, "优先级超出整数范围") + .optional() + .default(0), + cost_multiplier: z.coerce.number().min(0, "成本倍率不能为负数").optional().default(1.0), + group_tag: z.string().max(50, "分组标签不能超过50个字符").nullable().optional(), + // Codex 支持:供应商类型和模型重定向 + provider_type: z + .enum(["claude", "claude-auth", "codex", "gemini", "gemini-cli", "openai-compatible"]) + .optional() + .default("claude"), + preserve_client_ip: z.boolean().optional().default(false), + model_redirects: z.record(z.string(), z.string()).nullable().optional(), + allowed_models: z.array(z.string()).nullable().optional(), + join_claude_pool: z.boolean().optional().default(false), + // MCP 透传配置 + mcp_passthrough_type: z.enum(["none", "minimax", "glm", "custom"]).optional().default("none"), + mcp_passthrough_url: z + .string() + .max(512, "MCP透传URL长度不能超过512个字符") + .url("请输入有效的URL地址") + .nullable() + .optional(), + // 金额限流配置 + limit_5h_usd: z.coerce + .number() + .min(0, "5小时消费上限不能为负数") + .max(10000, "5小时消费上限不能超过10000美元") + .nullable() + .optional(), + limit_daily_usd: z.coerce + .number() + .min(0, "每日消费上限不能为负数") + .max(10000, "每日消费上限不能超过10000美元") + .nullable() + .optional(), + daily_reset_mode: z.enum(["fixed", "rolling"]).optional().default("fixed"), + daily_reset_time: z + .string() + .regex(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/, "重置时间格式必须为 HH:mm") + .optional() + .default("00:00"), + limit_weekly_usd: z.coerce + .number() + .min(0, "周消费上限不能为负数") + .max(50000, "周消费上限不能超过50000美元") + .nullable() + .optional(), + limit_monthly_usd: z.coerce + .number() + .min(0, "月消费上限不能为负数") + .max(200000, "月消费上限不能超过200000美元") + .nullable() + .optional(), + limit_total_usd: z.coerce + .number() + .min(0, "总消费上限不能为负数") + .max(10000000, "总消费上限不能超过10000000美元") + .nullable() + .optional(), + limit_concurrent_sessions: z.coerce + .number() + .int("并发Session上限必须是整数") + .min(0, "并发Session上限不能为负数") + .max(1000, "并发Session上限不能超过1000") + .optional() + .default(0), + cache_ttl_preference: CACHE_TTL_PREFERENCE.optional().default("inherit"), + context_1m_preference: CONTEXT_1M_PREFERENCE.nullable().optional(), + codex_reasoning_effort_preference: + CODEX_REASONING_EFFORT_PREFERENCE.optional().default("inherit"), + codex_reasoning_summary_preference: + CODEX_REASONING_SUMMARY_PREFERENCE.optional().default("inherit"), + codex_text_verbosity_preference: CODEX_TEXT_VERBOSITY_PREFERENCE.optional().default("inherit"), + codex_parallel_tool_calls_preference: + CODEX_PARALLEL_TOOL_CALLS_PREFERENCE.optional().default("inherit"), + anthropic_max_tokens_preference: ANTHROPIC_MAX_TOKENS_PREFERENCE.optional().default("inherit"), + anthropic_thinking_budget_preference: + ANTHROPIC_THINKING_BUDGET_PREFERENCE.optional().default("inherit"), + max_retry_attempts: z.coerce + .number() + .int("重试次数必须是整数") + .min(PROVIDER_LIMITS.MAX_RETRY_ATTEMPTS.MIN, "重试次数不能少于1次") + .max(PROVIDER_LIMITS.MAX_RETRY_ATTEMPTS.MAX, "重试次数不能超过10次") + .nullable() + .optional(), + // 熔断器配置 + circuit_breaker_failure_threshold: z.coerce + .number() + .int("失败阈值必须是整数") + .min(0, "失败阈值不能为负数") + .optional(), + circuit_breaker_open_duration: z.coerce + .number() + .int("熔断时长必须是整数") + .min(1000, "熔断时长不能少于1秒") + .max(86400000, "熔断时长不能超过24小时") + .optional(), + circuit_breaker_half_open_success_threshold: z.coerce + .number() + .int("恢复阈值必须是整数") + .min(1, "恢复阈值不能少于1次") + .max(10, "恢复阈值不能超过10次") + .optional(), + // 代理配置 + proxy_url: z.string().max(512, "代理地址长度不能超过512个字符").nullable().optional(), + proxy_fallback_to_direct: z.boolean().optional().default(false), + // 超时配置(毫秒) + // 注意:0 表示禁用超时(Infinity) + first_byte_timeout_streaming_ms: z + .union([ + z.literal(0), // 0 = 禁用超时 + z.coerce + .number() + .int("流式首字节超时必须是整数") + .min( + PROVIDER_TIMEOUT_LIMITS.FIRST_BYTE_TIMEOUT_STREAMING_MS.MIN, + "流式首字节超时不能少于1秒" + ) + .max( + PROVIDER_TIMEOUT_LIMITS.FIRST_BYTE_TIMEOUT_STREAMING_MS.MAX, + "流式首字节超时不能超过180秒" + ), + ]) + .optional(), + streaming_idle_timeout_ms: z + .union([ + z.literal(0), // 0 = 禁用超时 + z.coerce + .number() + .int("流式静默期超时必须是整数") + .min(PROVIDER_TIMEOUT_LIMITS.STREAMING_IDLE_TIMEOUT_MS.MIN, "流式静默期超时不能少于60秒") + .max( + PROVIDER_TIMEOUT_LIMITS.STREAMING_IDLE_TIMEOUT_MS.MAX, + "流式静默期超时不能超过600秒" + ), + ]) + .optional(), + request_timeout_non_streaming_ms: z + .union([ + z.literal(0), // 0 = 禁用超时 + z.coerce + .number() + .int("非流式总超时必须是整数") + .min( + PROVIDER_TIMEOUT_LIMITS.REQUEST_TIMEOUT_NON_STREAMING_MS.MIN, + "非流式总超时不能少于60秒" + ) + .max( + PROVIDER_TIMEOUT_LIMITS.REQUEST_TIMEOUT_NON_STREAMING_MS.MAX, + "非流式总超时不能超过1800秒" + ), + ]) + .optional(), + // 供应商官网地址 + website_url: z + .string() + .url("请输入有效的URL地址") + .max(512, "URL长度不能超过512个字符") + .nullable() + .optional(), + favicon_url: z.string().max(512, "Favicon URL长度不能超过512个字符").nullable().optional(), + // 废弃字段(保留向后兼容,不再验证范围) + tpm: z.number().int().nullable().optional(), + rpm: z.number().int().nullable().optional(), + rpd: z.number().int().nullable().optional(), + cc: z.number().int().nullable().optional(), + }) + .superRefine((data, ctx) => { + const maxTokens = data.anthropic_max_tokens_preference; + const budget = data.anthropic_thinking_budget_preference; + if (maxTokens && maxTokens !== "inherit" && budget && budget !== "inherit") { + const maxTokensNum = Number.parseInt(maxTokens, 10); + const budgetNum = Number.parseInt(budget, 10); + if (budgetNum >= maxTokensNum) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "thinking.budget_tokens must be less than max_tokens", + path: ["anthropic_thinking_budget_preference"], + }); + } + } + }); /** * 服务商更新数据验证schema @@ -618,6 +670,8 @@ export const UpdateProviderSchema = z codex_reasoning_summary_preference: CODEX_REASONING_SUMMARY_PREFERENCE.optional(), codex_text_verbosity_preference: CODEX_TEXT_VERBOSITY_PREFERENCE.optional(), codex_parallel_tool_calls_preference: CODEX_PARALLEL_TOOL_CALLS_PREFERENCE.optional(), + anthropic_max_tokens_preference: ANTHROPIC_MAX_TOKENS_PREFERENCE.optional(), + anthropic_thinking_budget_preference: ANTHROPIC_THINKING_BUDGET_PREFERENCE.optional(), max_retry_attempts: z.coerce .number() .int("重试次数必须是整数") @@ -707,7 +761,22 @@ export const UpdateProviderSchema = z rpd: z.number().int().nullable().optional(), cc: z.number().int().nullable().optional(), }) - .refine((obj) => Object.keys(obj).length > 0, { message: "更新内容为空" }); + .refine((obj) => Object.keys(obj).length > 0, { message: "更新内容为空" }) + .superRefine((data, ctx) => { + const maxTokens = data.anthropic_max_tokens_preference; + const budget = data.anthropic_thinking_budget_preference; + if (maxTokens && maxTokens !== "inherit" && budget && budget !== "inherit") { + const maxTokensNum = Number.parseInt(maxTokens, 10); + const budgetNum = Number.parseInt(budget, 10); + if (budgetNum >= maxTokensNum) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "thinking.budget_tokens must be less than max_tokens", + path: ["anthropic_thinking_budget_preference"], + }); + } + } + }); /** * 系统设置更新数据验证schema @@ -763,6 +832,8 @@ export const UpdateSystemSettingsSchema = z.object({ interceptAnthropicWarmupRequests: z.boolean().optional(), // thinking signature 整流器(可选) enableThinkingSignatureRectifier: z.boolean().optional(), + // thinking budget 整流器(可选) + enableThinkingBudgetRectifier: z.boolean().optional(), // Codex Session ID 补全(可选) enableCodexSessionIdCompletion: z.boolean().optional(), // 响应整流(可选) @@ -814,3 +885,6 @@ export const UpdateSystemSettingsSchema = z.object({ }); // 导出类型推断 + +export const anthropicMaxTokensPreferenceSchema = ANTHROPIC_MAX_TOKENS_PREFERENCE; +export const anthropicThinkingBudgetPreferenceSchema = ANTHROPIC_THINKING_BUDGET_PREFERENCE; diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index c1b3f38b3..235e372c5 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -124,6 +124,8 @@ export function toProvider(dbProvider: any): Provider { codexReasoningSummaryPreference: dbProvider?.codexReasoningSummaryPreference ?? null, codexTextVerbosityPreference: dbProvider?.codexTextVerbosityPreference ?? null, codexParallelToolCallsPreference: dbProvider?.codexParallelToolCallsPreference ?? null, + anthropicMaxTokensPreference: dbProvider?.anthropicMaxTokensPreference ?? null, + anthropicThinkingBudgetPreference: dbProvider?.anthropicThinkingBudgetPreference ?? null, tpm: dbProvider?.tpm ?? null, rpm: dbProvider?.rpm ?? null, rpd: dbProvider?.rpd ?? null, @@ -189,6 +191,7 @@ export function toSystemSettings(dbSettings: any): SystemSettings { enableHttp2: dbSettings?.enableHttp2 ?? false, interceptAnthropicWarmupRequests: dbSettings?.interceptAnthropicWarmupRequests ?? false, enableThinkingSignatureRectifier: dbSettings?.enableThinkingSignatureRectifier ?? true, + enableThinkingBudgetRectifier: dbSettings?.enableThinkingBudgetRectifier ?? true, enableCodexSessionIdCompletion: dbSettings?.enableCodexSessionIdCompletion ?? true, enableResponseFixer: dbSettings?.enableResponseFixer ?? true, responseFixerConfig: { diff --git a/src/repository/provider.ts b/src/repository/provider.ts index 50f3a2710..35850e9d0 100644 --- a/src/repository/provider.ts +++ b/src/repository/provider.ts @@ -71,6 +71,8 @@ export async function createProvider(providerData: CreateProviderData): Promise< codexReasoningSummaryPreference: providerData.codex_reasoning_summary_preference ?? null, codexTextVerbosityPreference: providerData.codex_text_verbosity_preference ?? null, codexParallelToolCallsPreference: providerData.codex_parallel_tool_calls_preference ?? null, + anthropicMaxTokensPreference: providerData.anthropic_max_tokens_preference ?? null, + anthropicThinkingBudgetPreference: providerData.anthropic_thinking_budget_preference ?? null, tpm: providerData.tpm, rpm: providerData.rpm, rpd: providerData.rpd, @@ -122,6 +124,8 @@ export async function createProvider(providerData: CreateProviderData): Promise< codexReasoningSummaryPreference: providers.codexReasoningSummaryPreference, codexTextVerbosityPreference: providers.codexTextVerbosityPreference, codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -202,6 +206,8 @@ export async function findProviderList( codexReasoningSummaryPreference: providers.codexReasoningSummaryPreference, codexTextVerbosityPreference: providers.codexTextVerbosityPreference, codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -278,6 +284,8 @@ export async function findAllProvidersFresh(): Promise { codexReasoningSummaryPreference: providers.codexReasoningSummaryPreference, codexTextVerbosityPreference: providers.codexTextVerbosityPreference, codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -358,6 +366,8 @@ export async function findProviderById(id: number): Promise { codexReasoningSummaryPreference: providers.codexReasoningSummaryPreference, codexTextVerbosityPreference: providers.codexTextVerbosityPreference, codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -465,6 +475,11 @@ export async function updateProvider( if (providerData.codex_parallel_tool_calls_preference !== undefined) dbData.codexParallelToolCallsPreference = providerData.codex_parallel_tool_calls_preference ?? null; + if (providerData.anthropic_max_tokens_preference !== undefined) + dbData.anthropicMaxTokensPreference = providerData.anthropic_max_tokens_preference ?? null; + if (providerData.anthropic_thinking_budget_preference !== undefined) + dbData.anthropicThinkingBudgetPreference = + providerData.anthropic_thinking_budget_preference ?? null; if (providerData.tpm !== undefined) dbData.tpm = providerData.tpm; if (providerData.rpm !== undefined) dbData.rpm = providerData.rpm; if (providerData.rpd !== undefined) dbData.rpd = providerData.rpd; @@ -545,6 +560,8 @@ export async function updateProvider( codexReasoningSummaryPreference: providers.codexReasoningSummaryPreference, codexTextVerbosityPreference: providers.codexTextVerbosityPreference, codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, + anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, + anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, diff --git a/src/repository/system-config.ts b/src/repository/system-config.ts index 58ddc5f0f..3b46cec03 100644 --- a/src/repository/system-config.ts +++ b/src/repository/system-config.ts @@ -150,6 +150,7 @@ function createFallbackSettings(): SystemSettings { enableHttp2: false, interceptAnthropicWarmupRequests: false, enableThinkingSignatureRectifier: true, + enableThinkingBudgetRectifier: true, enableCodexSessionIdCompletion: true, enableResponseFixer: true, responseFixerConfig: { @@ -191,6 +192,7 @@ export async function getSystemSettings(): Promise { enableHttp2: systemSettings.enableHttp2, interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, enableCodexSessionIdCompletion: systemSettings.enableCodexSessionIdCompletion, enableResponseFixer: systemSettings.enableResponseFixer, responseFixerConfig: systemSettings.responseFixerConfig, @@ -340,6 +342,11 @@ export async function updateSystemSettings( updates.enableThinkingSignatureRectifier = payload.enableThinkingSignatureRectifier; } + // thinking budget 整流器开关(如果提供) + if (payload.enableThinkingBudgetRectifier !== undefined) { + updates.enableThinkingBudgetRectifier = payload.enableThinkingBudgetRectifier; + } + // Codex Session ID 补全开关(如果提供) if (payload.enableCodexSessionIdCompletion !== undefined) { updates.enableCodexSessionIdCompletion = payload.enableCodexSessionIdCompletion; @@ -398,6 +405,7 @@ export async function updateSystemSettings( enableHttp2: systemSettings.enableHttp2, interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, + enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, enableCodexSessionIdCompletion: systemSettings.enableCodexSessionIdCompletion, enableResponseFixer: systemSettings.enableResponseFixer, responseFixerConfig: systemSettings.responseFixerConfig, diff --git a/src/types/provider.ts b/src/types/provider.ts index dd961e79c..220054bae 100644 --- a/src/types/provider.ts +++ b/src/types/provider.ts @@ -30,6 +30,12 @@ export type CodexTextVerbosityPreference = "inherit" | "low" | "medium" | "high" // 由于 Select 的 value 需要是字符串,这里用 "true"/"false" 表示布尔值 export type CodexParallelToolCallsPreference = "inherit" | "true" | "false"; +// Anthropic (Messages API) parameter overrides +// - "inherit": follow client request (default) +// - numeric string: force override to that value +export type AnthropicMaxTokensPreference = "inherit" | string; +export type AnthropicThinkingBudgetPreference = "inherit" | string; + // Codex Instructions 策略枚举 export type CodexInstructionsStrategy = "auto" | "force_official" | "keep_original"; @@ -128,6 +134,10 @@ export interface Provider { codexTextVerbosityPreference: CodexTextVerbosityPreference | null; codexParallelToolCallsPreference: CodexParallelToolCallsPreference | null; + // Anthropic (Messages API) parameter overrides (only for claude/claude-auth providers) + anthropicMaxTokensPreference: AnthropicMaxTokensPreference | null; + anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference | null; + // 废弃(保留向后兼容,但不再使用) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 tpm: number | null; @@ -202,6 +212,8 @@ export interface ProviderDisplay { codexReasoningSummaryPreference: CodexReasoningSummaryPreference | null; codexTextVerbosityPreference: CodexTextVerbosityPreference | null; codexParallelToolCallsPreference: CodexParallelToolCallsPreference | null; + anthropicMaxTokensPreference: AnthropicMaxTokensPreference | null; + anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference | null; // 废弃字段(保留向后兼容) tpm: number | null; rpm: number | null; @@ -290,6 +302,8 @@ export interface CreateProviderData { codex_reasoning_summary_preference?: CodexReasoningSummaryPreference | null; codex_text_verbosity_preference?: CodexTextVerbosityPreference | null; codex_parallel_tool_calls_preference?: CodexParallelToolCallsPreference | null; + anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; + anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; // 废弃字段(保留向后兼容) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 @@ -360,6 +374,8 @@ export interface UpdateProviderData { codex_reasoning_summary_preference?: CodexReasoningSummaryPreference | null; codex_text_verbosity_preference?: CodexTextVerbosityPreference | null; codex_parallel_tool_calls_preference?: CodexParallelToolCallsPreference | null; + anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; + anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; // 废弃字段(保留向后兼容) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 diff --git a/src/types/special-settings.ts b/src/types/special-settings.ts index ae9c622e9..a5294dd0f 100644 --- a/src/types/special-settings.ts +++ b/src/types/special-settings.ts @@ -10,6 +10,7 @@ export type SpecialSetting = | ResponseFixerSpecialSetting | GuardInterceptSpecialSetting | ThinkingSignatureRectifierSpecialSetting + | ThinkingBudgetRectifierSpecialSetting | CodexSessionIdCompletionSpecialSetting | AnthropicCacheTtlHeaderOverrideSpecialSetting | AnthropicContext1mHeaderOverrideSpecialSetting; @@ -132,3 +133,24 @@ export type CodexSessionIdCompletionSpecialSetting = { | "generated_uuid_v7"; sessionId: string; }; + +export type ThinkingBudgetRectifierSpecialSetting = { + type: "thinking_budget_rectifier"; + scope: "request"; + hit: boolean; + providerId: number | null; + providerName: string | null; + trigger: "budget_tokens_too_low"; + attemptNumber: number; + retryAttemptNumber: number; + before: { + maxTokens: number | null; + thinkingType: string | null; + thinkingBudgetTokens: number | null; + }; + after: { + maxTokens: number | null; + thinkingType: string | null; + thinkingBudgetTokens: number | null; + }; +}; diff --git a/src/types/system-config.ts b/src/types/system-config.ts index 06ea9e84e..66b20dfff 100644 --- a/src/types/system-config.ts +++ b/src/types/system-config.ts @@ -49,6 +49,10 @@ export interface SystemSettings { // 目标:当 Anthropic 类型供应商出现 thinking 签名不兼容导致的 400 错误时,自动整流并重试一次 enableThinkingSignatureRectifier: boolean; + // thinking budget 整流器(默认开启) + // 目标:当 Anthropic 类型供应商出现 budget_tokens < 1024 错误时,自动整流并重试一次 + enableThinkingBudgetRectifier: boolean; + // Codex Session ID 补全(默认开启) // 目标:当 Codex 请求缺少 session_id / prompt_cache_key 时,自动补全或生成稳定的会话标识 enableCodexSessionIdCompletion: boolean; @@ -104,6 +108,9 @@ export interface UpdateSystemSettingsInput { // thinking signature 整流器(可选) enableThinkingSignatureRectifier?: boolean; + // thinking budget 整流器(可选) + enableThinkingBudgetRectifier?: boolean; + // Codex Session ID 补全(可选) enableCodexSessionIdCompletion?: boolean; diff --git a/tests/unit/proxy/anthropic-provider-overrides.test.ts b/tests/unit/proxy/anthropic-provider-overrides.test.ts new file mode 100644 index 000000000..962218a6f --- /dev/null +++ b/tests/unit/proxy/anthropic-provider-overrides.test.ts @@ -0,0 +1,775 @@ +import { describe, expect, it } from "vitest"; +import { + applyAnthropicProviderOverrides, + applyAnthropicProviderOverridesWithAudit, +} from "@/lib/anthropic/provider-overrides"; + +describe("Anthropic Provider Overrides", () => { + describe("Provider type filtering", () => { + it("should return unchanged request for non-claude/claude-auth providers (codex)", () => { + const provider = { + providerType: "codex", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toBe(input); + expect(output).toEqual(input); + }); + + it("should return unchanged request for non-claude/claude-auth providers (gemini)", () => { + const provider = { + providerType: "gemini", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "gemini-pro", + messages: [], + max_tokens: 8000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toBe(input); + }); + + it("should return unchanged request for non-claude/claude-auth providers (openai-compatible)", () => { + const provider = { + providerType: "openai-compatible", + anthropicMaxTokensPreference: "16000", + }; + + const input: Record = { + model: "gpt-4", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toBe(input); + }); + + it("should apply overrides for 'claude' provider type", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "32000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(32000); + }); + + it("should apply overrides for 'claude-auth' provider type", () => { + const provider = { + providerType: "claude-auth", + anthropicMaxTokensPreference: "16000", + }; + + const input: Record = { + model: "claude-3-sonnet-20240229", + messages: [], + max_tokens: 4000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(16000); + }); + }); + + describe("max_tokens override", () => { + it("should not change request when preference is 'inherit'", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "inherit", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should not change request when preference is null", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: null, + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should not change request when preference is undefined", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: undefined, + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should set max_tokens to numeric value when preference is valid string '32000'", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "32000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(32000); + }); + + it("should overwrite existing max_tokens value", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "64000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 4000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(64000); + expect(input.max_tokens).toBe(4000); + }); + + it("should not change max_tokens for invalid numeric string", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "invalid", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + }); + + describe("thinking.budget_tokens override", () => { + it("should not change request when preference is 'inherit'", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "inherit", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + thinking: { type: "enabled", budget_tokens: 5000 }, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should not change request when preference is null", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: null, + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + thinking: { type: "enabled", budget_tokens: 5000 }, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should not change request when preference is undefined", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: undefined, + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + thinking: { type: "enabled", budget_tokens: 5000 }, + }; + const snapshot = structuredClone(input); + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output).toEqual(snapshot); + }); + + it("should set thinking.budget_tokens and thinking.type when preference is valid '10240'", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.thinking).toEqual({ + type: "enabled", + budget_tokens: 10240, + }); + }); + + it("should preserve existing thinking properties not overridden", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "8000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + thinking: { + type: "disabled", + budget_tokens: 2000, + custom_field: "preserve_me", + }, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + const thinking = output.thinking as Record; + expect(thinking.type).toBe("enabled"); + expect(thinking.budget_tokens).toBe(8000); + expect(thinking.custom_field).toBe("preserve_me"); + }); + + it("should create thinking object if not present", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "5000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 10000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.thinking).toBeDefined(); + const thinking = output.thinking as Record; + expect(thinking.type).toBe("enabled"); + expect(thinking.budget_tokens).toBe(5000); + }); + + it("should handle non-object thinking value by replacing it", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "6000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + thinking: "invalid_string_value", + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.thinking).toEqual({ + type: "enabled", + budget_tokens: 6000, + }); + }); + }); + + describe("Clamping logic", () => { + it("should clamp budget_tokens to max_tokens - 1 when budget_tokens >= max_tokens (overridden max_tokens)", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "10000", + anthropicThinkingBudgetPreference: "15000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(10000); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(9999); + }); + + it("should clamp budget_tokens to max_tokens - 1 when budget_tokens >= max_tokens (request-provided max_tokens)", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "20000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 16000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(15999); + }); + + it("should clamp budget_tokens when exactly equal to max_tokens", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "8000", + anthropicThinkingBudgetPreference: "8000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(8000); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(7999); + }); + + it("should not clamp when budget_tokens < max_tokens", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(32000); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(10000); + }); + + it("should not clamp when max_tokens is not set", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "50000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(50000); + }); + + it("should skip thinking override when clamped budget_tokens would be below 1024 (API minimum)", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "500", + anthropicThinkingBudgetPreference: "10000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(500); + expect(output.thinking).toBeUndefined(); + }); + + it("should skip thinking override when budget_tokens preference itself is below 1024", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "500", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.thinking).toBeUndefined(); + }); + + it("should skip thinking override when max_tokens is exactly 1024 (clamped would be 1023)", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "1024", + anthropicThinkingBudgetPreference: "2000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(1024); + expect(output.thinking).toBeUndefined(); + }); + + it("should apply thinking override when clamped budget_tokens is exactly 1024", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "1025", + anthropicThinkingBudgetPreference: "2000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const output = applyAnthropicProviderOverrides(provider, input); + expect(output.max_tokens).toBe(1025); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(1024); + expect(thinking.type).toBe("enabled"); + }); + + it("should apply thinking override when budget_tokens is exactly 1024 and no clamping needed", () => { + const provider = { + providerType: "claude", + anthropicThinkingBudgetPreference: "1024", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + }; + + const output = applyAnthropicProviderOverrides(provider, input); + const thinking = output.thinking as Record; + expect(thinking.budget_tokens).toBe(1024); + expect(thinking.type).toBe("enabled"); + }); + }); + + describe("Audit function", () => { + it("should return null audit when provider type is not claude/claude-auth", () => { + const provider = { + id: 123, + name: "codex-provider", + providerType: "codex", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "gpt-4", + messages: [], + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.request).toBe(input); + expect(result.audit).toBeNull(); + }); + + it("should return null audit when all preferences are inherit/null/undefined", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "inherit", + anthropicThinkingBudgetPreference: null, + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.request).toBe(input); + expect(result.audit).toBeNull(); + }); + + it("should return null audit when preferences are invalid numeric strings", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "invalid", + anthropicThinkingBudgetPreference: "not_a_number", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.request).toBe(input); + expect(result.audit).toBeNull(); + }); + + it("should return audit with hit=true when max_tokens override is applied", () => { + const provider = { + id: 1, + name: "claude-provider", + providerType: "claude", + anthropicMaxTokensPreference: "32000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.hit).toBe(true); + expect(result.audit?.providerId).toBe(1); + expect(result.audit?.providerName).toBe("claude-provider"); + }); + + it("should return audit with hit=true when thinking override is applied", () => { + const provider = { + id: 2, + name: "anthropic-direct", + providerType: "claude-auth", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.hit).toBe(true); + expect(result.audit?.providerId).toBe(2); + expect(result.audit?.providerName).toBe("anthropic-direct"); + }); + + it("should track before/after values correctly for max_tokens", () => { + const provider = { + id: 1, + name: "test-provider", + providerType: "claude", + anthropicMaxTokensPreference: "32000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + const maxTokensChange = result.audit?.changes.find((c) => c.path === "max_tokens"); + expect(maxTokensChange?.before).toBe(8000); + expect(maxTokensChange?.after).toBe(32000); + expect(maxTokensChange?.changed).toBe(true); + }); + + it("should track before/after values correctly for thinking fields", () => { + const provider = { + id: 1, + name: "test-provider", + providerType: "claude", + anthropicThinkingBudgetPreference: "10000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 32000, + thinking: { type: "disabled", budget_tokens: 5000 }, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + + const typeChange = result.audit?.changes.find((c) => c.path === "thinking.type"); + expect(typeChange?.before).toBe("disabled"); + expect(typeChange?.after).toBe("enabled"); + expect(typeChange?.changed).toBe(true); + + const budgetChange = result.audit?.changes.find((c) => c.path === "thinking.budget_tokens"); + expect(budgetChange?.before).toBe(5000); + expect(budgetChange?.after).toBe(10000); + expect(budgetChange?.changed).toBe(true); + }); + + it("should set changed=false when override value equals existing value", () => { + const provider = { + id: 1, + name: "test-provider", + providerType: "claude", + anthropicMaxTokensPreference: "8000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + const maxTokensChange = result.audit?.changes.find((c) => c.path === "max_tokens"); + expect(maxTokensChange?.before).toBe(8000); + expect(maxTokensChange?.after).toBe(8000); + expect(maxTokensChange?.changed).toBe(false); + }); + + it("should set audit.changed=true only when at least one value actually changed", () => { + const provider = { + id: 1, + name: "test-provider", + providerType: "claude", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10240", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.changed).toBe(true); + }); + + it("should set audit.changed=false when no values actually changed", () => { + const provider = { + id: 1, + name: "test-provider", + providerType: "claude", + anthropicMaxTokensPreference: "8000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + max_tokens: 8000, + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.hit).toBe(true); + expect(result.audit?.changed).toBe(false); + }); + + it("should include correct metadata in audit", () => { + const provider = { + id: 42, + name: "my-claude-provider", + providerType: "claude", + anthropicMaxTokensPreference: "16000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.type).toBe("provider_parameter_override"); + expect(result.audit?.scope).toBe("provider"); + expect(result.audit?.providerId).toBe(42); + expect(result.audit?.providerName).toBe("my-claude-provider"); + expect(result.audit?.providerType).toBe("claude"); + }); + + it("should handle missing provider id and name gracefully", () => { + const provider = { + providerType: "claude", + anthropicMaxTokensPreference: "16000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + expect(result.audit?.providerId).toBeNull(); + expect(result.audit?.providerName).toBeNull(); + }); + + it("should track null before values when fields do not exist", () => { + const provider = { + id: 1, + name: "test", + providerType: "claude", + anthropicMaxTokensPreference: "32000", + anthropicThinkingBudgetPreference: "10000", + }; + + const input: Record = { + model: "claude-3-opus-20240229", + messages: [], + }; + + const result = applyAnthropicProviderOverridesWithAudit(provider, input); + + const maxTokensChange = result.audit?.changes.find((c) => c.path === "max_tokens"); + expect(maxTokensChange?.before).toBeNull(); + expect(maxTokensChange?.after).toBe(32000); + + const typeChange = result.audit?.changes.find((c) => c.path === "thinking.type"); + expect(typeChange?.before).toBeNull(); + expect(typeChange?.after).toBe("enabled"); + + const budgetChange = result.audit?.changes.find((c) => c.path === "thinking.budget_tokens"); + expect(budgetChange?.before).toBeNull(); + expect(budgetChange?.after).toBe(10000); + }); + }); +});