From f70b7832c9b4310e53911c6200f23c2411491a29 Mon Sep 17 00:00:00 2001 From: ding113 Date: Thu, 11 Jun 2026 13:49:09 +0800 Subject: [PATCH 1/3] feat(proxy): add thinking effort conflict rectifier When Anthropic-compatible providers like DeepSeek or MiMo receive a request with thinking disabled but reasoning_effort or output_config.effort set, they return a 400 error. This rectifier detects the specific error message, strips the conflicting effort fields while preserving the disabled thinking state, and retries the request once against the same provider. Adds a new system setting enableThinkingEffortConflictRectifier (enabled by default) with full UI, API, and database migration support to control this behavior. --- drizzle/0105_chief_rocket_racer.sql | 1 + drizzle/meta/0105_snapshot.json | 4535 +++++++++++++++++ drizzle/meta/_journal.json | 7 + messages/en/settings/config.json | 2 + messages/ja/settings/config.json | 2 + messages/ru/settings/config.json | 2 + messages/zh-CN/settings/config.json | 2 + messages/zh-TW/settings/config.json | 2 + package.json | 1 + src/actions/system-config.ts | 2 + .../_components/system-settings-form.tsx | 28 + src/app/[locale]/settings/config/page.tsx | 1 + src/app/v1/_lib/proxy/forwarder.ts | 93 +- ...thinking-effort-conflict-rectifier.test.ts | 158 + .../thinking-effort-conflict-rectifier.ts | 113 + src/drizzle/schema.ts | 7 + src/lib/api-client/v1/openapi-types.gen.ts | 6 + src/lib/api/v1/schemas/system-config.ts | 3 + src/lib/config/system-settings-cache.ts | 3 + src/lib/utils/special-settings.ts | 13 + src/lib/validation/schemas.ts | 2 + src/repository/_shared/transformers.ts | 2 + src/repository/system-config.ts | 10 + src/types/special-settings.ts | 23 + src/types/system-config.ts | 5 + ...inking-effort-conflict-rectifier.config.ts | 12 + ...g-thinking-effort-conflict-setting.test.ts | 39 + ...thinking-effort-conflict-rectifier.test.ts | 253 + 28 files changed, 5319 insertions(+), 8 deletions(-) create mode 100644 drizzle/0105_chief_rocket_racer.sql create mode 100644 drizzle/meta/0105_snapshot.json create mode 100644 src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts create mode 100644 src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts create mode 100644 tests/configs/thinking-effort-conflict-rectifier.config.ts create mode 100644 tests/unit/actions/system-config-thinking-effort-conflict-setting.test.ts create mode 100644 tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts diff --git a/drizzle/0105_chief_rocket_racer.sql b/drizzle/0105_chief_rocket_racer.sql new file mode 100644 index 000000000..14aa3e620 --- /dev/null +++ b/drizzle/0105_chief_rocket_racer.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN "enable_thinking_effort_conflict_rectifier" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0105_snapshot.json b/drizzle/meta/0105_snapshot.json new file mode 100644 index 000000000..41d3c35e7 --- /dev/null +++ b/drizzle/meta/0105_snapshot.json @@ -0,0 +1,4535 @@ +{ + "id": "e61c4e85-4edf-4d73-b2e3-5326af94eba0", + "prevId": "e287317d-0fc7-4491-960d-b22636dc9471", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hedge_losers": { + "name": "hedge_losers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 7d08f0bbd..4d57a874a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -736,6 +736,13 @@ "when": 1780575810214, "tag": "0104_watery_thunderbird", "breakpoints": true + }, + { + "idx": 105, + "version": "7", + "when": 1781156586163, + "tag": "0105_chief_rocket_racer", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 6d1447d2a..a61789449 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -66,6 +66,8 @@ "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).", + "enableThinkingEffortConflictRectifier": "Enable Thinking Effort Conflict Rectifier", + "enableThinkingEffortConflictRectifierDesc": "When Anthropic-compatible providers (such as DeepSeek or MiMo) return 400 errors because disabled thinking conflicts with reasoning_effort, automatically strips the effort fields (output_config/reasoning_effort) and retries once against the same provider (enabled by default).", "enableBillingHeaderRectifier": "Enable Billing Header Rectifier", "enableBillingHeaderRectifierDesc": "Proactively removes x-anthropic-billing-header text blocks injected by Claude Code client into the system prompt, preventing Amazon Bedrock and other non-native Anthropic upstreams from returning 400 errors (enabled by default).", "enableResponseInputRectifier": "Enable Response Input Rectifier", diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index 68a55077c..6623f9504 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -66,6 +66,8 @@ "enableThinkingSignatureRectifierDesc": "Anthropic プロバイダーで thinking 署名の不整合や不正なリクエストエラーが発生した場合、thinking 関連ブロックを削除して同一プロバイダーへ1回だけ再試行します(既定で有効)。", "enableThinkingBudgetRectifier": "thinking 予算整流を有効化", "enableThinkingBudgetRectifierDesc": "Anthropic プロバイダーで budget_tokens < 1024 エラーが発生した場合、thinking 予算を最大値(32000)に設定し、必要に応じて max_tokens を 64000 に設定して1回だけ再試行します(既定で有効)。", + "enableThinkingEffortConflictRectifier": "thinking effort 競合整流を有効化", + "enableThinkingEffortConflictRectifierDesc": "Anthropic 互換プロバイダー(DeepSeek や MiMo など)が thinking 無効と reasoning_effort の併存により 400 エラーを返した場合、effort フィールド(output_config/reasoning_effort)を自動的に取り除き、同じプロバイダーに対して1回だけ再試行します(既定で有効)。", "enableBillingHeaderRectifier": "課金ヘッダー整流を有効化", "enableBillingHeaderRectifierDesc": "Claude Code クライアントが system プロンプトに注入する x-anthropic-billing-header テキストブロックを事前に削除し、Amazon Bedrock などの非ネイティブ Anthropic 上流による 400 エラーを防止します(既定で有効)。", "enableResponseInputRectifier": "Response Input 整流器を有効化", diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 52090a13a..c7add884d 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -66,6 +66,8 @@ "enableThinkingSignatureRectifierDesc": "Если Anthropic-провайдер возвращает ошибку несовместимой подписи thinking или некорректного запроса, автоматически удаляет несовместимые thinking-блоки и повторяет запрос один раз к тому же провайдеру (включено по умолчанию).", "enableThinkingBudgetRectifier": "Включить исправление thinking-budget", "enableThinkingBudgetRectifierDesc": "Если Anthropic-провайдер возвращает ошибку budget_tokens < 1024, автоматически устанавливает thinking budget на максимум (32000) и при необходимости max_tokens на 64000, затем повторяет запрос один раз (включено по умолчанию).", + "enableThinkingEffortConflictRectifier": "Включить исправление конфликта thinking-effort", + "enableThinkingEffortConflictRectifierDesc": "Если Anthropic-совместимый провайдер (например, DeepSeek или MiMo) возвращает ошибку 400 из-за сочетания отключённого thinking и reasoning_effort, автоматически удаляет поля effort (output_config/reasoning_effort) и повторяет запрос к тому же провайдеру один раз (включено по умолчанию).", "enableBillingHeaderRectifier": "Включить исправление billing-заголовка", "enableBillingHeaderRectifierDesc": "Проактивно удаляет текстовые блоки x-anthropic-billing-header, добавленные клиентом Claude Code в системный промпт, предотвращая ошибки 400 от Amazon Bedrock и других не-Anthropic провайдеров (включено по умолчанию).", "enableResponseInputRectifier": "Включить исправление Response Input", diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index ecd85ddbd..c705683a0 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -64,6 +64,8 @@ "enableThinkingSignatureRectifierDesc": "当 Anthropic 类型供应商返回 thinking 签名不兼容或非法请求等错误时,自动移除不兼容的 thinking 相关块并对同一供应商重试一次(默认开启)。", "enableThinkingBudgetRectifier": "启用 thinking 预算整流器", "enableThinkingBudgetRectifierDesc": "当 Anthropic 类型供应商返回 budget_tokens < 1024 错误时,自动将 thinking 预算设为最大值(32000),并在需要时将 max_tokens 设为 64000,然后重试一次(默认开启)。", + "enableThinkingEffortConflictRectifier": "启用 thinking effort 冲突整流器", + "enableThinkingEffortConflictRectifierDesc": "当 Anthropic 兼容供应商(如 DeepSeek、MiMo 等)因 thinking 关闭与 reasoning_effort 同时存在返回 400 错误时,自动剥离 effort 字段(output_config/reasoning_effort)并对同一供应商重试一次(默认开启)。", "enableBillingHeaderRectifier": "启用计费标头整流器", "enableBillingHeaderRectifierDesc": "主动移除 Claude Code 客户端注入到 system 提示中的 x-anthropic-billing-header 文本块,防止 Amazon Bedrock 等非原生 Anthropic 上游返回 400 错误(默认开启)。", "enableResponseInputRectifier": "启用 Response Input 整流器", diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index 7c94a809d..8ef145873 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -66,6 +66,8 @@ "enableThinkingSignatureRectifierDesc": "當 Anthropic 類型供應商返回 thinking 簽名不相容或非法請求等錯誤時,自動移除不相容的 thinking 相關區塊並對同一供應商重試一次(預設開啟)。", "enableThinkingBudgetRectifier": "啟用 thinking 預算整流器", "enableThinkingBudgetRectifierDesc": "當 Anthropic 類型供應商返回 budget_tokens < 1024 錯誤時,自動將 thinking 預算設為最大值(32000),並在需要時將 max_tokens 設為 64000,然後重試一次(預設開啟)。", + "enableThinkingEffortConflictRectifier": "啟用 thinking effort 衝突整流器", + "enableThinkingEffortConflictRectifierDesc": "當 Anthropic 相容供應商(如 DeepSeek、MiMo 等)因 thinking 關閉與 reasoning_effort 同時存在返回 400 錯誤時,自動剝離 effort 欄位(output_config/reasoning_effort)並對同一供應商重試一次(預設開啟)。", "enableBillingHeaderRectifier": "啟用計費標頭整流器", "enableBillingHeaderRectifierDesc": "主動移除 Claude Code 客戶端注入到 system 提示中的 x-anthropic-billing-header 文字區塊,防止 Amazon Bedrock 等非原生 Anthropic 上游回傳 400 錯誤(預設開啟)。", "enableResponseInputRectifier": "啟用 Response Input 整流器", diff --git a/package.json b/package.json index 530014dd0..da5ce414b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "test:coverage:logs-sessionid-time-filter": "vitest run --config tests/configs/logs-sessionid-time-filter.config.ts --coverage", "test:coverage:codex-session-id-completer": "vitest run --config tests/configs/codex-session-id-completer.config.ts --coverage", "test:coverage:thinking-signature-rectifier": "vitest run --config tests/configs/thinking-signature-rectifier.config.ts --coverage", + "test:coverage:thinking-effort-conflict-rectifier": "vitest run --config tests/configs/thinking-effort-conflict-rectifier.config.ts --coverage", "test:coverage:quota": "vitest run --config tests/configs/quota.config.ts --coverage", "test:coverage:my-usage": "vitest run --config tests/configs/my-usage.config.ts --coverage", "test:coverage:proxy-guard-pipeline": "vitest run --config tests/configs/proxy-guard-pipeline.config.ts --coverage", diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index eb4a24636..c40a7302d 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -73,6 +73,7 @@ export async function saveSystemSettings(formData: { interceptAnthropicWarmupRequests?: boolean; enableThinkingSignatureRectifier?: boolean; enableThinkingBudgetRectifier?: boolean; + enableThinkingEffortConflictRectifier?: boolean; enableBillingHeaderRectifier?: boolean; enableResponseInputRectifier?: boolean; allowNonConversationEndpointProviderFallback?: boolean; @@ -125,6 +126,7 @@ export async function saveSystemSettings(formData: { interceptAnthropicWarmupRequests: validated.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: validated.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: validated.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: validated.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: validated.enableBillingHeaderRectifier, enableResponseInputRectifier: validated.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: 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 843ef0338..a6808393e 100644 --- a/src/app/[locale]/settings/config/_components/system-settings-form.tsx +++ b/src/app/[locale]/settings/config/_components/system-settings-form.tsx @@ -76,6 +76,7 @@ interface SystemSettingsFormProps { | "enableBillingHeaderRectifier" | "enableResponseInputRectifier" | "enableThinkingBudgetRectifier" + | "enableThinkingEffortConflictRectifier" | "allowNonConversationEndpointProviderFallback" | "fakeStreamingWhitelist" | "enableCodexSessionIdCompletion" @@ -169,6 +170,8 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) const [enableThinkingBudgetRectifier, setEnableThinkingBudgetRectifier] = useState( initialSettings.enableThinkingBudgetRectifier ); + const [enableThinkingEffortConflictRectifier, setEnableThinkingEffortConflictRectifier] = + useState(initialSettings.enableThinkingEffortConflictRectifier); const [enableCodexSessionIdCompletion, setEnableCodexSessionIdCompletion] = useState( initialSettings.enableCodexSessionIdCompletion ); @@ -317,6 +320,7 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) allowNonConversationEndpointProviderFallback, fakeStreamingWhitelist: sanitizedFakeStreamingWhitelist, enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier, enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection, enableResponseFixer, @@ -364,6 +368,7 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) })) ); setEnableThinkingBudgetRectifier(result.data.enableThinkingBudgetRectifier); + setEnableThinkingEffortConflictRectifier(result.data.enableThinkingEffortConflictRectifier); setEnableCodexSessionIdCompletion(result.data.enableCodexSessionIdCompletion); setEnableClaudeMetadataUserIdInjection(result.data.enableClaudeMetadataUserIdInjection); setEnableResponseFixer(result.data.enableResponseFixer); @@ -796,6 +801,29 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) /> + {/* Enable Thinking Effort Conflict Rectifier */} +
+
+
+ +
+
+

