diff --git a/drizzle/0062_aromatic_taskmaster.sql b/drizzle/0062_aromatic_taskmaster.sql new file mode 100644 index 000000000..dc6dd60b5 --- /dev/null +++ b/drizzle/0062_aromatic_taskmaster.sql @@ -0,0 +1 @@ +ALTER TABLE "providers" ADD COLUMN "gemini_google_search_preference" varchar(20); \ No newline at end of file diff --git a/drizzle/meta/0062_snapshot.json b/drizzle/meta/0062_snapshot.json new file mode 100644 index 000000000..f17865803 --- /dev/null +++ b/drizzle/meta/0062_snapshot.json @@ -0,0 +1,2961 @@ +{ + "id": "0bb539db-ea09-4259-9267-2d82d6bfb278", + "prevId": "ae09efb5-f924-4c72-b41f-baccc1765fcb", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "join_claude_pool": { + "name": "join_claude_pool", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "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_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index b6a6acf44..f73d3ea09 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -435,6 +435,13 @@ "when": 1769852342867, "tag": "0061_exotic_trauma", "breakpoints": true + }, + { + "idx": 62, + "version": "7", + "when": 1770185417918, + "tag": "0062_aromatic_taskmaster", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/settings/providers/form/sections.json b/messages/en/settings/providers/form/sections.json index 0a294e8ef..3b6c80cad 100644 --- a/messages/en/settings/providers/form/sections.json +++ b/messages/en/settings/providers/form/sections.json @@ -235,6 +235,18 @@ "maxOutButton": "Max Out (32000)" } }, + "geminiOverrides": { + "title": "Gemini Parameter Overrides", + "desc": "Override Gemini API request parameters at the provider level", + "googleSearch": { + "label": "Google Search (Web Access)", + "options": { + "inherit": "No override (follow client)", + "enabled": "Force enabled", + "disabled": "Force disabled" + } + } + }, "context1m": { "desc": "Configure 1M context window support. Only affects Sonnet models (claude-sonnet-4-5, claude-sonnet-4). Tiered pricing applies when enabled.", "label": "1M Context Window", diff --git a/messages/ja/settings/providers/form/sections.json b/messages/ja/settings/providers/form/sections.json index 1dbd9ca52..c858939e8 100644 --- a/messages/ja/settings/providers/form/sections.json +++ b/messages/ja/settings/providers/form/sections.json @@ -236,6 +236,18 @@ "maxOutButton": "最大化 (32000)" } }, + "geminiOverrides": { + "title": "Gemini パラメータオーバーライド", + "desc": "プロバイダーレベルで Gemini API リクエストパラメータを上書きします", + "googleSearch": { + "label": "Google Search(ウェブアクセス)", + "options": { + "inherit": "上書きなし(クライアントに従う)", + "enabled": "強制有効", + "disabled": "強制無効" + } + } + }, "context1m": { "desc": "1M コンテキストウィンドウのサポートを設定します。Sonnet モデル(claude-sonnet-4-5、claude-sonnet-4)にのみ適用されます。有効時は段階的料金が適用されます。", "label": "1M コンテキストウィンドウ", diff --git a/messages/ru/settings/providers/form/sections.json b/messages/ru/settings/providers/form/sections.json index c621c3c8d..29982110b 100644 --- a/messages/ru/settings/providers/form/sections.json +++ b/messages/ru/settings/providers/form/sections.json @@ -236,6 +236,18 @@ "maxOutButton": "Максимум (32000)" } }, + "geminiOverrides": { + "title": "Переопределение параметров Gemini", + "desc": "Переопределение параметров запроса Gemini API на уровне провайдера", + "googleSearch": { + "label": "Google Search (веб-доступ)", + "options": { + "inherit": "Без переопределения (следовать клиенту)", + "enabled": "Принудительно включено", + "disabled": "Принудительно отключено" + } + } + }, "context1m": { "desc": "Настройка поддержки контекстного окна 1M. Применяется только к моделям Sonnet (claude-sonnet-4-5, claude-sonnet-4). При включении применяется многоуровневая тарификация.", "label": "Контекстное окно 1M", diff --git a/messages/zh-CN/settings/providers/form/sections.json b/messages/zh-CN/settings/providers/form/sections.json index 1c2830a0f..47588abb8 100644 --- a/messages/zh-CN/settings/providers/form/sections.json +++ b/messages/zh-CN/settings/providers/form/sections.json @@ -151,6 +151,18 @@ "placeholder": "如 10240", "maxOutButton": "拉满 (32000)" } + }, + "geminiOverrides": { + "title": "Gemini 参数覆写", + "desc": "在供应商层面覆写 Gemini API 请求参数", + "googleSearch": { + "label": "Google Search 联网", + "options": { + "inherit": "不覆写(遵循客户端)", + "enabled": "强制开启", + "disabled": "强制关闭" + } + } } }, "rateLimit": { diff --git a/messages/zh-TW/settings/providers/form/sections.json b/messages/zh-TW/settings/providers/form/sections.json index 43374cea5..b2099df93 100644 --- a/messages/zh-TW/settings/providers/form/sections.json +++ b/messages/zh-TW/settings/providers/form/sections.json @@ -236,6 +236,18 @@ "maxOutButton": "拉滿 (32000)" } }, + "geminiOverrides": { + "title": "Gemini 參數覆寫", + "desc": "在供應商層級覆寫 Gemini API 請求參數", + "googleSearch": { + "label": "Google Search 聯網", + "options": { + "inherit": "不覆寫(遵循客戶端)", + "enabled": "強制開啟", + "disabled": "強制關閉" + } + } + }, "context1m": { "desc": "設定 1M 上下文視窗支援。僅對 Sonnet 模型生效(claude-sonnet-4-5、claude-sonnet-4)。啟用後將套用階梯定價。", "label": "1M 上下文視窗", diff --git a/src/actions/providers.ts b/src/actions/providers.ts index 536be1fda..0cb58ddc8 100644 --- a/src/actions/providers.ts +++ b/src/actions/providers.ts @@ -280,6 +280,7 @@ export async function getProviders(): Promise { codexParallelToolCallsPreference: provider.codexParallelToolCallsPreference, anthropicMaxTokensPreference: provider.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: provider.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: provider.geminiGoogleSearchPreference, tpm: provider.tpm, rpm: provider.rpm, rpd: provider.rpd, diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx index d90377849..4e81c886a 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx @@ -208,6 +208,7 @@ function ProviderFormContent({ codex_parallel_tool_calls_preference: state.routing.codexParallelToolCallsPreference, anthropic_max_tokens_preference: state.routing.anthropicMaxTokensPreference, anthropic_thinking_budget_preference: state.routing.anthropicThinkingBudgetPreference, + gemini_google_search_preference: state.routing.geminiGoogleSearchPreference, limit_5h_usd: state.rateLimit.limit5hUsd, limit_daily_usd: state.rateLimit.limitDailyUsd, daily_reset_mode: state.rateLimit.dailyResetMode, diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx index 09ba45811..5b80ae250 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx @@ -61,6 +61,7 @@ export function createInitialState( anthropicMaxTokensPreference: sourceProvider?.anthropicMaxTokensPreference ?? "inherit", anthropicThinkingBudgetPreference: sourceProvider?.anthropicThinkingBudgetPreference ?? "inherit", + geminiGoogleSearchPreference: sourceProvider?.geminiGoogleSearchPreference ?? "inherit", }, rateLimit: { limit5hUsd: sourceProvider?.limit5hUsd ?? null, @@ -178,6 +179,11 @@ export function providerFormReducer( ...state, routing: { ...state.routing, anthropicThinkingBudgetPreference: action.payload }, }; + case "SET_GEMINI_GOOGLE_SEARCH": + return { + ...state, + routing: { ...state.routing, geminiGoogleSearchPreference: action.payload }, + }; // Rate limit case "SET_LIMIT_5H_USD": diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts index 321cfc1e1..27f9203b2 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts @@ -6,6 +6,7 @@ import type { CodexReasoningEffortPreference, CodexReasoningSummaryPreference, CodexTextVerbosityPreference, + GeminiGoogleSearchPreference, McpPassthroughType, ProviderDisplay, ProviderType, @@ -51,6 +52,8 @@ export interface RoutingState { // Anthropic-specific anthropicMaxTokensPreference: AnthropicMaxTokensPreference; anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference; + // Gemini-specific + geminiGoogleSearchPreference: GeminiGoogleSearchPreference; } export interface RateLimitState { @@ -125,6 +128,7 @@ export type ProviderFormAction = | { type: "SET_CODEX_PARALLEL_TOOL_CALLS"; payload: CodexParallelToolCallsPreference } | { type: "SET_ANTHROPIC_MAX_TOKENS"; payload: AnthropicMaxTokensPreference } | { type: "SET_ANTHROPIC_THINKING_BUDGET"; payload: AnthropicThinkingBudgetPreference } + | { type: "SET_GEMINI_GOOGLE_SEARCH"; payload: GeminiGoogleSearchPreference } // Rate limit actions | { type: "SET_LIMIT_5H_USD"; payload: number | null } | { type: "SET_LIMIT_DAILY_USD"; payload: number | null } diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx index 447efcdc6..e93e950bb 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx @@ -22,6 +22,7 @@ import type { CodexReasoningEffortPreference, CodexReasoningSummaryPreference, CodexTextVerbosityPreference, + GeminiGoogleSearchPreference, ProviderType, } from "@/types/provider"; import { ModelMultiSelect } from "../../../model-multi-select"; @@ -654,6 +655,42 @@ export function RoutingSection() { )} + + {/* Gemini Overrides - Gemini type only */} + {(state.routing.providerType === "gemini" || + state.routing.providerType === "gemini-cli") && ( + + + + + + )} ); diff --git a/src/app/v1/_lib/proxy/forwarder.ts b/src/app/v1/_lib/proxy/forwarder.ts index 7c6c4420d..2c592a6ac 100644 --- a/src/app/v1/_lib/proxy/forwarder.ts +++ b/src/app/v1/_lib/proxy/forwarder.ts @@ -15,6 +15,7 @@ import { getCachedSystemSettings, isHttp2Enabled } from "@/lib/config"; import { getEnvConfig } from "@/lib/config/env.schema"; import { PROVIDER_DEFAULTS, PROVIDER_LIMITS } from "@/lib/constants/provider.constants"; import { recordEndpointFailure, recordEndpointSuccess } from "@/lib/endpoint-circuit-breaker"; +import { applyGeminiGoogleSearchOverrideWithAudit } from "@/lib/gemini/provider-overrides"; import { logger } from "@/lib/logger"; import { getPreferredProviderEndpoints } from "@/lib/provider-endpoints/endpoint-selector"; import { getGlobalAgentPool, getProxyAgentForProvider } from "@/lib/proxy-agent"; @@ -1283,7 +1284,43 @@ export class ProxyForwarder { // 1. 直接透传请求体(不转换)- 仅对有 body 的请求 const hasBody = session.method !== "GET" && session.method !== "HEAD"; if (hasBody) { - const bodyString = JSON.stringify(session.request.message); + let bodyToSerialize = session.request.message as Record; + + // Apply Gemini Google Search override if configured + const { request: overriddenBody, audit: googleSearchAudit } = + applyGeminiGoogleSearchOverrideWithAudit(provider, bodyToSerialize); + if (googleSearchAudit) { + session.addSpecialSetting(googleSearchAudit); + bodyToSerialize = overriddenBody; + session.request.message = overriddenBody; + + // Persist special settings immediately (same pattern as Anthropic overrides) + const specialSettings = session.getSpecialSettings(); + if (session.sessionId) { + await SessionManager.storeSessionSpecialSettings( + session.sessionId, + specialSettings, + session.requestSequence + ).catch((err) => { + logger.error("[ProxyForwarder] Failed to store Gemini special settings", { + error: err, + sessionId: session.sessionId, + }); + }); + } + if (session.messageContext?.id) { + await updateMessageRequestDetails(session.messageContext.id, { + specialSettings, + }).catch((err) => { + logger.error("[ProxyForwarder] Failed to persist Gemini special settings", { + error: err, + messageRequestId: session.messageContext?.id, + }); + }); + } + } + + const bodyString = JSON.stringify(bodyToSerialize); requestBody = bodyString; } diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index 800f1a288..087c496fc 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -282,6 +282,12 @@ export const providers = pgTable('providers', { anthropicMaxTokensPreference: varchar('anthropic_max_tokens_preference', { length: 20 }), anthropicThinkingBudgetPreference: varchar('anthropic_thinking_budget_preference', { length: 20 }), + // Gemini (generateContent API) parameter overrides (only for gemini/gemini-cli providers) + // - 'inherit' or null: follow client request + // - 'enabled': force inject googleSearch tool + // - 'disabled': force remove googleSearch tool from request + geminiGoogleSearchPreference: varchar('gemini_google_search_preference', { length: 20 }), + // 废弃(保留向后兼容,但不再使用) tpm: integer('tpm').default(0), rpm: integer('rpm').default(0), diff --git a/src/lib/gemini/provider-overrides.ts b/src/lib/gemini/provider-overrides.ts new file mode 100644 index 000000000..04555a7a6 --- /dev/null +++ b/src/lib/gemini/provider-overrides.ts @@ -0,0 +1,138 @@ +import type { GeminiGoogleSearchPreference, ProviderType } from "@/types/provider"; +import type { GeminiGoogleSearchOverrideSpecialSetting } from "@/types/special-settings"; + +type GeminiProviderOverrideConfig = { + id?: number; + name?: string; + providerType?: ProviderType; + geminiGoogleSearchPreference?: GeminiGoogleSearchPreference | null; +}; + +function isPlainObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** + * Check if a tools array contains a googleSearch tool + */ +function hasGoogleSearchTool(tools: unknown[]): boolean { + return tools.some((tool) => { + if (!isPlainObject(tool)) return false; + return "googleSearch" in tool; + }); +} + +/** + * Remove googleSearch tools from a tools array + */ +function removeGoogleSearchTools(tools: unknown[]): unknown[] { + return tools.filter((tool) => { + if (!isPlainObject(tool)) return true; + return !("googleSearch" in tool); + }); +} + +/** + * Apply Gemini Google Search provider override to request body. + * + * Conventions: + * - providerType !== "gemini" && providerType !== "gemini-cli" -> no processing + * - Preference value null/undefined/"inherit" means "follow client" + * - Overrides affect: + * - tools array (inject or remove googleSearch tool) + */ +export function applyGeminiGoogleSearchOverride( + provider: GeminiProviderOverrideConfig, + request: Record +): Record { + if (provider.providerType !== "gemini" && provider.providerType !== "gemini-cli") { + return request; + } + + const preference = provider.geminiGoogleSearchPreference; + + // inherit or not set -> pass through unchanged + if (!preference || preference === "inherit") { + return request; + } + + let output: Record = request; + const ensureCloned = () => { + if (output === request) { + output = { ...request }; + } + }; + + const existingTools = Array.isArray(request.tools) ? request.tools : []; + const hadGoogleSearch = hasGoogleSearchTool(existingTools); + + if (preference === "enabled") { + // Force inject googleSearch tool if not present + if (!hadGoogleSearch) { + ensureCloned(); + output.tools = [...existingTools, { googleSearch: {} }]; + } + } else if (preference === "disabled") { + // Force remove googleSearch tool if present + if (hadGoogleSearch) { + ensureCloned(); + const filteredTools = removeGoogleSearchTools(existingTools); + if (filteredTools.length > 0) { + output.tools = filteredTools; + } else { + // Remove tools array entirely if empty after filtering + delete output.tools; + } + } + } + + return output; +} + +/** + * Apply Gemini Google Search override with audit trail + */ +export function applyGeminiGoogleSearchOverrideWithAudit( + provider: GeminiProviderOverrideConfig, + request: Record +): { request: Record; audit: GeminiGoogleSearchOverrideSpecialSetting | null } { + if (provider.providerType !== "gemini" && provider.providerType !== "gemini-cli") { + return { request, audit: null }; + } + + const preference = provider.geminiGoogleSearchPreference; + + // inherit or not set -> pass through unchanged, no audit + if (!preference || preference === "inherit") { + return { request, audit: null }; + } + + const existingTools = Array.isArray(request.tools) ? request.tools : []; + const hadGoogleSearch = hasGoogleSearchTool(existingTools); + + // Determine action based on preference and current state + let action: "inject" | "remove" | "passthrough"; + if (preference === "enabled") { + action = hadGoogleSearch ? "passthrough" : "inject"; + } else if (preference === "disabled") { + action = hadGoogleSearch ? "remove" : "passthrough"; + } else { + // Unreachable with current validation, but handle gracefully + return { request, audit: null }; + } + + const nextRequest = applyGeminiGoogleSearchOverride(provider, request); + + const audit: GeminiGoogleSearchOverrideSpecialSetting = { + type: "gemini_google_search_override", + scope: "request", + hit: true, + providerId: provider.id ?? null, + providerName: provider.name ?? null, + action, + preference: preference as "enabled" | "disabled", + hadGoogleSearchInRequest: hadGoogleSearch, + }; + + return { request: nextRequest, audit }; +} diff --git a/src/lib/utils/special-settings.ts b/src/lib/utils/special-settings.ts index 322df538c..bc3cfdad2 100644 --- a/src/lib/utils/special-settings.ts +++ b/src/lib/utils/special-settings.ts @@ -88,6 +88,15 @@ function buildSettingKey(setting: SpecialSetting): string { setting.after.maxTokens, setting.after.thinkingBudgetTokens, ]); + case "gemini_google_search_override": + return JSON.stringify([ + setting.type, + setting.hit, + setting.providerId ?? null, + setting.action, + setting.preference, + setting.hadGoogleSearchInRequest, + ]); default: { // 兜底:保证即使未来扩展类型也不会导致运行时崩溃 const _exhaustive: never = setting; diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index c38c16c38..fe6176ddc 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -55,6 +55,12 @@ const ANTHROPIC_THINKING_BUDGET_PREFERENCE = z.union([ ), ]); +// Gemini (generateContent API) Google Search preference +// - 'inherit': follow client request (default) +// - 'enabled': force inject googleSearch tool +// - 'disabled': force remove googleSearch tool from request +const GEMINI_GOOGLE_SEARCH_PREFERENCE = z.enum(["inherit", "enabled", "disabled"]); + /** * 用户创建数据验证schema */ @@ -477,6 +483,7 @@ export const CreateProviderSchema = z anthropic_max_tokens_preference: ANTHROPIC_MAX_TOKENS_PREFERENCE.optional().default("inherit"), anthropic_thinking_budget_preference: ANTHROPIC_THINKING_BUDGET_PREFERENCE.optional().default("inherit"), + gemini_google_search_preference: GEMINI_GOOGLE_SEARCH_PREFERENCE.optional().default("inherit"), max_retry_attempts: z.coerce .number() .int("重试次数必须是整数") @@ -670,6 +677,7 @@ export const UpdateProviderSchema = z codex_parallel_tool_calls_preference: CODEX_PARALLEL_TOOL_CALLS_PREFERENCE.optional(), anthropic_max_tokens_preference: ANTHROPIC_MAX_TOKENS_PREFERENCE.optional(), anthropic_thinking_budget_preference: ANTHROPIC_THINKING_BUDGET_PREFERENCE.optional(), + gemini_google_search_preference: GEMINI_GOOGLE_SEARCH_PREFERENCE.optional(), max_retry_attempts: z.coerce .number() .int("重试次数必须是整数") diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index 479952807..781eec38f 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -125,6 +125,7 @@ export function toProvider(dbProvider: any): Provider { codexParallelToolCallsPreference: dbProvider?.codexParallelToolCallsPreference ?? null, anthropicMaxTokensPreference: dbProvider?.anthropicMaxTokensPreference ?? null, anthropicThinkingBudgetPreference: dbProvider?.anthropicThinkingBudgetPreference ?? null, + geminiGoogleSearchPreference: dbProvider?.geminiGoogleSearchPreference ?? null, tpm: dbProvider?.tpm ?? null, rpm: dbProvider?.rpm ?? null, rpd: dbProvider?.rpd ?? null, diff --git a/src/repository/provider.ts b/src/repository/provider.ts index ffea9b8b3..4eaa9cf23 100644 --- a/src/repository/provider.ts +++ b/src/repository/provider.ts @@ -71,6 +71,7 @@ export async function createProvider(providerData: CreateProviderData): Promise< codexParallelToolCallsPreference: providerData.codex_parallel_tool_calls_preference ?? null, anthropicMaxTokensPreference: providerData.anthropic_max_tokens_preference ?? null, anthropicThinkingBudgetPreference: providerData.anthropic_thinking_budget_preference ?? null, + geminiGoogleSearchPreference: providerData.gemini_google_search_preference ?? null, tpm: providerData.tpm, rpm: providerData.rpm, rpd: providerData.rpd, @@ -122,6 +123,7 @@ export async function createProvider(providerData: CreateProviderData): Promise< codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: providers.geminiGoogleSearchPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -202,6 +204,7 @@ export async function findProviderList( codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: providers.geminiGoogleSearchPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -278,6 +281,7 @@ export async function findAllProvidersFresh(): Promise { codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: providers.geminiGoogleSearchPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -358,6 +362,7 @@ export async function findProviderById(id: number): Promise { codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: providers.geminiGoogleSearchPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, @@ -466,6 +471,8 @@ export async function updateProvider( if (providerData.anthropic_thinking_budget_preference !== undefined) dbData.anthropicThinkingBudgetPreference = providerData.anthropic_thinking_budget_preference ?? null; + if (providerData.gemini_google_search_preference !== undefined) + dbData.geminiGoogleSearchPreference = providerData.gemini_google_search_preference ?? null; if (providerData.tpm !== undefined) dbData.tpm = providerData.tpm; if (providerData.rpm !== undefined) dbData.rpm = providerData.rpm; if (providerData.rpd !== undefined) dbData.rpd = providerData.rpd; @@ -546,6 +553,7 @@ export async function updateProvider( codexParallelToolCallsPreference: providers.codexParallelToolCallsPreference, anthropicMaxTokensPreference: providers.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: providers.anthropicThinkingBudgetPreference, + geminiGoogleSearchPreference: providers.geminiGoogleSearchPreference, tpm: providers.tpm, rpm: providers.rpm, rpd: providers.rpd, diff --git a/src/types/provider.ts b/src/types/provider.ts index 1b2bdcf65..24ee03ba3 100644 --- a/src/types/provider.ts +++ b/src/types/provider.ts @@ -36,6 +36,12 @@ export type CodexParallelToolCallsPreference = "inherit" | "true" | "false"; export type AnthropicMaxTokensPreference = "inherit" | string; export type AnthropicThinkingBudgetPreference = "inherit" | string; +// Gemini (generateContent API) parameter overrides +// - "inherit": follow client request (default) +// - "enabled": force inject googleSearch tool +// - "disabled": force remove googleSearch tool from request +export type GeminiGoogleSearchPreference = "inherit" | "enabled" | "disabled"; + // MCP 透传类型枚举 export type McpPassthroughType = "none" | "minimax" | "glm" | "custom"; @@ -128,6 +134,9 @@ export interface Provider { anthropicMaxTokensPreference: AnthropicMaxTokensPreference | null; anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference | null; + // Gemini (generateContent API) parameter overrides (only for gemini/gemini-cli providers) + geminiGoogleSearchPreference: GeminiGoogleSearchPreference | null; + // 废弃(保留向后兼容,但不再使用) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 tpm: number | null; @@ -200,6 +209,7 @@ export interface ProviderDisplay { codexParallelToolCallsPreference: CodexParallelToolCallsPreference | null; anthropicMaxTokensPreference: AnthropicMaxTokensPreference | null; anthropicThinkingBudgetPreference: AnthropicThinkingBudgetPreference | null; + geminiGoogleSearchPreference: GeminiGoogleSearchPreference | null; // 废弃字段(保留向后兼容) tpm: number | null; rpm: number | null; @@ -288,6 +298,7 @@ export interface CreateProviderData { codex_parallel_tool_calls_preference?: CodexParallelToolCallsPreference | null; anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; + gemini_google_search_preference?: GeminiGoogleSearchPreference | null; // 废弃字段(保留向后兼容) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 @@ -358,6 +369,7 @@ export interface UpdateProviderData { codex_parallel_tool_calls_preference?: CodexParallelToolCallsPreference | null; anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; + gemini_google_search_preference?: GeminiGoogleSearchPreference | null; // 废弃字段(保留向后兼容) // TPM (Tokens Per Minute): 每分钟可处理的文本总量 diff --git a/src/types/special-settings.ts b/src/types/special-settings.ts index a5294dd0f..8d77f78be 100644 --- a/src/types/special-settings.ts +++ b/src/types/special-settings.ts @@ -13,7 +13,8 @@ export type SpecialSetting = | ThinkingBudgetRectifierSpecialSetting | CodexSessionIdCompletionSpecialSetting | AnthropicCacheTtlHeaderOverrideSpecialSetting - | AnthropicContext1mHeaderOverrideSpecialSetting; + | AnthropicContext1mHeaderOverrideSpecialSetting + | GeminiGoogleSearchOverrideSpecialSetting; export type SpecialSettingChangeValue = string | number | boolean | null; @@ -154,3 +155,20 @@ export type ThinkingBudgetRectifierSpecialSetting = { thinkingBudgetTokens: number | null; }; }; + +/** + * Gemini Google Search 覆写审计 + * + * 用于记录:当 Gemini 类型供应商配置了 googleSearch 偏好时, + * 系统对请求体中 tools 数组进行注入或移除 googleSearch 工具的行为。 + */ +export type GeminiGoogleSearchOverrideSpecialSetting = { + type: "gemini_google_search_override"; + scope: "request"; + hit: boolean; + providerId: number | null; + providerName: string | null; + action: "inject" | "remove" | "passthrough"; + preference: "enabled" | "disabled"; + hadGoogleSearchInRequest: boolean; +}; diff --git a/tests/unit/lib/gemini/provider-overrides.test.ts b/tests/unit/lib/gemini/provider-overrides.test.ts new file mode 100644 index 000000000..b7d214621 --- /dev/null +++ b/tests/unit/lib/gemini/provider-overrides.test.ts @@ -0,0 +1,329 @@ +import { describe, expect, test } from "vitest"; +import { + applyGeminiGoogleSearchOverride, + applyGeminiGoogleSearchOverrideWithAudit, +} from "@/lib/gemini/provider-overrides"; + +describe("applyGeminiGoogleSearchOverride", () => { + describe("non-Gemini providers", () => { + test("should return unchanged request for claude provider", () => { + const provider = { providerType: "claude" }; + const request = { tools: [{ codeExecution: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + + test("should return unchanged request for codex provider", () => { + const provider = { providerType: "codex", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + }); + + describe("inherit preference", () => { + test("should pass through unchanged when preference is inherit", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "inherit" }; + const request = { contents: [], tools: [{ googleSearch: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + + test("should pass through unchanged when preference is null", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: null }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + + test("should pass through unchanged when preference is undefined", () => { + const provider = { providerType: "gemini" }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + }); + + describe("enabled preference", () => { + test("should inject googleSearch tool when not present", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).not.toBe(request); + expect(result.tools).toEqual([{ googleSearch: {} }]); + }); + + test("should inject googleSearch tool alongside existing tools", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [], tools: [{ codeExecution: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).not.toBe(request); + expect(result.tools).toEqual([{ codeExecution: {} }, { googleSearch: {} }]); + }); + + test("should not duplicate googleSearch if already present", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [], tools: [{ googleSearch: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + expect(result.tools).toEqual([{ googleSearch: {} }]); + }); + + test("should work with gemini-cli provider type", () => { + const provider = { providerType: "gemini-cli", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result.tools).toEqual([{ googleSearch: {} }]); + }); + }); + + describe("disabled preference", () => { + test("should remove googleSearch tool when present", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "disabled" }; + const request = { contents: [], tools: [{ googleSearch: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).not.toBe(request); + expect(result.tools).toBeUndefined(); + }); + + test("should preserve other tools when removing googleSearch", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "disabled" }; + const request = { + contents: [], + tools: [{ codeExecution: {} }, { googleSearch: {} }, { functionDeclarations: [] }], + }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).not.toBe(request); + expect(result.tools).toEqual([{ codeExecution: {} }, { functionDeclarations: [] }]); + }); + + test("should pass through unchanged when googleSearch not present", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "disabled" }; + const request = { contents: [], tools: [{ codeExecution: {} }] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + + test("should pass through unchanged when no tools array", () => { + const provider = { providerType: "gemini", geminiGoogleSearchPreference: "disabled" }; + const request = { contents: [] }; + + const result = applyGeminiGoogleSearchOverride(provider, request); + + expect(result).toBe(request); + }); + }); +}); + +describe("applyGeminiGoogleSearchOverrideWithAudit", () => { + describe("non-Gemini providers", () => { + test("should return null audit for non-Gemini provider", () => { + const provider = { providerType: "claude", geminiGoogleSearchPreference: "enabled" }; + const request = { contents: [] }; + + const { request: result, audit } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request + ); + + expect(result).toBe(request); + expect(audit).toBeNull(); + }); + }); + + describe("inherit preference", () => { + test("should return null audit when preference is inherit", () => { + const provider = { + id: 1, + name: "Test Gemini", + providerType: "gemini", + geminiGoogleSearchPreference: "inherit", + }; + const request = { contents: [] }; + + const { audit } = applyGeminiGoogleSearchOverrideWithAudit(provider, request); + + expect(audit).toBeNull(); + }); + }); + + describe("enabled preference", () => { + test("should return inject audit when googleSearch is injected", () => { + const provider = { + id: 1, + name: "Test Gemini", + providerType: "gemini", + geminiGoogleSearchPreference: "enabled", + }; + const request = { contents: [] }; + + const { request: result, audit } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request + ); + + expect(result.tools).toEqual([{ googleSearch: {} }]); + expect(audit).toEqual({ + type: "gemini_google_search_override", + scope: "request", + hit: true, + providerId: 1, + providerName: "Test Gemini", + action: "inject", + preference: "enabled", + hadGoogleSearchInRequest: false, + }); + }); + + test("should return passthrough audit when googleSearch already present", () => { + const provider = { + id: 2, + name: "Gemini Pro", + providerType: "gemini", + geminiGoogleSearchPreference: "enabled", + }; + const request = { contents: [], tools: [{ googleSearch: {} }] }; + + const { request: result, audit } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request + ); + + expect(result).toBe(request); + expect(audit).toEqual({ + type: "gemini_google_search_override", + scope: "request", + hit: true, + providerId: 2, + providerName: "Gemini Pro", + action: "passthrough", + preference: "enabled", + hadGoogleSearchInRequest: true, + }); + }); + }); + + describe("disabled preference", () => { + test("should return remove audit when googleSearch is removed", () => { + const provider = { + id: 3, + name: "Gemini Flash", + providerType: "gemini", + geminiGoogleSearchPreference: "disabled", + }; + const request = { contents: [], tools: [{ googleSearch: {} }] }; + + const { request: result, audit } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request + ); + + expect(result.tools).toBeUndefined(); + expect(audit).toEqual({ + type: "gemini_google_search_override", + scope: "request", + hit: true, + providerId: 3, + providerName: "Gemini Flash", + action: "remove", + preference: "disabled", + hadGoogleSearchInRequest: true, + }); + }); + + test("should return passthrough audit when no googleSearch to remove", () => { + const provider = { + id: 4, + providerType: "gemini-cli", + geminiGoogleSearchPreference: "disabled", + }; + const request = { contents: [], tools: [{ codeExecution: {} }] }; + + const { request: result, audit } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request + ); + + expect(result).toBe(request); + expect(audit).toEqual({ + type: "gemini_google_search_override", + scope: "request", + hit: true, + providerId: 4, + providerName: null, + action: "passthrough", + preference: "disabled", + hadGoogleSearchInRequest: false, + }); + }); + }); + + describe("edge cases", () => { + test("should handle missing provider id and name", () => { + const provider = { + providerType: "gemini", + geminiGoogleSearchPreference: "enabled", + }; + const request = { contents: [] }; + + const { audit } = applyGeminiGoogleSearchOverrideWithAudit(provider, request); + + expect(audit?.providerId).toBeNull(); + expect(audit?.providerName).toBeNull(); + }); + + test("should handle non-plain object tools", () => { + const provider = { + providerType: "gemini", + geminiGoogleSearchPreference: "disabled", + }; + const request = { contents: [], tools: ["string-tool", 123, null] }; + + const { request: result } = applyGeminiGoogleSearchOverrideWithAudit( + provider, + request as unknown as Record + ); + + expect(result).toBe(request); + }); + + test("should handle googleSearch with extra properties", () => { + const provider = { + providerType: "gemini", + geminiGoogleSearchPreference: "disabled", + }; + const request = { + contents: [], + tools: [{ googleSearch: { dynamicRetrievalConfig: { threshold: 0.5 } } }], + }; + + const { request: result } = applyGeminiGoogleSearchOverrideWithAudit(provider, request); + + expect(result.tools).toBeUndefined(); + }); + }); +});