+ {t("enableThinkingEffortConflictRectifier")} +

+

+ {t("enableThinkingEffortConflictRectifierDesc")} +

+
+
+ setEnableThinkingEffortConflictRectifier(checked)} + disabled={isPending} + /> +
+ {/* Enable Billing Header Rectifier */}
diff --git a/src/app/[locale]/settings/config/page.tsx b/src/app/[locale]/settings/config/page.tsx index 46b992bd0..72cc51a6d 100644 --- a/src/app/[locale]/settings/config/page.tsx +++ b/src/app/[locale]/settings/config/page.tsx @@ -61,6 +61,7 @@ async function SettingsConfigContent({ locale }: { locale: string }) { interceptAnthropicWarmupRequests: settings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: settings.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: settings.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: settings.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: settings.enableBillingHeaderRectifier, enableResponseInputRectifier: settings.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: diff --git a/src/app/v1/_lib/proxy/forwarder.ts b/src/app/v1/_lib/proxy/forwarder.ts index a568f0717..523f63a0f 100644 --- a/src/app/v1/_lib/proxy/forwarder.ts +++ b/src/app/v1/_lib/proxy/forwarder.ts @@ -96,6 +96,10 @@ import { detectThinkingBudgetRectifierTrigger, rectifyThinkingBudget, } from "./thinking-budget-rectifier"; +import { + detectThinkingEffortConflictRectifierTrigger, + rectifyThinkingEffortConflict, +} from "./thinking-effort-conflict-rectifier"; import { detectThinkingSignatureRectifierTrigger, rectifyAnthropicRequestMessage, @@ -225,21 +229,27 @@ type StreamingHedgeAttempt = { type ReactiveRectifierRetryState = { thinkingSignatureRetried: boolean; thinkingBudgetRetried: boolean; + thinkingEffortConflictRetried: boolean; }; +type ReactiveRectifierType = + | "thinking_signature_rectifier" + | "thinking_budget_rectifier" + | "thinking_effort_conflict_rectifier"; + type ReactiveRectifierResult = | { matched: false } | { matched: true; applied: false; reason: "already_retried" | "not_applicable"; - rectifierType: "thinking_signature_rectifier" | "thinking_budget_rectifier"; + rectifierType: ReactiveRectifierType; trigger: string; } | { matched: true; applied: true; - rectifierType: "thinking_signature_rectifier" | "thinking_budget_rectifier"; + rectifierType: ReactiveRectifierType; trigger: string; requestDetailsBeforeRectify: ReturnType; }; @@ -762,12 +772,15 @@ function buildRetryFailedChainEntry( }; } -function getReactiveRectifierDisplayName( - rectifierType: "thinking_signature_rectifier" | "thinking_budget_rectifier" -): string { - return rectifierType === "thinking_signature_rectifier" - ? "Thinking signature rectifier" - : "Thinking budget rectifier"; +function getReactiveRectifierDisplayName(rectifierType: ReactiveRectifierType): string { + switch (rectifierType) { + case "thinking_signature_rectifier": + return "Thinking signature rectifier"; + case "thinking_budget_rectifier": + return "Thinking budget rectifier"; + case "thinking_effort_conflict_rectifier": + return "Thinking effort conflict rectifier"; + } } async function tryApplyReactiveAnthropicRectifier(params: { @@ -794,6 +807,68 @@ async function tryApplyReactiveAnthropicRectifier(params: { return { matched: false }; } + // 先于签名整流器检测:effort 冲突的错误文案更具体(reasoning_effort/output_config), + // 避免被签名整流器的通用 invalid request 兜底吞掉。 + const effortConflictTrigger = detectThinkingEffortConflictRectifierTrigger(errorMessage); + if (effortConflictTrigger) { + const settings = await getCachedSystemSettings(); + const enabled = settings.enableThinkingEffortConflictRectifier ?? true; + + if (!enabled) { + return { matched: false }; + } + + if (params.retryState.thinkingEffortConflictRetried) { + return { + matched: true, + applied: false, + reason: "already_retried", + rectifierType: "thinking_effort_conflict_rectifier", + trigger: effortConflictTrigger, + }; + } + + const requestDetailsBeforeRectify = buildRequestDetails(requestSession); + const rectified = rectifyThinkingEffortConflict( + requestSession.request.message as Record + ); + + addSpecialSettingForPersistence(requestSession, persistSession, { + type: "thinking_effort_conflict_rectifier", + scope: "request", + hit: rectified.applied, + providerId: provider.id, + providerName: provider.name, + trigger: effortConflictTrigger, + attemptNumber, + retryAttemptNumber, + removedOutputConfig: rectified.removedOutputConfig, + removedReasoningEffort: rectified.removedReasoningEffort, + thinkingType: rectified.thinkingType, + effort: rectified.effort, + }); + await persistSpecialSettings(persistSession); + + if (!rectified.applied) { + return { + matched: true, + applied: false, + reason: "not_applicable", + rectifierType: "thinking_effort_conflict_rectifier", + trigger: effortConflictTrigger, + }; + } + + params.retryState.thinkingEffortConflictRetried = true; + return { + matched: true, + applied: true, + rectifierType: "thinking_effort_conflict_rectifier", + trigger: effortConflictTrigger, + requestDetailsBeforeRectify, + }; + } + const signatureTrigger = detectThinkingSignatureRectifierTrigger(errorMessage); if (signatureTrigger) { const settings = await getCachedSystemSettings(); @@ -1094,6 +1169,7 @@ export class ProxyForwarder { const reactiveRectifierRetryState: ReactiveRectifierRetryState = { thinkingSignatureRetried: false, thinkingBudgetRetried: false, + thinkingEffortConflictRetried: false, }; const requestPath = session.requestUrl.pathname; @@ -4452,6 +4528,7 @@ export class ProxyForwarder { reactiveRectifierRetryState: { thinkingSignatureRetried: false, thinkingBudgetRetried: false, + thinkingEffortConflictRetried: false, }, settled: false, thresholdTriggered: false, diff --git a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts new file mode 100644 index 000000000..983779e42 --- /dev/null +++ b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts @@ -0,0 +1,158 @@ +import { describe, expect, test } from "vitest"; +import { + detectThinkingEffortConflictRectifierTrigger, + rectifyThinkingEffortConflict, +} from "./thinking-effort-conflict-rectifier"; + +describe("detectThinkingEffortConflictRectifierTrigger", () => { + test("matches the documented DeepSeek conflict error", () => { + expect( + detectThinkingEffortConflictRectifierTrigger( + "thinking options type cannot be disabled when reasoning_effort is set" + ) + ).toBe("thinking_disabled_with_reasoning_effort"); + }); + + test("matches the error embedded in a proxy upstream envelope", () => { + expect( + detectThinkingEffortConflictRectifierTrigger( + 'Provider deepseek returned 400: Provider returned 400: Bad Request | Upstream: {"error":{"message":"thinking options type cannot be disabled when reasoning_effort is set","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}' + ) + ).toBe("thinking_disabled_with_reasoning_effort"); + }); + + test("matches case and quoting variants", () => { + expect( + detectThinkingEffortConflictRectifierTrigger( + "Thinking options `type` cannot be disabled when `reasoning_effort` is set." + ) + ).toBe("thinking_disabled_with_reasoning_effort"); + }); + + test("matches output_config flavored variants", () => { + expect( + detectThinkingEffortConflictRectifierTrigger( + "thinking cannot be disabled when output_config.effort is set" + ) + ).toBe("thinking_disabled_with_reasoning_effort"); + }); + + test("ignores unrelated errors", () => { + expect(detectThinkingEffortConflictRectifierTrigger(null)).toBeNull(); + expect(detectThinkingEffortConflictRectifierTrigger(undefined)).toBeNull(); + expect(detectThinkingEffortConflictRectifierTrigger("")).toBeNull(); + expect( + detectThinkingEffortConflictRectifierTrigger("Invalid `signature` in `thinking` block") + ).toBeNull(); + expect( + detectThinkingEffortConflictRectifierTrigger( + "thinking.enabled.budget_tokens: Input should be greater than or equal to 1024" + ) + ).toBeNull(); + expect( + detectThinkingEffortConflictRectifierTrigger("reasoning_effort must be one of low|medium") + ).toBeNull(); + expect(detectThinkingEffortConflictRectifierTrigger("invalid request: malformed")).toBeNull(); + }); +}); + +describe("rectifyThinkingEffortConflict", () => { + test("removes output_config when thinking is disabled (Claude Code subagent shape)", () => { + const message: Record = { + model: "deepseek-v4-pro", + thinking: { type: "disabled" }, + output_config: { effort: "max" }, + messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }], + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(true); + expect(result.removedOutputConfig).toBe(true); + expect(result.removedReasoningEffort).toBe(false); + expect(result.thinkingType).toBe("disabled"); + expect(result.effort).toBe("max"); + expect("output_config" in message).toBe(false); + expect(message.thinking).toEqual({ type: "disabled" }); + }); + + test("removes a top-level reasoning_effort passthrough as well", () => { + const message: Record = { + thinking: { type: "disabled" }, + reasoning_effort: "high", + messages: [], + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(true); + expect(result.removedOutputConfig).toBe(false); + expect(result.removedReasoningEffort).toBe(true); + expect(result.effort).toBe("high"); + expect("reasoning_effort" in message).toBe(false); + }); + + test("treats a missing thinking field as disabled and strips effort", () => { + const message: Record = { + output_config: { effort: "medium" }, + messages: [], + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(true); + expect(result.removedOutputConfig).toBe(true); + expect(result.thinkingType).toBeNull(); + }); + + test("does not touch requests with thinking enabled", () => { + const message: Record = { + thinking: { type: "enabled", budget_tokens: 2048 }, + output_config: { effort: "max" }, + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(false); + expect(message.output_config).toEqual({ effort: "max" }); + expect(message.thinking).toEqual({ type: "enabled", budget_tokens: 2048 }); + }); + + test("does not touch requests with adaptive thinking", () => { + const message: Record = { + thinking: { type: "adaptive" }, + output_config: { effort: "low" }, + }; + + expect(rectifyThinkingEffortConflict(message).applied).toBe(false); + expect(message.output_config).toEqual({ effort: "low" }); + }); + + test("is a no-op when no effort fields are present", () => { + const message: Record = { + thinking: { type: "disabled" }, + messages: [], + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(false); + expect(result.removedOutputConfig).toBe(false); + expect(result.removedReasoningEffort).toBe(false); + }); + + test("keeps an effort-less output_config in place", () => { + const message: Record = { + thinking: { type: "disabled" }, + output_config: { something_else: true }, + reasoning_effort: "low", + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(true); + expect(result.removedOutputConfig).toBe(false); + expect(result.removedReasoningEffort).toBe(true); + expect(message.output_config).toEqual({ something_else: true }); + }); +}); diff --git a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts new file mode 100644 index 000000000..c907ee6c5 --- /dev/null +++ b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts @@ -0,0 +1,113 @@ +/** + * Thinking Effort Conflict Rectifier - Reactive rectifier for strict Anthropic-compatible + * providers (DeepSeek, MiMo, ...) that reject `thinking: { type: "disabled" }` combined + * with a reasoning-effort field. + * + * Background (issue #1257): Claude Code v2.1.166+ disables thinking for subagent tasks + * but keeps the global `output_config: { effort }` in the payload. The official Anthropic + * API ignores the contradiction; DeepSeek's validation rejects it with + * "thinking options type cannot be disabled when reasoning_effort is set" (DeepSeek docs: + * `output_config` only supports `effort`, which maps to reasoning_effort internally). + * + * Action: strip the effort fields (`output_config.effort` carrier / top-level + * `reasoning_effort`) while keeping thinking disabled, then retry the same provider once. + */ + +export type ThinkingEffortConflictRectifierTrigger = "thinking_disabled_with_reasoning_effort"; + +export type ThinkingEffortConflictRectifierResult = { + applied: boolean; + removedOutputConfig: boolean; + removedReasoningEffort: boolean; + thinkingType: string | null; + effort: string | null; +}; + +/** + * 检测是否需要触发「thinking effort 冲突整流器」 + * + * 注意:不依赖错误规则开关(error rules 可能被用户关闭),仅做字符串匹配。 + */ +export function detectThinkingEffortConflictRectifierTrigger( + errorMessage: string | null | undefined +): ThinkingEffortConflictRectifierTrigger | null { + if (!errorMessage) return null; + + const lower = errorMessage.toLowerCase(); + + const mentionsDisableConflict = + lower.includes("cannot be disabled") || lower.includes("can not be disabled"); + if (!mentionsDisableConflict) return null; + + // DeepSeek 原文:thinking options type cannot be disabled when reasoning_effort is set + if (lower.includes("reasoning_effort")) { + return "thinking_disabled_with_reasoning_effort"; + } + + // 变体兜底:以 output_config(.effort) 表述同一冲突的上游 + if (lower.includes("output_config") && lower.includes("thinking")) { + return "thinking_disabled_with_reasoning_effort"; + } + + return null; +} + +/** + * 对 Anthropic 请求体做最小侵入整流: + * - 仅当 thinking 关闭(或缺省,上游按关闭处理)时生效 + * - 移除携带 effort 的 output_config 与顶层 reasoning_effort 透传字段 + * - 保留 thinking 关闭状态(尊重客户端对子 agent 关闭思考的意图) + * + * 说明:仅在上游报错后、同供应商重试前调用(被动触发),不影响正常请求; + * 原地修改 message 对象。 + */ +export function rectifyThinkingEffortConflict( + message: Record +): ThinkingEffortConflictRectifierResult { + const thinking = message.thinking; + const thinkingType = + thinking && typeof thinking === "object" && !Array.isArray(thinking) + ? typeof (thinking as Record).type === "string" + ? ((thinking as Record).type as string) + : null + : null; + + const result: ThinkingEffortConflictRectifierResult = { + applied: false, + removedOutputConfig: false, + removedReasoningEffort: false, + thinkingType, + effort: null, + }; + + // thinking 显式启用(enabled/adaptive 等)时不属于该冲突,保持原样 + const thinkingDisabled = thinkingType === null || thinkingType === "disabled"; + if (!thinkingDisabled) { + return result; + } + + const outputConfig = message.output_config; + const outputConfigEffort = + outputConfig && typeof outputConfig === "object" && !Array.isArray(outputConfig) + ? (outputConfig as Record).effort + : undefined; + + if (outputConfigEffort !== undefined) { + result.effort = typeof outputConfigEffort === "string" ? outputConfigEffort : null; + delete message.output_config; + result.removedOutputConfig = true; + result.applied = true; + } + + const reasoningEffort = message.reasoning_effort; + if (reasoningEffort !== undefined) { + if (result.effort === null && typeof reasoningEffort === "string") { + result.effort = reasoningEffort; + } + delete message.reasoning_effort; + result.removedReasoningEffort = true; + result.applied = true; + } + + return result; +} diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index 034c07a1f..ff1a76a2a 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -804,6 +804,13 @@ export const systemSettings = pgTable('system_settings', { .notNull() .default(true), + // thinking effort 冲突整流器(默认开启) + // 开启后:当 Anthropic 兼容供应商(DeepSeek/MiMo 等)因 thinking 关闭 + reasoning_effort 同时存在 + // 返回 400 错误时,自动剥离 effort 字段并对同供应商重试一次 + enableThinkingEffortConflictRectifier: boolean('enable_thinking_effort_conflict_rectifier') + .notNull() + .default(true), + // billing header 整流器(默认开启) // 开启后:主动移除 Claude Code 客户端注入到 system 提示中的 x-anthropic-billing-header 文本块 enableBillingHeaderRectifier: boolean('enable_billing_header_rectifier') diff --git a/src/lib/api-client/v1/openapi-types.gen.ts b/src/lib/api-client/v1/openapi-types.gen.ts index 3b3aba480..b603e42ec 100644 --- a/src/lib/api-client/v1/openapi-types.gen.ts +++ b/src/lib/api-client/v1/openapi-types.gen.ts @@ -11655,6 +11655,8 @@ export interface operations { enableThinkingSignatureRectifier: boolean; /** @description Whether thinking budget rectifier retries are enabled. */ enableThinkingBudgetRectifier: boolean; + /** @description Whether thinking effort conflict rectifier retries are enabled. */ + enableThinkingEffortConflictRectifier: boolean; /** @description Whether billing-header rectifier is enabled. */ enableBillingHeaderRectifier: boolean; /** @description Whether Responses API input rectifier is enabled. */ @@ -11913,6 +11915,8 @@ export interface operations { enableThinkingSignatureRectifier?: boolean; /** @description Whether thinking budget rectifier retries are enabled. */ enableThinkingBudgetRectifier?: boolean; + /** @description Whether thinking effort conflict rectifier retries are enabled. */ + enableThinkingEffortConflictRectifier?: boolean; /** @description Whether billing-header rectifier is enabled. */ enableBillingHeaderRectifier?: boolean; /** @description Whether Responses API input rectifier is enabled. */ @@ -12044,6 +12048,8 @@ export interface operations { enableThinkingSignatureRectifier: boolean; /** @description Whether thinking budget rectifier retries are enabled. */ enableThinkingBudgetRectifier: boolean; + /** @description Whether thinking effort conflict rectifier retries are enabled. */ + enableThinkingEffortConflictRectifier: boolean; /** @description Whether billing-header rectifier is enabled. */ enableBillingHeaderRectifier: boolean; /** @description Whether Responses API input rectifier is enabled. */ diff --git a/src/lib/api/v1/schemas/system-config.ts b/src/lib/api/v1/schemas/system-config.ts index 92dc06ecb..944699f0c 100644 --- a/src/lib/api/v1/schemas/system-config.ts +++ b/src/lib/api/v1/schemas/system-config.ts @@ -126,6 +126,9 @@ export const SystemSettingsSchema = z enableThinkingBudgetRectifier: z .boolean() .describe("Whether thinking budget rectifier retries are enabled."), + enableThinkingEffortConflictRectifier: z + .boolean() + .describe("Whether thinking effort conflict rectifier retries are enabled."), enableBillingHeaderRectifier: z .boolean() .describe("Whether billing-header rectifier is enabled."), diff --git a/src/lib/config/system-settings-cache.ts b/src/lib/config/system-settings-cache.ts index 232349d56..2b4043460 100644 --- a/src/lib/config/system-settings-cache.ts +++ b/src/lib/config/system-settings-cache.ts @@ -41,6 +41,7 @@ const DEFAULT_SETTINGS: Pick< | "codexPriorityBillingSource" | "enableThinkingSignatureRectifier" | "enableThinkingBudgetRectifier" + | "enableThinkingEffortConflictRectifier" | "enableBillingHeaderRectifier" | "enableResponseInputRectifier" | "allowNonConversationEndpointProviderFallback" @@ -60,6 +61,7 @@ const DEFAULT_SETTINGS: Pick< codexPriorityBillingSource: "requested", enableThinkingSignatureRectifier: true, enableThinkingBudgetRectifier: true, + enableThinkingEffortConflictRectifier: true, enableBillingHeaderRectifier: true, enableResponseInputRectifier: true, // 安全敏感开关:冷缓存 / DB 读取失败时 fail-closed,避免意外重新开启跨供应商 raw fallback。 @@ -148,6 +150,7 @@ export async function getCachedSystemSettings(): Promise { interceptAnthropicWarmupRequests: DEFAULT_SETTINGS.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: DEFAULT_SETTINGS.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: DEFAULT_SETTINGS.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: DEFAULT_SETTINGS.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: DEFAULT_SETTINGS.enableBillingHeaderRectifier, enableResponseInputRectifier: DEFAULT_SETTINGS.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: diff --git a/src/lib/utils/special-settings.ts b/src/lib/utils/special-settings.ts index a5f31c028..09922e741 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.removedRedactedThinkingBlocks, setting.removedSignatureFields, ]); + case "thinking_effort_conflict_rectifier": + return JSON.stringify([ + setting.type, + setting.hit, + setting.providerId ?? null, + setting.trigger, + setting.attemptNumber, + setting.retryAttemptNumber, + setting.removedOutputConfig, + setting.removedReasoningEffort, + setting.thinkingType, + setting.effort, + ]); case "codex_session_id_completion": return JSON.stringify([ setting.type, diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index 368f564ab..5640440ff 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -1005,6 +1005,8 @@ export const UpdateSystemSettingsSchema = z.object({ enableThinkingSignatureRectifier: z.boolean().optional(), // thinking budget 整流器(可选) enableThinkingBudgetRectifier: z.boolean().optional(), + // thinking effort 冲突整流器(可选) + enableThinkingEffortConflictRectifier: z.boolean().optional(), // billing header 整流器(可选) enableBillingHeaderRectifier: z.boolean().optional(), // Response API input 整流器(可选) diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index e26969ce0..353b5e5d6 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -266,6 +266,8 @@ export function toSystemSettings(dbSettings: any): SystemSettings { interceptAnthropicWarmupRequests: dbSettings?.interceptAnthropicWarmupRequests ?? false, enableThinkingSignatureRectifier: dbSettings?.enableThinkingSignatureRectifier ?? true, enableThinkingBudgetRectifier: dbSettings?.enableThinkingBudgetRectifier ?? true, + enableThinkingEffortConflictRectifier: + dbSettings?.enableThinkingEffortConflictRectifier ?? true, enableBillingHeaderRectifier: dbSettings?.enableBillingHeaderRectifier ?? true, enableResponseInputRectifier: dbSettings?.enableResponseInputRectifier ?? true, allowNonConversationEndpointProviderFallback: diff --git a/src/repository/system-config.ts b/src/repository/system-config.ts index ab83f114d..9dc7722ff 100644 --- a/src/repository/system-config.ts +++ b/src/repository/system-config.ts @@ -163,6 +163,7 @@ function createFallbackSettings(): SystemSettings { interceptAnthropicWarmupRequests: false, enableThinkingSignatureRectifier: true, enableThinkingBudgetRectifier: true, + enableThinkingEffortConflictRectifier: true, enableBillingHeaderRectifier: true, enableResponseInputRectifier: true, allowNonConversationEndpointProviderFallback: true, @@ -218,6 +219,7 @@ export async function getSystemSettings(): Promise { interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: systemSettings.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: systemSettings.enableBillingHeaderRectifier, enableResponseInputRectifier: systemSettings.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: @@ -260,6 +262,7 @@ export async function getSystemSettings(): Promise { interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: systemSettings.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: systemSettings.enableBillingHeaderRectifier, enableResponseInputRectifier: systemSettings.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: @@ -580,6 +583,7 @@ export async function updateSystemSettings( interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: systemSettings.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: systemSettings.enableBillingHeaderRectifier, enableResponseInputRectifier: systemSettings.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: @@ -622,6 +626,7 @@ export async function updateSystemSettings( interceptAnthropicWarmupRequests: systemSettings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: systemSettings.enableThinkingSignatureRectifier, enableThinkingBudgetRectifier: systemSettings.enableThinkingBudgetRectifier, + enableThinkingEffortConflictRectifier: systemSettings.enableThinkingEffortConflictRectifier, enableBillingHeaderRectifier: systemSettings.enableBillingHeaderRectifier, enableResponseInputRectifier: systemSettings.enableResponseInputRectifier, enableCodexSessionIdCompletion: systemSettings.enableCodexSessionIdCompletion, @@ -751,6 +756,11 @@ export async function updateSystemSettings( updates.enableThinkingBudgetRectifier = payload.enableThinkingBudgetRectifier; } + // thinking effort 冲突整流器开关(如果提供) + if (payload.enableThinkingEffortConflictRectifier !== undefined) { + updates.enableThinkingEffortConflictRectifier = payload.enableThinkingEffortConflictRectifier; + } + // billing header 整流器开关(如果提供) if (payload.enableBillingHeaderRectifier !== undefined) { updates.enableBillingHeaderRectifier = payload.enableBillingHeaderRectifier; diff --git a/src/types/special-settings.ts b/src/types/special-settings.ts index a93b8d1fe..c24bc1930 100644 --- a/src/types/special-settings.ts +++ b/src/types/special-settings.ts @@ -11,6 +11,7 @@ export type SpecialSetting = | GuardInterceptSpecialSetting | ThinkingSignatureRectifierSpecialSetting | ThinkingBudgetRectifierSpecialSetting + | ThinkingEffortConflictRectifierSpecialSetting | BillingHeaderRectifierSpecialSetting | CodexSessionIdCompletionSpecialSetting | ClaudeMetadataUserIdInjectionSpecialSetting @@ -148,6 +149,28 @@ export type ThinkingSignatureRectifierSpecialSetting = { removedSignatureFields: number; }; +/** + * Thinking effort 冲突整流器审计 + * + * 用于记录:当 Anthropic 兼容供应商(如 DeepSeek、MiMo 等)因 + * thinking 关闭 + reasoning_effort/output_config.effort 同时存在而返回 400 时, + * 代理剥离 effort 字段并对同供应商自动重试一次的行为。 + */ +export type ThinkingEffortConflictRectifierSpecialSetting = { + type: "thinking_effort_conflict_rectifier"; + scope: "request"; + hit: boolean; + providerId: number | null; + providerName: string | null; + trigger: "thinking_disabled_with_reasoning_effort"; + attemptNumber: number; + retryAttemptNumber: number; + removedOutputConfig: boolean; + removedReasoningEffort: boolean; + thinkingType: string | null; + effort: string | null; +}; + /** * Codex Session ID 补全审计 * diff --git a/src/types/system-config.ts b/src/types/system-config.ts index e0080a37f..86d15124f 100644 --- a/src/types/system-config.ts +++ b/src/types/system-config.ts @@ -91,6 +91,11 @@ export interface SystemSettings { // 目标:当 Anthropic 类型供应商出现 budget_tokens < 1024 错误时,自动整流并重试一次 enableThinkingBudgetRectifier: boolean; + // thinking effort 冲突整流器(默认开启) + // 目标:当 Anthropic 兼容供应商(DeepSeek/MiMo 等)因 thinking 关闭 + reasoning_effort + // 同时存在返回 400 错误时,自动剥离 effort 字段并对同供应商重试一次 + enableThinkingEffortConflictRectifier: boolean; + // billing header 整流器(默认开启) // 目标:主动移除 Claude Code 客户端注入到 system 提示中的 x-anthropic-billing-header 文本块, // 防止 Amazon Bedrock 等非原生 Anthropic 上游返回 400 错误 diff --git a/tests/configs/thinking-effort-conflict-rectifier.config.ts b/tests/configs/thinking-effort-conflict-rectifier.config.ts new file mode 100644 index 000000000..5585c41c1 --- /dev/null +++ b/tests/configs/thinking-effort-conflict-rectifier.config.ts @@ -0,0 +1,12 @@ +import { createCoverageConfig } from "../vitest.base"; + +export default createCoverageConfig({ + name: "thinking-effort-conflict-rectifier", + environment: "node", + testFiles: [ + "src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts", + "tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts", + ], + sourceFiles: ["src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts"], + thresholds: { lines: 80, functions: 80, branches: 70, statements: 80 }, +}); diff --git a/tests/unit/actions/system-config-thinking-effort-conflict-setting.test.ts b/tests/unit/actions/system-config-thinking-effort-conflict-setting.test.ts new file mode 100644 index 000000000..c1f980722 --- /dev/null +++ b/tests/unit/actions/system-config-thinking-effort-conflict-setting.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test, vi } from "vitest"; + +vi.mock("server-only", () => ({})); + +describe("enableThinkingEffortConflictRectifier system setting", () => { + test("defaults to enabled in the DB-row transformer", async () => { + const { toSystemSettings } = await import("@/repository/_shared/transformers"); + + expect(toSystemSettings(undefined).enableThinkingEffortConflictRectifier).toBe(true); + expect( + toSystemSettings({ id: 1, siteTitle: "Claude Code Hub" }) + .enableThinkingEffortConflictRectifier + ).toBe(true); + expect( + toSystemSettings({ id: 1, enableThinkingEffortConflictRectifier: false }) + .enableThinkingEffortConflictRectifier + ).toBe(false); + }); + + test("is accepted by the settings update validation schema", async () => { + const { UpdateSystemSettingsSchema } = await import("@/lib/validation/schemas"); + + const parsed = UpdateSystemSettingsSchema.parse({ + enableThinkingEffortConflictRectifier: false, + }); + expect(parsed.enableThinkingEffortConflictRectifier).toBe(false); + + const empty = UpdateSystemSettingsSchema.parse({}); + expect(empty.enableThinkingEffortConflictRectifier).toBeUndefined(); + }); + + test("is exposed by the v1 system settings response schema", async () => { + const { SystemSettingsSchema } = await import("@/lib/api/v1/schemas/system-config"); + + expect(Object.keys(SystemSettingsSchema.shape)).toContain( + "enableThinkingEffortConflictRectifier" + ); + }); +}); diff --git a/tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts b/tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts new file mode 100644 index 000000000..ad5bd8171 --- /dev/null +++ b/tests/unit/proxy/proxy-forwarder-thinking-effort-conflict-rectifier.test.ts @@ -0,0 +1,253 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; + +const mocks = vi.hoisted(() => { + return { + getCachedSystemSettings: vi.fn(async () => ({ + enableThinkingEffortConflictRectifier: true, + enableHighConcurrencyMode: false, + })), + recordSuccess: vi.fn(), + recordFailure: vi.fn(async () => {}), + getCircuitState: vi.fn(() => "closed"), + getProviderHealthInfo: vi.fn(async () => ({ + health: { failureCount: 0 }, + config: { failureThreshold: 3 }, + })), + updateMessageRequestDetails: vi.fn(async () => {}), + storeSessionSpecialSettings: vi.fn(async () => {}), + updateSessionBindingSmart: vi.fn(async () => ({ + updated: true, + reason: "first_success", + details: "mocked", + })), + updateSessionProvider: vi.fn(async () => {}), + }; +}); + +vi.mock("@/lib/config", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isHttp2Enabled: vi.fn(async () => false), + getCachedSystemSettings: mocks.getCachedSystemSettings, + }; +}); + +vi.mock("@/lib/circuit-breaker", () => ({ + getCircuitState: mocks.getCircuitState, + getProviderHealthInfo: mocks.getProviderHealthInfo, + recordFailure: mocks.recordFailure, + recordSuccess: mocks.recordSuccess, +})); + +vi.mock("@/repository/message", () => ({ + updateMessageRequestDetails: mocks.updateMessageRequestDetails, +})); + +vi.mock("@/lib/session-manager", () => ({ + SessionManager: { + storeSessionSpecialSettings: mocks.storeSessionSpecialSettings, + updateSessionBindingSmart: mocks.updateSessionBindingSmart, + updateSessionProvider: mocks.updateSessionProvider, + }, +})); + +import { ProxyError } from "@/app/v1/_lib/proxy/errors"; +import { ProxyForwarder } from "@/app/v1/_lib/proxy/forwarder"; +import { ProxySession } from "@/app/v1/_lib/proxy/session"; +import type { Provider } from "@/types/provider"; + +const DEEPSEEK_CONFLICT_ERROR = + 'Provider returned 400: Bad Request | Upstream: {"error":{"message":"thinking options type cannot be disabled when reasoning_effort is set","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}'; + +function createSession(): ProxySession { + const headers = new Headers(); + const session = Object.create(ProxySession.prototype); + + Object.assign(session, { + startTime: Date.now(), + method: "POST", + requestUrl: new URL("https://example.com/v1/messages"), + headers, + originalHeaders: new Headers(headers), + headerLog: JSON.stringify(Object.fromEntries(headers.entries())), + request: { + model: "deepseek-v4-pro", + log: "", + message: { + model: "deepseek-v4-pro", + thinking: { type: "disabled" }, + output_config: { effort: "max" }, + messages: [{ role: "user", content: [{ type: "text", text: "subagent task" }] }], + }, + }, + userAgent: null, + context: null, + clientAbortSignal: null, + userName: "test-user", + authState: { success: true, user: null, key: null, apiKey: null }, + provider: null, + messageContext: { id: 123, createdAt: new Date(), user: { id: 1 }, key: {}, apiKey: "k" }, + sessionId: null, + requestSequence: 1, + originalFormat: "claude", + providerType: null, + originalModelName: null, + originalUrlPathname: null, + providerChain: [], + cacheTtlResolved: null, + context1mApplied: false, + specialSettings: [], + cachedPriceData: undefined, + cachedBillingModelSource: undefined, + highConcurrencyModeEnabled: false, + isHeaderModified: () => false, + setHighConcurrencyModeEnabled(enabled: boolean) { + this.highConcurrencyModeEnabled = enabled; + }, + shouldPersistSessionDebugArtifacts() { + return !this.highConcurrencyModeEnabled; + }, + shouldTrackSessionObservability() { + return !this.highConcurrencyModeEnabled; + }, + }); + + return session as any; +} + +function createAnthropicProvider(): Provider { + return { + id: 1, + name: "deepseek-anthropic", + providerType: "claude", + url: "https://api.deepseek.com/anthropic/v1/messages", + key: "k", + preserveClientIp: false, + priority: 0, + } as unknown as Provider; +} + +function okResponse(): Response { + const body = JSON.stringify({ type: "message", content: [{ type: "text", text: "ok" }] }); + return new Response(body, { + status: 200, + headers: { "content-type": "application/json", "content-length": String(body.length) }, + }); +} + +describe("ProxyForwarder - thinking effort conflict rectifier", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.getCachedSystemSettings.mockResolvedValue({ + enableThinkingEffortConflictRectifier: true, + enableHighConcurrencyMode: false, + }); + }); + + test("命中 DeepSeek thinking/reasoning_effort 冲突 400 时应剥离 effort 字段并对同供应商重试一次", async () => { + const session = createSession(); + session.setProvider(createAnthropicProvider()); + + const doForward = vi.spyOn(ProxyForwarder as any, "doForward"); + + doForward.mockImplementationOnce(async () => { + throw new ProxyError(DEEPSEEK_CONFLICT_ERROR, 400, { + body: "", + providerId: 1, + providerName: "deepseek-anthropic", + }); + }); + + doForward.mockImplementationOnce(async (s: ProxySession) => { + const msg = s.request.message as any; + expect("output_config" in msg).toBe(false); + expect("reasoning_effort" in msg).toBe(false); + expect(msg.thinking).toEqual({ type: "disabled" }); + return okResponse(); + }); + + const response = await ProxyForwarder.send(session); + + expect(response.status).toBe(200); + expect(doForward).toHaveBeenCalledTimes(2); + expect(session.getProviderChain()?.length).toBeGreaterThanOrEqual(2); + + const special = JSON.stringify(session.getSpecialSettings()); + expect(special).toContain("thinking_effort_conflict_rectifier"); + expect(special).not.toContain("thinking_signature_rectifier"); + expect(mocks.updateMessageRequestDetails).toHaveBeenCalledTimes(1); + }); + + test("开关关闭时不整流也不重试", async () => { + mocks.getCachedSystemSettings.mockResolvedValue({ + enableThinkingEffortConflictRectifier: false, + enableHighConcurrencyMode: false, + }); + + const session = createSession(); + session.setProvider(createAnthropicProvider()); + + const doForward = vi.spyOn(ProxyForwarder as any, "doForward"); + doForward.mockImplementation(async () => { + throw new ProxyError(DEEPSEEK_CONFLICT_ERROR, 400, { + body: "", + providerId: 1, + providerName: "deepseek-anthropic", + }); + }); + + await expect(ProxyForwarder.send(session)).rejects.toThrow(); + + // 开关关闭:请求体不被整流(常规重试策略仍可能多次尝试,但都带原始字段) + const message = session.request.message as Record; + expect(message.output_config).toEqual({ effort: "max" }); + expect(JSON.stringify(session.getSpecialSettings() ?? [])).not.toContain( + "thinking_effort_conflict_rectifier" + ); + expect(doForward.mock.calls.length).toBeGreaterThanOrEqual(1); + }); + + test("请求体不含冲突字段时记录 not_applicable 且不重试", async () => { + const session = createSession(); + const message = session.request.message as Record; + delete message.output_config; + (message.thinking as Record).type = "enabled"; + session.setProvider(createAnthropicProvider()); + + const doForward = vi.spyOn(ProxyForwarder as any, "doForward"); + doForward.mockImplementation(async () => { + throw new ProxyError(DEEPSEEK_CONFLICT_ERROR, 400, { + body: "", + providerId: 1, + providerName: "deepseek-anthropic", + }); + }); + + await expect(ProxyForwarder.send(session)).rejects.toThrow(); + expect(doForward).toHaveBeenCalledTimes(1); + + const special = JSON.stringify(session.getSpecialSettings()); + expect(special).toContain("thinking_effort_conflict_rectifier"); + expect(special).toContain('"hit":false'); + }); + + test("同一供应商最多整流重试一次(第二次命中不再重试)", async () => { + const session = createSession(); + session.setProvider(createAnthropicProvider()); + + const doForward = vi.spyOn(ProxyForwarder as any, "doForward"); + doForward.mockImplementation(async (s: ProxySession) => { + // 即使整流移除了 effort 字段,上游仍持续返回同样错误 + void s; + throw new ProxyError(DEEPSEEK_CONFLICT_ERROR, 400, { + body: "", + providerId: 1, + providerName: "deepseek-anthropic", + }); + }); + + await expect(ProxyForwarder.send(session)).rejects.toThrow(); + expect(doForward).toHaveBeenCalledTimes(2); + }); +}); From 8d7c23e60d2f96f2935c862070ff683e7c555456 Mon Sep 17 00:00:00 2001 From: ding113 Date: Thu, 11 Jun 2026 15:17:23 +0800 Subject: [PATCH 2/3] fix: preserve output_config siblings and extend db fallback chain Add missing enableThinkingEffortConflictRectifier field to UpdateSystemSettingsInput to resolve a typecheck failure. Strip only the effort key from output_config in the thinking effort conflict rectifier instead of deleting the entire object, preserving any sibling configuration fields. Extend the system_settings database degradation fallback chain to handle missing enableThinkingEffortConflictRectifier columns in un-migrated databases for both read and update operations. --- ...thinking-effort-conflict-rectifier.test.ts | 28 ++++++ .../thinking-effort-conflict-rectifier.ts | 8 +- src/repository/system-config.ts | 85 +++++++++++++++---- src/types/system-config.ts | 3 + ...stem-config-update-missing-columns.test.ts | 52 ++++++++++++ 5 files changed, 160 insertions(+), 16 deletions(-) diff --git a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts index 983779e42..db01dad57 100644 --- a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts +++ b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.test.ts @@ -76,6 +76,34 @@ describe("rectifyThinkingEffortConflict", () => { expect(message.thinking).toEqual({ type: "disabled" }); }); + test("strips only effort and preserves sibling keys in output_config", () => { + const message: Record = { + thinking: { type: "disabled" }, + output_config: { effort: "max", verbosity: "high", future_flag: true }, + messages: [], + }; + + const result = rectifyThinkingEffortConflict(message); + + expect(result.applied).toBe(true); + expect(result.removedOutputConfig).toBe(true); + expect(result.effort).toBe("max"); + // Sibling fields must survive; only the conflicting effort carrier is removed. + expect(message.output_config).toEqual({ verbosity: "high", future_flag: true }); + }); + + test("drops output_config entirely when effort was its only key", () => { + const message: Record = { + thinking: { type: "disabled" }, + output_config: { effort: "max" }, + messages: [], + }; + + rectifyThinkingEffortConflict(message); + + expect("output_config" in message).toBe(false); + }); + test("removes a top-level reasoning_effort passthrough as well", () => { const message: Record = { thinking: { type: "disabled" }, diff --git a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts index c907ee6c5..a05110c73 100644 --- a/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts +++ b/src/app/v1/_lib/proxy/thinking-effort-conflict-rectifier.ts @@ -94,7 +94,13 @@ export function rectifyThinkingEffortConflict( if (outputConfigEffort !== undefined) { result.effort = typeof outputConfigEffort === "string" ? outputConfigEffort : null; - delete message.output_config; + // 仅剥离冲突的 effort 字段,保留 output_config 中的其他配置;若剥离后为空对象则整体移除。 + const { effort: _removedEffort, ...restOutputConfig } = outputConfig as Record; + if (Object.keys(restOutputConfig).length > 0) { + message.output_config = restOutputConfig; + } else { + delete message.output_config; + } result.removedOutputConfig = true; result.applied = true; } diff --git a/src/repository/system-config.ts b/src/repository/system-config.ts index 9dc7722ff..d3f6262b6 100644 --- a/src/repository/system-config.ts +++ b/src/repository/system-config.ts @@ -305,9 +305,33 @@ export async function getSystemSettings(): Promise { error, }); - // 最新降级:移除最近新增的 billHedgeLosers 列。 + // 最新降级:移除最近新增的 enableThinkingEffortConflictRectifier 列。 + const { + enableThinkingEffortConflictRectifier: _omitEffortConflict, + ...selectionWithoutEffortConflict + } = fullSelection; + + try { + const [row] = await db + .select(selectionWithoutEffortConflict) + .from(systemSettings) + .orderBy(asc(systemSettings.id)) + .limit(1); + return row ?? null; + } catch (effortConflictFallbackError) { + if (!isUndefinedColumnError(effortConflictFallbackError)) { + throw effortConflictFallbackError; + } + + logger.warn( + "system_settings 表除 enableThinkingEffortConflictRectifier 外仍有列缺失,继续回退到上一代字段集。", + { error: effortConflictFallbackError } + ); + } + + // 次新降级:移除 billHedgeLosers 列。 const { billHedgeLosers: _omitBillHedgeLosers, ...selectionWithoutBillHedgeLosers } = - fullSelection; + selectionWithoutEffortConflict; try { const [row] = await db @@ -856,26 +880,57 @@ export async function updateSystemSettings( error, }); - // 最新降级:移除最近新增的 billHedgeLosers 列。 - const { billHedgeLosers: _omitUpdateBillHedgeLosers, ...updatesWithoutBillHedgeLosers } = - updates; - const { billHedgeLosers: _omitReturningBillHedgeLosers, ...returningWithoutBillHedgeLosers } = - fullReturning; + // 最新降级:移除最近新增的 enableThinkingEffortConflictRectifier 列。 + const { + enableThinkingEffortConflictRectifier: _omitUpdateEffortConflict, + ...updatesWithoutEffortConflict + } = updates; + const { + enableThinkingEffortConflictRectifier: _omitReturningEffortConflict, + ...returningWithoutEffortConflict + } = fullReturning; try { [updated] = await executor .update(systemSettings) - .set(updatesWithoutBillHedgeLosers) + .set(updatesWithoutEffortConflict) .where(eq(systemSettings.id, current.id)) - .returning(returningWithoutBillHedgeLosers); - } catch (billHedgeLosersFallbackError) { - if (!isUndefinedColumnError(billHedgeLosersFallbackError)) { - throw billHedgeLosersFallbackError; + .returning(returningWithoutEffortConflict); + } catch (effortConflictFallbackError) { + if (!isUndefinedColumnError(effortConflictFallbackError)) { + throw effortConflictFallbackError; } - logger.warn("system_settings 表除 billHedgeLosers 外仍有列缺失,继续降级更新。", { - error: billHedgeLosersFallbackError, - }); + logger.warn( + "system_settings 表除 enableThinkingEffortConflictRectifier 外仍有列缺失,继续降级更新。", + { + error: effortConflictFallbackError, + } + ); + } + + // 次新降级:移除 billHedgeLosers 列。 + const { billHedgeLosers: _omitUpdateBillHedgeLosers, ...updatesWithoutBillHedgeLosers } = + updatesWithoutEffortConflict; + const { billHedgeLosers: _omitReturningBillHedgeLosers, ...returningWithoutBillHedgeLosers } = + returningWithoutEffortConflict; + + if (!updated) { + try { + [updated] = await executor + .update(systemSettings) + .set(updatesWithoutBillHedgeLosers) + .where(eq(systemSettings.id, current.id)) + .returning(returningWithoutBillHedgeLosers); + } catch (billHedgeLosersFallbackError) { + if (!isUndefinedColumnError(billHedgeLosersFallbackError)) { + throw billHedgeLosersFallbackError; + } + + logger.warn("system_settings 表除 billHedgeLosers 外仍有列缺失,继续降级更新。", { + error: billHedgeLosersFallbackError, + }); + } } // 次新降级:移除 billNonSuccessfulRequests 列。 diff --git a/src/types/system-config.ts b/src/types/system-config.ts index 86d15124f..d0d168ed7 100644 --- a/src/types/system-config.ts +++ b/src/types/system-config.ts @@ -201,6 +201,9 @@ export interface UpdateSystemSettingsInput { // thinking budget 整流器(可选) enableThinkingBudgetRectifier?: boolean; + // thinking effort 冲突整流器(可选) + enableThinkingEffortConflictRectifier?: boolean; + // billing header 整流器(可选) enableBillingHeaderRectifier?: boolean; diff --git a/tests/unit/repository/system-config-update-missing-columns.test.ts b/tests/unit/repository/system-config-update-missing-columns.test.ts index 20aaaa318..66a419655 100644 --- a/tests/unit/repository/system-config-update-missing-columns.test.ts +++ b/tests/unit/repository/system-config-update-missing-columns.test.ts @@ -291,6 +291,58 @@ describe("SystemSettings:数据库缺列时的保存兜底", () => { vi.useRealTimers(); }); + test("getSystemSettings 在仅缺 enable_thinking_effort_conflict_rectifier 新列时应降级读取并默认开启", async () => { + vi.resetModules(); + + const now = new Date("2026-01-04T00:00:00.000Z"); + vi.useFakeTimers(); + vi.setSystemTime(now); + + // 第一次 select(fullSelection) 因新列缺失而抛 42703; + // 第二次 select(selectionWithoutEffortConflict) 命中——验证新列已加入降级链最外层。 + const selectMock = vi + .fn() + .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" })) + .mockReturnValueOnce( + createThenableQuery([ + { + id: 1, + siteTitle: "Claude Code Hub", + allowGlobalUsageView: false, + currencyDisplay: "USD", + billingModelSource: "original", + codexPriorityBillingSource: "requested", + enableHttp2: true, + enableThinkingSignatureRectifier: true, + enableThinkingBudgetRectifier: true, + createdAt: now, + updatedAt: now, + }, + ]) + ); + + vi.doMock("@/drizzle/db", () => ({ + db: { + select: selectMock, + update: vi.fn(() => createThenableQuery([])), + insert: vi.fn(() => createThenableQuery([])), + execute: vi.fn(async () => ({ count: 0 })), + }, + })); + + const { getSystemSettings } = await import("@/repository/system-config"); + + const result = await getSystemSettings(); + + // 降级读取成功(未抛错),且缺失的新列经 transformer 默认开启。 + expect(selectMock).toHaveBeenCalledTimes(2); + expect(result.siteTitle).toBe("Claude Code Hub"); + expect(result.enableHttp2).toBe(true); + expect(result.enableThinkingEffortConflictRectifier).toBe(true); + + vi.useRealTimers(); + }); + test("getSystemSettings 在缺少新列且无记录时应使用降级插入初始化", async () => { vi.resetModules(); From ca35d6f5ec40e665c03cced3d77f3c98466cef80 Mon Sep 17 00:00:00 2001 From: ding113 Date: Thu, 11 Jun 2026 15:46:06 +0800 Subject: [PATCH 3/3] test(repository): assert stripped selection in config fallback guard The previous assertion only checked the transformer default value for the new column, which did not guarantee the fallback query actually stripped the correct column. The test now inspects the second select call to ensure the newly added column is removed while older fallback columns remain, catching regressions in the degradation chain order. --- .../system-config-update-missing-columns.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/unit/repository/system-config-update-missing-columns.test.ts b/tests/unit/repository/system-config-update-missing-columns.test.ts index 66a419655..addafccb0 100644 --- a/tests/unit/repository/system-config-update-missing-columns.test.ts +++ b/tests/unit/repository/system-config-update-missing-columns.test.ts @@ -334,11 +334,16 @@ describe("SystemSettings:数据库缺列时的保存兜底", () => { const result = await getSystemSettings(); - // 降级读取成功(未抛错),且缺失的新列经 transformer 默认开启。 + // 降级读取成功(未抛错)。 expect(selectMock).toHaveBeenCalledTimes(2); expect(result.siteTitle).toBe("Claude Code Hub"); expect(result.enableHttp2).toBe(true); - expect(result.enableThinkingEffortConflictRectifier).toBe(true); + + // 关键回归保护:第二次 select 必须恰好剥离了新列(最外层降级), + // 而非旧行为先剥离 billHedgeLosers。若新列未加入降级链最外层,下面两条断言会失败。 + const secondSelection = selectMock.mock.calls[1]?.[0] as Record; + expect(secondSelection).not.toHaveProperty("enableThinkingEffortConflictRectifier"); + expect(secondSelection).toHaveProperty("billHedgeLosers"); vi.useRealTimers(); });