diff --git a/.env.example b/.env.example index b6c6d72b8..177141c29 100644 --- a/.env.example +++ b/.env.example @@ -97,6 +97,7 @@ ENABLE_API_KEY_REDIS_CACHE="true" # 是否启用 API Key Redis 缓存( # 降低该值会按签发时间收紧已签发 ADMIN_TOKEN 签名 cookie 的剩余寿命,且不会延长其原始 exp。 AUTH_SESSION_TTL_SECONDS=604800 # Web UI 登录态过期时间(秒,默认 604800 = 7 天,范围 60-31536000) SESSION_TTL=300 # 代理请求上下文缓存时间(秒,默认 300 = 5 分钟;不控制 Web UI 登录态) +DISCOVERY_ROLLOUT_PERCENT=100 # Discovery 运维灰度比例(0-100,按 API Key + Session 稳定分桶) STORE_SESSION_MESSAGES=false # 会话消息存储模式(默认:false) # - false:存储请求/响应体但对 message 内容脱敏 [REDACTED] # - true:原样存储 message 内容(注意隐私和存储空间影响) diff --git a/docs/streaming-discovery.md b/docs/streaming-discovery.md new file mode 100644 index 000000000..1c6d221e5 --- /dev/null +++ b/docs/streaming-discovery.md @@ -0,0 +1,71 @@ +# Bounded Streaming Discovery + +Bounded Discovery is an optional cold-start routing mode for streaming +requests. It exists to reduce duplicate upstream spend while preserving a +working request when the first provider is slow. + +## Defaults + +| Setting | Default | Meaning | +| --- | ---: | --- | +| `discoveryEnabled` | `false` | Keep the existing Hedge path until explicitly enabled. | +| `discoveryConcurrency` | `2` | Number of normal providers in the first batch. | +| `maxDiscoveryRounds` | `2` | Maximum Discovery rounds. | +| `discoverySlaMs` | `10000` | First-byte budget for a Discovery round. | +| `stickySlaMs` | `20000` | First-byte budget for an existing Sticky provider. | +| `racingTotalTimeoutMs` | `60000` | Total pre-winner deadline; it is cleared after a winner is committed. | +| `stickyTimeoutCooldownMs` | `300000` | Session/provider cooldown after a Sticky timeout. | + +The total deadline must be at least `stickySlaMs + maxDiscoveryRounds * +discoverySlaMs`. The UI and API reject configurations that do not satisfy +this relationship. + +## Request lifecycle + +- A healthy Sticky provider is probed alone. If it times out, it becomes the + single fallback for this request and receives a cooldown; a later request + may select it again after the cooldown. +- A cold start launches the configured initial normal candidates. The highest + priority ready candidate wins; a lower-priority candidate remains held while + a higher-priority candidate is still inside its SLA window. +- At a round boundary, at most one pending normal attempt is promoted to the + fallback. The next round uses the remaining slots for new normal candidates, + so `discoveryConcurrency=2` means `one fallback + one new candidate`. +- A fallback that has produced a valid prefix is held until the current normal + window closes, all normal candidates fail, or no candidates remain. A normal + winner always has precedence during the window. +- When `bill_hedge_losers` is enabled, a Discovery loser that already produced + a protocol-valid prefix and reached ready state may reuse the legacy + background drain and billing path. It is billed only after natural stream + completion with a completion marker and explicit usage. All other losers are + cancelled and their readers/agents/provider-session references are released. +- Sticky binding is written only after a natural, successful stream completion + with the protocol completion marker and a generation-aware CAS. Fake-200, + incomplete, and client-aborted streams do not create or renew Sticky. + +Discovery is eligible only for supported streaming protocol families and when +the versioned Redis binding capability is available. If Redis capability is +unknown/unavailable, the existing provider selection and Hedge behavior remain +active. + +The versioned binding scripts require the canonical binding, legacy provider, +legacy owner, lease, and cooldown keys to be evaluated atomically. On Redis +Cluster layouts where those keys do not share a slot and Redis returns +`CROSSSLOT` (or when Lua capability probing fails), the capability state is +`unavailable`; the service records that state and uses the tenant-checked +legacy wrapper. Discovery stays disabled until a later connection-lifecycle +probe succeeds. + +## Rollout + +1. Apply the system-settings migration. +2. Confirm the Redis versioned-binding capability probe is `available`. +3. Leave `discoveryEnabled=false` while validating the existing Hedge and + versioned binding checks. +4. Enable Discovery for a controlled group, observe provider-chain outcomes, + first-token latency, fallback promotions, cancellations, CAS conflicts, and + final 503s. +5. Disable the setting to return immediately to the legacy Hedge path. + +This feature does not change the final client failure contract: an exhausted +request continues to return the existing `503` mapping. diff --git a/drizzle/0110_daffy_rawhide_kid.sql b/drizzle/0110_daffy_rawhide_kid.sql new file mode 100644 index 000000000..ff57e9a44 --- /dev/null +++ b/drizzle/0110_daffy_rawhide_kid.sql @@ -0,0 +1,7 @@ +ALTER TABLE "system_settings" ADD COLUMN "discovery_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "discovery_concurrency" integer DEFAULT 2 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "max_discovery_rounds" integer DEFAULT 2 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "discovery_sla_ms" integer DEFAULT 10000 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "sticky_sla_ms" integer DEFAULT 20000 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "racing_total_timeout_ms" integer DEFAULT 60000 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "sticky_timeout_cooldown_ms" integer DEFAULT 300000 NOT NULL; diff --git a/drizzle/0111_happy_mauler.sql b/drizzle/0111_happy_mauler.sql new file mode 100644 index 000000000..b0a595652 --- /dev/null +++ b/drizzle/0111_happy_mauler.sql @@ -0,0 +1,41 @@ +ALTER TABLE "message_request" ADD COLUMN "routing_trace" jsonb; + +-- Routing trace finalization is observability-only. Restrict the ledger trigger +-- to columns that can actually change its projection so trace-only patches do +-- not rewrite accounting rows. +DROP TRIGGER IF EXISTS trg_upsert_usage_ledger ON message_request; + +CREATE TRIGGER trg_upsert_usage_ledger +AFTER INSERT OR UPDATE OF + blocked_by, + status_code, + error_message, + provider_chain, + actual_response_model, + endpoint, + provider_id, + user_id, + "key", + model, + original_model, + api_type, + session_id, + cost_usd, + cost_multiplier, + group_cost_multiplier, + input_tokens, + output_tokens, + cache_creation_input_tokens, + cache_read_input_tokens, + cache_creation_5m_input_tokens, + cache_creation_1h_input_tokens, + cache_ttl_applied, + context_1m_applied, + swap_cache_ttl_applied, + duration_ms, + ttfb_ms, + client_ip, + created_at +ON message_request +FOR EACH ROW +EXECUTE FUNCTION fn_upsert_usage_ledger(); diff --git a/drizzle/meta/0110_snapshot.json b/drizzle/meta/0110_snapshot.json new file mode 100644 index 000000000..66d670d57 --- /dev/null +++ b/drizzle/meta/0110_snapshot.json @@ -0,0 +1,4691 @@ +{ + "id": "b6b7996d-5b70-4a31-a1c7-3a318b3398a0", + "prevId": "c054c34a-98a4-4ae1-b0e5-0b663380f123", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hedge_losers": { + "name": "hedge_losers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_image_generation_preference": { + "name": "codex_image_generation_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0111_snapshot.json b/drizzle/meta/0111_snapshot.json new file mode 100644 index 000000000..2c6d18c62 --- /dev/null +++ b/drizzle/meta/0111_snapshot.json @@ -0,0 +1,4697 @@ +{ + "id": "b59029b9-eae7-4d23-97b8-323ee78c8e99", + "prevId": "b6b7996d-5b70-4a31-a1c7-3a318b3398a0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "routing_trace": { + "name": "routing_trace", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hedge_losers": { + "name": "hedge_losers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_image_generation_preference": { + "name": "codex_image_generation_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 609e69822..d74fe7801 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -771,6 +771,20 @@ "when": 1784067387303, "tag": "0109_nice_shriek", "breakpoints": true + }, + { + "idx": 110, + "version": "7", + "when": 1784571513591, + "tag": "0110_daffy_rawhide_kid", + "breakpoints": true + }, + { + "idx": 111, + "version": "7", + "when": 1784622924311, + "tag": "0111_happy_mauler", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 085467bc3..0c2eb03c2 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -300,6 +300,7 @@ "title": "Billing Details", "input": "Input", "output": "Output", + "cacheWrite": "Cache Write", "cacheWrite5m": "Cache Write (5m)", "cacheWrite1h": "Cache Write (1h)", "cacheRead": "Cache Read", @@ -380,6 +381,140 @@ "overridden": "Overridden by provider", "tooltip": "Thinking effort requested by the client (output_config.effort), shown verbatim. The proxy does not rename or convert levels." }, + "routingTrace": { + "title": "Routing mode", + "modes": { + "discovery": "Bounded Discovery", + "legacy_hedge": "Legacy Hedge", + "legacy_serial": "Legacy serial fallback", + "single_upstream": "Single upstream", + "lease_conflict": "Single-route protection" + }, + "bypassed": "Discovery was not used: {reason}", + "bypassReasons": { + "disabled": "Discovery is disabled", + "non_streaming": "Non-streaming request", + "retry_not_allowed": "This endpoint does not allow retries", + "provider_switch_not_allowed": "This endpoint does not allow provider switching", + "raw_passthrough": "Raw passthrough request", + "unsupported_protocol": "Protocol is not supported by Discovery", + "websocket": "WebSocket request", + "streaming_hedge_disabled": "Streaming racing is disabled for this request", + "raw_cross_provider_fallback": "Raw cross-provider fallback is enabled", + "missing_session": "Session identity is missing", + "missing_key": "API key identity is missing", + "rollout_ineligible": "Request is outside the current rollout", + "redis_capability_unavailable": "Redis binding capability is unavailable", + "binding_conflict": "Session binding conflict", + "lease_conflict": "Another request owns the Discovery lease; this request uses one upstream", + "lease_unavailable": "Discovery lease is unavailable", + "unknown": "Eligibility requirement was not met" + }, + "discoveryTitle": "Discovery rounds", + "discoveryCompact": "{rounds}R · {attempts} tries", + "routeMode": "Route mode", + "routeModes": { + "sticky": "Sticky", + "cold_start": "Cold start", + "rediscovery": "Rediscovery" + }, + "finalProvider": "Final provider", + "roundsLabel": "Rounds", + "attemptsLabel": "Attempts", + "winnerOrigin": "Winner origin", + "viewDetails": "View Discovery details", + "stickyPhase": "Sticky probe", + "round": "Round {round}", + "attempts": "{count} attempts", + "noAttempts": "No provider attempts were recorded", + "providerFallback": "Provider {id}", + "elapsed": "+{elapsed}ms", + "priority": "Priority {priority}", + "roles": { + "sticky": "Sticky", + "normal": "Candidate", + "fallback": "Fallback" + }, + "winnerSources": { + "sticky": "Sticky", + "normal": "Normal candidate", + "fallback": "Fallback takeover" + }, + "roleTransition": "{from} → {to}", + "outcomes": { + "success": "Success", + "pending": "Pending", + "ready": "Ready", + "held": "Ready, held", + "winner": "Winner", + "failed": "Failed", + "cancelled": "Cancelled", + "timeout": "SLA timeout", + "client_abort": "Client aborted", + "deadline": "Total deadline" + }, + "terminalOutcome": "Request result", + "bindingResult": "Sticky binding", + "bindingActions": { + "create": "Create", + "renew": "Renew", + "clear": "Clear", + "none": "No change" + }, + "bindingFallbackWinner": "Not written · Fallback winner", + "bindingOutcomes": { + "updated": "Updated", + "cleared": "Cleared", + "skipped": "Skipped", + "failed": "Failed", + "unknown": "Unknown" + }, + "fallbackPromoted": "Promoted to fallback", + "winnerCommitted": "Winner committed", + "roundStarted": "Round started", + "events": { + "started": "Request started", + "ready": "Valid first content received", + "held": "Delivery held by routing policy", + "fallbackPromoted": "Promoted to fallback", + "winnerCommitted": "Committed as winner", + "finished": "Finished: {outcome}" + }, + "traceTruncated": "Some events were omitted because this trace reached its storage limit.", + "summary": "{rounds} rounds · {attempts} attempts · max {maxActive} active", + "config": "Configuration snapshot", + "configConcurrency": "Concurrency", + "configMaxRounds": "Maximum rounds", + "configDiscoverySla": "Round SLA", + "configStickySla": "Sticky SLA", + "configTotalTimeout": "Total timeout", + "configStickyCooldown": "Sticky timeout cooldown", + "configStickyBindingTtl": "Sticky binding validity (SESSION_TTL)", + "attemptDetails": { + "providerId": "Provider ID", + "attempt": "Attempt", + "status": "HTTP status", + "inferred": "inferred", + "endpoint": "Endpoint", + "error": "Upstream error", + "cancellation": "Cancellation reason", + "timeline": "Attempt timeline", + "billing": "Cost and usage", + "billed": "Included in total cost", + "billedCost": "Billed cost", + "winnerCost": "Winner cost", + "notObtained": "Cancelled; usage unavailable" + }, + "cancellationKinds": { + "discovery_loser": "Another Discovery attempt won", + "discovery_sla_timeout": "Discovery SLA expired", + "round_timeout": "Round SLA expired", + "sticky_timeout": "Sticky SLA expired", + "request_deadline": "Total Discovery deadline reached", + "client_abort": "Client disconnected", + "winner_committed": "Another provider was committed" + } + }, "reasoningEffort": { "label": "Reasoning effort", "overridden": "Overridden by provider", @@ -387,6 +522,7 @@ }, "logicTrace": { "title": "Decision Chain", + "singleRouteSelectionTitle": "Provider selection under single-route protection", "noDecisionData": "No decision data available", "providersCount": "{count} providers", "healthyCount": "{count} healthy", diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 0440d2c83..e5654556d 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -116,8 +116,21 @@ "billNonSuccessfulRequestsDesc": "When enabled, requests with non-success status (e.g., 499 client cancellation) are billed by token usage if upstream returned positive usage data. Default off.", "billNonSuccessfulRequestsTooltip": "Useful when an upstream provider counts tokens regardless of the final status (e.g., aborted streaming responses). Fake-200 upstream errors remain unbilled.", "billHedgeLosers": "Bill Provider-Racing Losers by Token Usage", - "billHedgeLosersDesc": "When provider racing (streaming hedge) is on, losing providers are kept connected in the background, drained for their token usage, and billed - their cost is added into this request's total. Default on.", - "billHedgeLosersTooltip": "Upstreams usually bill a request even after we cancel it. Keeping racing losers alive lets us reclaim their token counts so CCH's cost matches what every upstream actually charged. Each loser's cost is accumulated asynchronously into the request total.", + "billHedgeLosersDesc": "When provider racing (streaming hedge or bounded Discovery) is on, losers that have already produced a valid response prefix may be kept connected in the background, drained for usage, and billed. Their cost is added to this request's total. Other losers are cancelled. Default on.", + "billHedgeLosersTooltip": "Only losers with a readable valid response are drained. SLA timeouts, explicit failures, and attempts without a valid prefix are cancelled; their unknown upstream cost is not added.", + "discoveryEnabled": "Enable bounded provider Discovery", + "discoveryEnabledDesc": "When enabled, cold-start streaming requests probe multiple providers within a bounded window and keep at most one fallback. It is disabled by default.", + "discoveryConcurrency": "Discovery initial concurrency", + "maxDiscoveryRounds": "Discovery maximum rounds", + "discoverySlaMs": "Discovery SLA (milliseconds)", + "stickySlaMs": "Sticky SLA (milliseconds)", + "racingTotalTimeoutMs": "Discovery total timeout (milliseconds)", + "stickyTimeoutCooldownMs": "Sticky timeout cooldown (milliseconds)", + "stickyBindingTtl": "Sticky binding validity", + "stickyBindingTtlDesc": "Read-only. This follows the SESSION_TTL environment variable and is shared with other session snapshots.", + "discoveryWindowDesc": "The total timeout must be at least Sticky SLA + maximum rounds × Discovery SLA. With loser billing enabled, only ready Discovery losers may be drained; SLA timeouts and failed or no-prefix attempts remain cancelled.", + "discoveryWindowInvalid": "Discovery total timeout is shorter than the configured Sticky and Discovery windows.", + "discoverySettingsInvalid": "One or more Discovery values are outside the allowed range.", "verboseProviderError": "Verbose Provider Error", "verboseProviderErrorDesc": "When enabled, CCH may return detailed diagnostic information for some upstream failure types in `error.details` (for example provider availability diagnostics or sanitized upstream snippets).", "verboseProviderErrorTooltip": "May expose provider names, internal routing clues, upstream failure reasons, and other diagnostic details. Enable only if clients are allowed to see low-level troubleshooting context.", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index dcbfbe427..b07d5d7a3 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -300,6 +300,7 @@ "title": "課金詳細", "input": "入力", "output": "出力", + "cacheWrite": "キャッシュ書き込み", "cacheWrite5m": "キャッシュ書き込み (5m)", "cacheWrite1h": "キャッシュ書き込み (1h)", "cacheRead": "キャッシュ読み取り", @@ -380,6 +381,140 @@ "overridden": "プロバイダーにより上書き", "tooltip": "クライアントがリクエストボディで指定した思考強度 (output_config.effort)。プロキシは値をそのまま表示し、レベル名を変換しません。" }, + "routingTrace": { + "title": "ルーティングモード", + "modes": { + "discovery": "限定 Discovery", + "legacy_hedge": "従来の Hedge 競争", + "legacy_serial": "従来の直列フェイルオーバー", + "single_upstream": "単一アップストリーム", + "lease_conflict": "単一経路保護" + }, + "bypassed": "このリクエストでは Discovery を使用していません:{reason}", + "bypassReasons": { + "disabled": "Discovery が無効です", + "non_streaming": "非ストリーミングリクエストです", + "retry_not_allowed": "このエンドポイントでは再試行できません", + "provider_switch_not_allowed": "このエンドポイントではプロバイダーを切り替えられません", + "raw_passthrough": "Raw パススルーリクエストです", + "unsupported_protocol": "Discovery が対応していないプロトコルです", + "websocket": "WebSocket リクエストです", + "streaming_hedge_disabled": "このリクエストではストリーミング競争が無効です", + "raw_cross_provider_fallback": "Raw クロスプロバイダーフォールバックが有効です", + "missing_session": "Session 識別子がありません", + "missing_key": "API Key 識別子がありません", + "rollout_ineligible": "現在のロールアウト対象外です", + "redis_capability_unavailable": "Redis バインディング機能を利用できません", + "binding_conflict": "Session バインディングが競合しています", + "lease_conflict": "別のリクエストが Discovery リースを保持しているため、このリクエストは単一アップストリームを使用します", + "lease_unavailable": "Discovery リースを利用できません", + "unknown": "Discovery の適格条件を満たしていません" + }, + "discoveryTitle": "Discovery ラウンド", + "discoveryCompact": "{rounds}R · {attempts} 回", + "routeMode": "ルーティング方式", + "routeModes": { + "sticky": "Sticky", + "cold_start": "コールドスタート", + "rediscovery": "再探索" + }, + "finalProvider": "最終プロバイダー", + "roundsLabel": "ラウンド", + "attemptsLabel": "試行", + "winnerOrigin": "勝者の経路", + "viewDetails": "Discovery の詳細を表示", + "stickyPhase": "Sticky プローブ", + "round": "ラウンド {round}", + "attempts": "{count} 回の試行", + "noAttempts": "プロバイダー試行の記録がありません", + "providerFallback": "プロバイダー {id}", + "elapsed": "+{elapsed}ms", + "priority": "優先度 {priority}", + "roles": { + "sticky": "Sticky", + "normal": "候補", + "fallback": "フォールバック" + }, + "winnerSources": { + "sticky": "Sticky", + "normal": "通常候補", + "fallback": "フォールバック引き継ぎ" + }, + "roleTransition": "{from} → {to}", + "outcomes": { + "success": "正常終了", + "pending": "待機中", + "ready": "準備完了", + "held": "準備完了、保留中", + "winner": "勝者", + "failed": "失敗", + "cancelled": "キャンセル済み", + "timeout": "SLA タイムアウト", + "client_abort": "クライアント切断", + "deadline": "全体期限" + }, + "terminalOutcome": "リクエスト結果", + "bindingResult": "Sticky バインド", + "bindingActions": { + "create": "作成", + "renew": "更新", + "clear": "解除", + "none": "変更なし" + }, + "bindingFallbackWinner": "書き込みなし · フォールバック勝者", + "bindingOutcomes": { + "updated": "更新済み", + "cleared": "解除済み", + "skipped": "スキップ", + "failed": "失敗", + "unknown": "不明" + }, + "fallbackPromoted": "フォールバックに昇格", + "winnerCommitted": "勝者を確定", + "roundStarted": "ラウンド開始", + "events": { + "started": "リクエスト開始", + "ready": "有効な先頭データを受信", + "held": "ルーティング規則により配信を保留", + "fallbackPromoted": "フォールバックに昇格", + "winnerCommitted": "勝者として確定", + "finished": "完了:{outcome}" + }, + "traceTruncated": "保存上限に達したため、一部のイベントは省略されました。", + "summary": "{rounds} ラウンド · {attempts} 試行 · 最大同時実行 {maxActive}", + "config": "設定スナップショット", + "configConcurrency": "同時実行数", + "configMaxRounds": "最大ラウンド数", + "configDiscoverySla": "ラウンド SLA", + "configStickySla": "Sticky SLA", + "configTotalTimeout": "全体タイムアウト", + "configStickyCooldown": "Sticky タイムアウトのクールダウン", + "configStickyBindingTtl": "Sticky バインドの有効期間 (SESSION_TTL)", + "attemptDetails": { + "providerId": "Provider ID", + "attempt": "試行回数", + "status": "HTTP ステータス", + "inferred": "推定", + "endpoint": "エンドポイント", + "error": "上流エラー", + "cancellation": "キャンセル理由", + "timeline": "試行タイムライン", + "billing": "費用と使用量", + "billed": "合計費用に計上済み", + "billedCost": "計上費用", + "winnerCost": "勝者の費用", + "notObtained": "キャンセル済み、使用量未取得" + }, + "cancellationKinds": { + "discovery_loser": "別の Discovery 試行が勝利しました", + "discovery_sla_timeout": "Discovery SLA の期限に達しました", + "round_timeout": "ラウンド SLA の期限に達しました", + "sticky_timeout": "Sticky SLA の期限に達しました", + "request_deadline": "Discovery の合計期限に達しました", + "client_abort": "クライアントが切断しました", + "winner_committed": "別の Provider が勝者として確定しました" + } + }, "reasoningEffort": { "label": "推論強度", "overridden": "プロバイダーにより上書き", @@ -387,6 +522,7 @@ }, "logicTrace": { "title": "決定チェーン", + "singleRouteSelectionTitle": "単一経路保護での Provider 選択", "noDecisionData": "決定データがありません", "providersCount": "{count} プロバイダー", "healthyCount": "{count} 健全", diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index 0fc81c84a..f453b8519 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -118,8 +118,21 @@ "billNonSuccessfulRequestsDesc": "有効にすると、非 2xx ステータス(例: クライアント中断による 499)のリクエストでも、上流が正の token 使用量を返した場合は使用量に応じて課金されます。既定はオフ。", "billNonSuccessfulRequestsTooltip": "上流プロバイダーがレスポンス失敗時にも token をカウントするケース(ストリーム中断でも token を計上する等)に有用です。fake-200 の偽成功エラー応答は引き続き課金されません。", "billHedgeLosers": "プロバイダー競争(hedge)の敗者を Token 使用量で課金", - "billHedgeLosersDesc": "プロバイダー競争(ストリーミング hedge)が有効な場合、競争に敗れたプロバイダーを即座に切断せず、バックグラウンドで接続を維持して token 使用量を取得し課金します。その費用はこのリクエストの合計に加算されます。既定はオン。", - "billHedgeLosersTooltip": "上流はこちらが能動的にキャンセルしたリクエストも通常は課金します。競争の敗者を生かしておくことで token 数を回収し、CCH の課金を各上流の実際の課金と一致させます。各敗者の費用は非同期にリクエストの費用へ加算されます。", + "billHedgeLosersDesc": "プロバイダー競争(ストリーミング hedge または制限付き Discovery)が有効な場合、有効な応答プレフィックスを受信済みの敗者だけをバックグラウンドで読み切って課金します。費用はリクエスト合計に加算され、それ以外の敗者はキャンセルされます。既定はオン。", + "billHedgeLosersTooltip": "読み取り可能な有効応答がある敗者だけを読み切ります。SLA タイムアウト、明示的な失敗、有効なプレフィックスがない試行はキャンセルされ、確認できない上流費用は加算されません。", + "discoveryEnabled": "制限付き Provider Discovery を有効化", + "discoveryEnabledDesc": "有効にすると、コールドスタートのストリーミングリクエストで複数 Provider を制限時間内に探索し、フォールバックを最大 1 つ保持します。既定はオフです。", + "discoveryConcurrency": "Discovery 初期並列数", + "maxDiscoveryRounds": "Discovery 最大ラウンド数", + "discoverySlaMs": "Discovery SLA(ミリ秒)", + "stickySlaMs": "Sticky SLA(ミリ秒)", + "racingTotalTimeoutMs": "Discovery 合計タイムアウト(ミリ秒)", + "stickyTimeoutCooldownMs": "Sticky タイムアウト後のクールダウン(ミリ秒)", + "stickyBindingTtl": "Sticky バインドの有効期間", + "stickyBindingTtlDesc": "読み取り専用です。この値は環境変数 SESSION_TTL に従い、他の Session スナップショットと共有されます。", + "discoveryWindowDesc": "合計タイムアウトは Sticky SLA + 最大ラウンド数 × Discovery SLA 以上にしてください。敗者課金を有効にした場合も、有効な応答を受信済みの Discovery 敗者だけを読み切り、SLA タイムアウトや失敗はキャンセルします。", + "discoveryWindowInvalid": "Discovery 合計タイムアウトが設定された Sticky/Discovery ウィンドウより短くなっています。", + "discoverySettingsInvalid": "1 つ以上の Discovery 設定値が許容範囲外です。", "verboseProviderError": "詳細なプロバイダーエラー", "verboseProviderErrorDesc": "有効にすると、一部の上流障害タイプで `error.details` により詳細な診断情報(プロバイダー可用性の診断やサニタイズ済み上流断片など)を含める場合があります。", "verboseProviderErrorTooltip": "この設定を有効にすると、プロバイダー名、内部ルーティングの手掛かり、上流障害の理由などの診断情報が露出する可能性があります。クライアントに低レベルのトラブルシュート文脈を見せてもよい場合にのみ有効化してください。", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index ea05d66a8..2fc0fff47 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -300,6 +300,7 @@ "title": "Детали биллинга", "input": "Входные", "output": "Выходные", + "cacheWrite": "Запись в кэш", "cacheWrite5m": "Запись кэша (5m)", "cacheWrite1h": "Запись кэша (1h)", "cacheRead": "Чтение кэша", @@ -380,6 +381,140 @@ "overridden": "Переопределено провайдером", "tooltip": "Уровень усилий на размышления, запрошенный клиентом (output_config.effort); показан как есть — прокси не переименовывает и не преобразует уровни." }, + "routingTrace": { + "title": "Режим маршрутизации", + "modes": { + "discovery": "Ограниченный Discovery", + "legacy_hedge": "Прежняя гонка Hedge", + "legacy_serial": "Прежнее последовательное переключение", + "single_upstream": "Один upstream", + "lease_conflict": "Защита одним маршрутом" + }, + "bypassed": "Discovery не использован: {reason}", + "bypassReasons": { + "disabled": "Discovery отключён", + "non_streaming": "Непотоковый запрос", + "retry_not_allowed": "Эта конечная точка не разрешает повторы", + "provider_switch_not_allowed": "Эта конечная точка не разрешает смену провайдера", + "raw_passthrough": "Запрос с прямой передачей данных", + "unsupported_protocol": "Протокол не поддерживается Discovery", + "websocket": "Запрос WebSocket", + "streaming_hedge_disabled": "Потоковая гонка отключена для этого запроса", + "raw_cross_provider_fallback": "Включено прямое переключение между провайдерами", + "missing_session": "Нет идентификатора Session", + "missing_key": "Нет идентификатора API Key", + "rollout_ineligible": "Запрос не входит в текущий rollout", + "redis_capability_unavailable": "Возможность привязки Redis недоступна", + "binding_conflict": "Конфликт привязки Session", + "lease_conflict": "Lease Discovery занят другим запросом; этот запрос использует один upstream", + "lease_unavailable": "Lease Discovery недоступен", + "unknown": "Условия запуска Discovery не выполнены" + }, + "discoveryTitle": "Раунды Discovery", + "discoveryCompact": "{rounds} р. · {attempts} поп.", + "routeMode": "Способ маршрутизации", + "routeModes": { + "sticky": "Sticky", + "cold_start": "Холодный старт", + "rediscovery": "Повторное обнаружение" + }, + "finalProvider": "Итоговый провайдер", + "roundsLabel": "Раунды", + "attemptsLabel": "Попытки", + "winnerOrigin": "Источник победителя", + "viewDetails": "Открыть детали Discovery", + "stickyPhase": "Проверка Sticky", + "round": "Раунд {round}", + "attempts": "Попыток: {count}", + "noAttempts": "Попытки провайдеров не записаны", + "providerFallback": "Провайдер {id}", + "elapsed": "+{elapsed} мс", + "priority": "Приоритет {priority}", + "roles": { + "sticky": "Sticky", + "normal": "Кандидат", + "fallback": "Резерв" + }, + "winnerSources": { + "sticky": "Sticky", + "normal": "Обычный кандидат", + "fallback": "Перехват резервным провайдером" + }, + "roleTransition": "{from} → {to}", + "outcomes": { + "success": "Успешно", + "pending": "Ожидание", + "ready": "Готов", + "held": "Готов, удерживается", + "winner": "Победитель", + "failed": "Ошибка", + "cancelled": "Отменён", + "timeout": "Тайм-аут SLA", + "client_abort": "Клиент отключился", + "deadline": "Общий срок" + }, + "terminalOutcome": "Результат запроса", + "bindingResult": "Sticky-привязка", + "bindingActions": { + "create": "Создать", + "renew": "Продлить", + "clear": "Очистить", + "none": "Без изменений" + }, + "bindingFallbackWinner": "Не записано · Победитель резерва", + "bindingOutcomes": { + "updated": "Обновлено", + "cleared": "Очищено", + "skipped": "Пропущено", + "failed": "Ошибка", + "unknown": "Неизвестно" + }, + "fallbackPromoted": "Повышен до резерва", + "winnerCommitted": "Победитель подтверждён", + "roundStarted": "Раунд начат", + "events": { + "started": "Запрос запущен", + "ready": "Получены первые допустимые данные", + "held": "Выдача удержана правилами маршрутизации", + "fallbackPromoted": "Повышен до резерва", + "winnerCommitted": "Подтверждён как победитель", + "finished": "Завершено: {outcome}" + }, + "traceTruncated": "Часть событий опущена из-за ограничения хранилища.", + "summary": "Раундов: {rounds} · попыток: {attempts} · максимум активных: {maxActive}", + "config": "Снимок настроек", + "configConcurrency": "Параллельность", + "configMaxRounds": "Максимум раундов", + "configDiscoverySla": "SLA раунда", + "configStickySla": "SLA Sticky", + "configTotalTimeout": "Общий тайм-аут", + "configStickyCooldown": "Пауза после тайм-аута Sticky", + "configStickyBindingTtl": "Срок действия привязки Sticky (SESSION_TTL)", + "attemptDetails": { + "providerId": "ID провайдера", + "attempt": "Попытка", + "status": "Статус HTTP", + "inferred": "выведен", + "endpoint": "Upstream", + "error": "Ошибка upstream", + "cancellation": "Причина отмены", + "timeline": "Хронология попытки", + "billing": "Стоимость и использование", + "billed": "Включено в общую стоимость", + "billedCost": "Начисленная стоимость", + "winnerCost": "Стоимость победителя", + "notObtained": "Отменено, usage не получен" + }, + "cancellationKinds": { + "discovery_loser": "Победила другая попытка Discovery", + "discovery_sla_timeout": "SLA Discovery истёк", + "round_timeout": "SLA раунда истёк", + "sticky_timeout": "SLA Sticky истёк", + "request_deadline": "Общий срок Discovery истёк", + "client_abort": "Клиент отключился", + "winner_committed": "Другой провайдер был выбран победителем" + } + }, "reasoningEffort": { "label": "Интенсивность рассуждений", "overridden": "Переопределено провайдером", @@ -387,6 +522,7 @@ }, "logicTrace": { "title": "Цепочка решений", + "singleRouteSelectionTitle": "Выбор провайдера в режиме защиты одним маршрутом", "noDecisionData": "Нет данных о решениях", "providersCount": "{count} поставщиков", "healthyCount": "{count} исправных", diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 05e3dc217..490614560 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -118,8 +118,21 @@ "billNonSuccessfulRequestsDesc": "Если включено, запросы с не-2xx статусом (например, 499 при отмене клиентом) будут тарифицироваться по фактическому количеству токенов, если апстрим вернул положительные данные об использовании. По умолчанию выключено.", "billNonSuccessfulRequestsTooltip": "Полезно, когда апстрим считает токены даже при неудачном статусе (например, прерванные стриминговые ответы). Поддельные ответы 200 (fake-200) по-прежнему не тарифицируются.", "billHedgeLosers": "Тарифицировать проигравших в гонке провайдеров по токенам", - "billHedgeLosersDesc": "Когда включена гонка провайдеров (streaming hedge), проигравшие провайдеры не отключаются сразу, а остаются на связи в фоне, дочитываются для получения использования токенов и тарифицируются - их стоимость добавляется в общую стоимость этого запроса. По умолчанию включено.", - "billHedgeLosersTooltip": "Апстримы обычно тарифицируют запрос, даже если мы его отменили. Сохраняя проигравших в гонке, мы получаем их счётчики токенов, чтобы расходы CCH совпадали с тем, что фактически списал каждый апстрим. Стоимость каждого проигравшего асинхронно добавляется к стоимости запроса.", + "billHedgeLosersDesc": "Если включена гонка провайдеров (streaming hedge или ограниченный Discovery), в фоне дочитываются и тарифицируются только проигравшие, уже отдавшие корректный префикс ответа. Их стоимость добавляется к общей стоимости запроса. Остальные проигравшие отменяются. По умолчанию включено.", + "billHedgeLosersTooltip": "Дочитываются только проигравшие с доступным корректным ответом. Тайм-ауты SLA, явные ошибки и попытки без корректного префикса отменяются; неизвестная стоимость апстрима не добавляется.", + "discoveryEnabled": "Включить ограниченное обнаружение провайдеров", + "discoveryEnabledDesc": "При включении потоковые запросы холодного старта проверяют несколько провайдеров в ограниченном окне и сохраняют не более одного резервного. По умолчанию выключено.", + "discoveryConcurrency": "Начальная параллельность Discovery", + "maxDiscoveryRounds": "Максимальное число раундов Discovery", + "discoverySlaMs": "SLA Discovery (миллисекунды)", + "stickySlaMs": "SLA Sticky (миллисекунды)", + "racingTotalTimeoutMs": "Общий тайм-аут Discovery (миллисекунды)", + "stickyTimeoutCooldownMs": "Пауза после тайм-аута Sticky (миллисекунды)", + "stickyBindingTtl": "Срок действия привязки Sticky", + "stickyBindingTtlDesc": "Только для чтения. Значение следует переменной окружения SESSION_TTL и используется другими снимками Session.", + "discoveryWindowDesc": "Общий тайм-аут должен быть не меньше SLA Sticky + максимальное число раундов × SLA Discovery. При включённой тарификации проигравших дочитываются только готовые проигравшие Discovery; тайм-ауты SLA и ошибки остаются отменёнными.", + "discoveryWindowInvalid": "Общий тайм-аут Discovery меньше настроенного окна Sticky и Discovery.", + "discoverySettingsInvalid": "Одно или несколько значений Discovery находятся вне допустимого диапазона.", "verboseProviderError": "Подробные ошибки провайдеров", "verboseProviderErrorDesc": "При включении CCH может добавлять более подробную диагностику некоторых типов сбоев апстрима в `error.details` (например, диагностику доступности провайдеров или очищенные фрагменты ответа апстрима).", "verboseProviderErrorTooltip": "Может раскрывать названия провайдеров, внутренние подсказки маршрутизации, причины сбоев апстрима и другие диагностические детали. Включайте только если клиентам допустимо видеть низкоуровневый контекст отладки.", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index d707df578..fdb559c64 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -300,6 +300,7 @@ "title": "计费详情", "input": "输入", "output": "输出", + "cacheWrite": "缓存写入", "cacheWrite5m": "缓存写入 (5m)", "cacheWrite1h": "缓存写入 (1h)", "cacheRead": "缓存读取", @@ -380,6 +381,140 @@ "overridden": "已被供应商覆写", "tooltip": "客户端在请求体中声明的思考强度(output_config.effort),按原值显示,代理不会重命名或转换等级。" }, + "routingTrace": { + "title": "路由模式", + "modes": { + "discovery": "有界供应商 Discovery", + "legacy_hedge": "旧版 Hedge 竞速", + "legacy_serial": "旧版串行故障转移", + "single_upstream": "单上游模式", + "lease_conflict": "单路保护" + }, + "bypassed": "本请求未使用 Discovery:{reason}", + "bypassReasons": { + "disabled": "Discovery 未启用", + "non_streaming": "非流式请求", + "retry_not_allowed": "该端点不允许重试", + "provider_switch_not_allowed": "该端点不允许切换供应商", + "raw_passthrough": "原始透传请求", + "unsupported_protocol": "Discovery 不支持该协议", + "websocket": "WebSocket 请求", + "streaming_hedge_disabled": "本请求已禁用流式竞速", + "raw_cross_provider_fallback": "已启用原始跨供应商故障转移", + "missing_session": "缺少 Session 标识", + "missing_key": "缺少 API Key 标识", + "rollout_ineligible": "本请求不在当前灰度范围内", + "redis_capability_unavailable": "Redis 绑定能力不可用", + "binding_conflict": "Session 绑定状态冲突", + "lease_conflict": "另一个请求持有 Discovery 租约,本请求只使用一个上游", + "lease_unavailable": "Discovery 租约不可用", + "unknown": "未满足 Discovery 准入条件" + }, + "discoveryTitle": "Discovery 轮次", + "discoveryCompact": "{rounds} 轮 · {attempts} 次", + "routeMode": "路由方式", + "routeModes": { + "sticky": "Sticky", + "cold_start": "冷启动", + "rediscovery": "重新发现" + }, + "finalProvider": "最终供应商", + "roundsLabel": "轮次", + "attemptsLabel": "尝试", + "winnerOrigin": "赢家来源", + "viewDetails": "查看 Discovery 详情", + "stickyPhase": "Sticky 探测", + "round": "第 {round} 轮", + "attempts": "{count} 次尝试", + "noAttempts": "没有记录到供应商尝试", + "providerFallback": "供应商 {id}", + "elapsed": "+{elapsed}ms", + "priority": "优先级 {priority}", + "roles": { + "sticky": "Sticky", + "normal": "候选", + "fallback": "保底" + }, + "winnerSources": { + "sticky": "Sticky", + "normal": "正常候选", + "fallback": "保底接管" + }, + "roleTransition": "{from} → {to}", + "outcomes": { + "success": "成功", + "pending": "等待中", + "ready": "已就绪", + "held": "已就绪,暂缓交付", + "winner": "胜出", + "failed": "失败", + "cancelled": "已取消", + "timeout": "SLA 超时", + "client_abort": "客户端已断开", + "deadline": "总时限到期" + }, + "terminalOutcome": "请求结果", + "bindingResult": "Sticky 绑定", + "bindingActions": { + "create": "创建", + "renew": "续期", + "clear": "清除", + "none": "不变更" + }, + "bindingFallbackWinner": "未写入 · 保底赢家", + "bindingOutcomes": { + "updated": "已更新", + "cleared": "已清除", + "skipped": "已跳过", + "failed": "失败", + "unknown": "未知" + }, + "fallbackPromoted": "提升为保底请求", + "winnerCommitted": "已提交赢家", + "roundStarted": "轮次开始", + "events": { + "started": "请求已发出", + "ready": "已收到有效首字", + "held": "按路由规则暂缓交付", + "fallbackPromoted": "已提升为保底", + "winnerCommitted": "已提交为赢家", + "finished": "结束:{outcome}" + }, + "traceTruncated": "追踪事件达到存储上限,部分事件已省略。", + "summary": "{rounds} 轮 · {attempts} 次尝试 · 最大并发 {maxActive}", + "config": "配置快照", + "configConcurrency": "并发数", + "configMaxRounds": "最大轮数", + "configDiscoverySla": "每轮 SLA", + "configStickySla": "Sticky SLA", + "configTotalTimeout": "总超时", + "configStickyCooldown": "Sticky 超时冷却", + "configStickyBindingTtl": "Sticky 绑定有效期(SESSION_TTL)", + "attemptDetails": { + "providerId": "供应商 ID", + "attempt": "尝试次数", + "status": "HTTP 状态", + "inferred": "推断", + "endpoint": "端点", + "error": "上游错误", + "cancellation": "取消原因", + "timeline": "尝试时间线", + "billing": "费用与用量", + "billed": "已计入总费用", + "billedCost": "已计入费用", + "winnerCost": "赢家费用", + "notObtained": "已取消,未取得用量" + }, + "cancellationKinds": { + "discovery_loser": "其他 Discovery 尝试已胜出", + "discovery_sla_timeout": "Discovery SLA 已到期", + "round_timeout": "本轮 SLA 已到期", + "sticky_timeout": "Sticky SLA 已到期", + "request_deadline": "Discovery 总时限已到期", + "client_abort": "客户端已断开", + "winner_committed": "其他供应商已提交为赢家" + } + }, "reasoningEffort": { "label": "思考强度", "overridden": "已被供应商覆写", @@ -387,6 +522,7 @@ }, "logicTrace": { "title": "决策链", + "singleRouteSelectionTitle": "单路保护下的供应商选择", "noDecisionData": "暂无决策数据", "providersCount": "{count} 个供应商", "healthyCount": "{count} 个健康", diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index 19ea20509..f6ef4054a 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -45,8 +45,21 @@ "billNonSuccessfulRequestsDesc": "开启后,对于响应非 2xx 状态码(例如客户端中断的 499)的请求,只要上游返回了正向 token 用量,仍按用量计费。默认关闭。", "billNonSuccessfulRequestsTooltip": "适用于上游供应商即使响应失败也按 token 计费的场景(如流式中断时仍计入 token)。fake-200 假成功错误响应仍不会计费。", "billHedgeLosers": "对供应商竞速输家计费", - "billHedgeLosersDesc": "开启供应商竞速后,竞速落败的供应商不再被直接掐断,而是在后台保持连接、拿回其 token 用量并计费,其费用会累加进本条请求的总花费。默认开启。", - "billHedgeLosersTooltip": "上游通常即使请求被我们主动取消也照样计费。保活竞速输家可以拿回它们的 token 计数,使 CCH 的扣费与每个上游实际扣费保持一致。每个输家的费用都会异步累加到该请求的花费中。", + "billHedgeLosersDesc": "开启供应商竞速(流式 Hedge 或有界 Discovery)后,只有已经收到有效响应前缀的输家会在后台继续读取并按用量计费,费用会累加到本条请求总花费;其他输家会取消。默认开启。", + "billHedgeLosersTooltip": "只保活已经收到可读取有效响应的输家;SLA 超时、明确失败或未取得有效首字的请求会取消,无法确认的上游费用不会计入。", + "discoveryEnabled": "启用有界供应商 Discovery", + "discoveryEnabledDesc": "启用后,冷启动流式请求会在限定窗口内探测多个供应商,并且最多保留一个保底请求。默认关闭。", + "discoveryConcurrency": "Discovery 首轮并发数", + "maxDiscoveryRounds": "Discovery 最大轮数", + "discoverySlaMs": "Discovery SLA(毫秒)", + "stickySlaMs": "Sticky SLA(毫秒)", + "racingTotalTimeoutMs": "Discovery 总超时(毫秒)", + "stickyTimeoutCooldownMs": "Sticky 超时冷却(毫秒)", + "stickyBindingTtl": "Sticky 绑定有效期", + "stickyBindingTtlDesc": "只读;该值跟随环境变量 SESSION_TTL,并与其他 Session 快照共用。", + "discoveryWindowDesc": "总超时必须不小于 Sticky SLA + 最大轮数 × Discovery SLA。开启输家计费后,只有已取得有效首字的 Discovery 输家会后台读取;SLA 超时、失败或未取得首字的请求仍会取消。", + "discoveryWindowInvalid": "Discovery 总超时短于已配置的 Sticky 与 Discovery 窗口。", + "discoverySettingsInvalid": "一个或多个 Discovery 配置值超出允许范围。", "verboseProviderError": "详细供应商错误信息", "verboseProviderErrorDesc": "开启后,CCH 会在某些上游失败类型下于 `error.details` 返回更详细的诊断信息(例如供应商可用性诊断或脱敏后的上游片段)。", "verboseProviderErrorTooltip": "该选项可能暴露供应商名称、内部路由线索、上游失败原因等诊断信息。仅建议在客户端可以查看底层排障上下文时开启。", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index c38f603d6..7c0466715 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -300,6 +300,7 @@ "title": "計費詳情", "input": "輸入", "output": "輸出", + "cacheWrite": "快取寫入", "cacheWrite5m": "快取寫入(5m)", "cacheWrite1h": "快取寫入(1h)", "cacheRead": "快取讀取", @@ -380,6 +381,140 @@ "overridden": "已被供應商覆寫", "tooltip": "用戶端在請求體中宣告的思考強度(output_config.effort),按原值顯示,代理不會重新命名或轉換等級。" }, + "routingTrace": { + "title": "路由方式", + "modes": { + "discovery": "有界供應商 Discovery", + "legacy_hedge": "舊版 Hedge 競速", + "legacy_serial": "舊版串行故障轉移", + "single_upstream": "單上游模式", + "lease_conflict": "單路保護" + }, + "bypassed": "本請求未使用 Discovery:{reason}", + "bypassReasons": { + "disabled": "Discovery 未啟用", + "non_streaming": "非串流請求", + "retry_not_allowed": "該端點不允許重試", + "provider_switch_not_allowed": "該端點不允許切換供應商", + "raw_passthrough": "原始透傳請求", + "unsupported_protocol": "Discovery 不支援該協議", + "websocket": "WebSocket 請求", + "streaming_hedge_disabled": "本請求已停用串流競速", + "raw_cross_provider_fallback": "已啟用原始跨供應商故障轉移", + "missing_session": "缺少 Session 識別", + "missing_key": "缺少 API Key 識別", + "rollout_ineligible": "本請求不在目前灰度範圍內", + "redis_capability_unavailable": "Redis 綁定能力不可用", + "binding_conflict": "Session 綁定狀態衝突", + "lease_conflict": "另一個請求持有 Discovery 租約,本請求只使用一個上游", + "lease_unavailable": "Discovery 租約不可用", + "unknown": "未滿足 Discovery 准入條件" + }, + "discoveryTitle": "Discovery 輪次", + "discoveryCompact": "{rounds} 輪 · {attempts} 次", + "routeMode": "路由類型", + "routeModes": { + "sticky": "Sticky", + "cold_start": "冷啟動", + "rediscovery": "重新發現" + }, + "finalProvider": "最終供應商", + "roundsLabel": "輪次", + "attemptsLabel": "嘗試", + "winnerOrigin": "贏家來源", + "viewDetails": "查看 Discovery 詳情", + "stickyPhase": "Sticky 探測", + "round": "第 {round} 輪", + "attempts": "{count} 次嘗試", + "noAttempts": "沒有記錄到供應商嘗試", + "providerFallback": "供應商 {id}", + "elapsed": "+{elapsed}ms", + "priority": "優先級 {priority}", + "roles": { + "sticky": "Sticky", + "normal": "候選", + "fallback": "備援" + }, + "winnerSources": { + "sticky": "Sticky", + "normal": "正常候選", + "fallback": "備援接管" + }, + "roleTransition": "{from} → {to}", + "outcomes": { + "success": "成功完成", + "pending": "處理中", + "ready": "已就緒", + "held": "已就緒,暫緩交付", + "winner": "勝出", + "failed": "失敗", + "cancelled": "已中止", + "timeout": "SLA 逾時", + "client_abort": "用戶端已斷開", + "deadline": "總時限到期" + }, + "terminalOutcome": "請求結果", + "bindingResult": "Sticky 綁定", + "bindingActions": { + "create": "建立", + "renew": "續期", + "clear": "解除綁定", + "none": "不變更" + }, + "bindingFallbackWinner": "未寫入 · 備援贏家", + "bindingOutcomes": { + "updated": "綁定已更新", + "cleared": "綁定已解除", + "skipped": "已略過", + "failed": "失敗", + "unknown": "未知狀態" + }, + "fallbackPromoted": "提升為保底請求", + "winnerCommitted": "已提交贏家", + "roundStarted": "輪次開始", + "events": { + "started": "請求已送出", + "ready": "已收到有效首個字元", + "held": "依路由規則暫緩交付", + "fallbackPromoted": "已提升為備援", + "winnerCommitted": "已提交為贏家", + "finished": "結束:{outcome}" + }, + "traceTruncated": "追蹤事件達到儲存上限,部分事件已省略。", + "summary": "{rounds} 輪 · {attempts} 次嘗試 · 最大並發 {maxActive}", + "config": "設定快照", + "configConcurrency": "並發數", + "configMaxRounds": "最大輪數", + "configDiscoverySla": "每輪 SLA", + "configStickySla": "Sticky SLA", + "configTotalTimeout": "總逾時", + "configStickyCooldown": "Sticky 逾時冷卻", + "configStickyBindingTtl": "Sticky 綁定有效期(SESSION_TTL)", + "attemptDetails": { + "providerId": "供應商 ID", + "attempt": "嘗試次數", + "status": "HTTP 狀態", + "inferred": "推斷", + "endpoint": "端點", + "error": "上游錯誤", + "cancellation": "取消緣由", + "timeline": "嘗試時間線", + "billing": "費用與用量", + "billed": "已計入總費用", + "billedCost": "已計入費用", + "winnerCost": "贏家費用", + "notObtained": "已中止,未取得用量" + }, + "cancellationKinds": { + "discovery_loser": "其他 Discovery 嘗試已勝出", + "discovery_sla_timeout": "Discovery SLA 已逾期", + "round_timeout": "本輪 SLA 已到期", + "sticky_timeout": "Sticky SLA 已逾期", + "request_deadline": "Discovery 總時限已到期", + "client_abort": "客戶端已斷開", + "winner_committed": "其他供應商已提交為贏家" + } + }, "reasoningEffort": { "label": "思考強度", "overridden": "已被供應商覆寫", @@ -387,6 +522,7 @@ }, "logicTrace": { "title": "決策鏈", + "singleRouteSelectionTitle": "單路保護下的供應商選擇", "noDecisionData": "暫無決策資料", "providersCount": "{count} 個供應商", "healthyCount": "{count} 個健康", diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index 590ff7a77..308df4e58 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -118,8 +118,21 @@ "billNonSuccessfulRequestsDesc": "開啟後,對於回應非 2xx 狀態碼(例如客戶端中斷的 499)的請求,只要上游回報了正向 token 用量,仍會按用量計費。預設關閉。", "billNonSuccessfulRequestsTooltip": "適用於上游供應商即使回應失敗也按 token 計費的情境(例如串流中斷時仍記入 token)。fake-200 偽成功錯誤響應仍不會計費。", "billHedgeLosers": "對供應商競速輸家計費", - "billHedgeLosersDesc": "開啟供應商競速後,競速落敗的供應商不再被直接掐斷,而是在後台保持連線、取回其 token 用量並計費,其費用會累加進本條請求的總花費。預設開啟。", - "billHedgeLosersTooltip": "上游通常即使請求被我們主動取消也照常計費。保活競速輸家可以取回它們的 token 計數,使 CCH 的扣費與每個上游實際扣費保持一致。每個輸家的費用都會非同步累加到該請求的花費中。", + "billHedgeLosersDesc": "開啟供應商競速(串流 Hedge 或有界 Discovery)後,只有已收到有效回應前綴的輸家會在後台繼續讀取並按用量計費,費用會累加到本條請求總花費;其他輸家會取消。預設開啟。", + "billHedgeLosersTooltip": "只保活已收到可讀取有效回應的輸家;SLA 逾時、明確失敗或未取得有效首字的請求會取消,無法確認的上游費用不會計入。", + "discoveryEnabled": "啟用有界供應商 Discovery", + "discoveryEnabledDesc": "啟用後,冷啟動串流請求會在限定視窗內探測多個供應商,並且最多保留一個保底請求。預設關閉。", + "discoveryConcurrency": "Discovery 首輪並發數", + "maxDiscoveryRounds": "Discovery 最大輪數", + "discoverySlaMs": "Discovery 每輪 SLA(毫秒)", + "stickySlaMs": "Sticky 等待 SLA(毫秒)", + "racingTotalTimeoutMs": "Discovery 總逾時(毫秒)", + "stickyTimeoutCooldownMs": "Sticky 逾時冷卻(毫秒)", + "stickyBindingTtl": "Sticky 綁定有效期", + "stickyBindingTtlDesc": "唯讀;此值跟隨環境變數 SESSION_TTL,並與其他 Session 快照共用。", + "discoveryWindowDesc": "總逾時必須不小於 Sticky SLA + 最大輪數 × Discovery SLA。開啟輸家計費後,只有已取得有效首字的 Discovery 輸家會在後台讀取;SLA 逾時、失敗或未取得首字的請求仍會取消。", + "discoveryWindowInvalid": "Discovery 總逾時短於已設定的 Sticky 與 Discovery 視窗。", + "discoverySettingsInvalid": "一個或多個 Discovery 設定值超出允許範圍。", "verboseProviderError": "詳細供應商錯誤資訊", "verboseProviderErrorDesc": "開啟後,CCH 會在某些上游失敗類型下於 `error.details` 返回較詳細的診斷資訊(例如供應商可用性診斷或脫敏後的上游片段)。", "verboseProviderErrorTooltip": "此選項可能暴露供應商名稱、內部路由線索、上游失敗原因等診斷資訊。僅建議在客戶端可以查看底層排障上下文時開啟。", diff --git a/package.json b/package.json index 786c92e13..bc60d55ca 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "test:coverage:proxy-guard-pipeline": "vitest run --config tests/configs/proxy-guard-pipeline.config.ts --coverage", "test:coverage:include-session-id-in-errors": "vitest run --config tests/configs/include-session-id-in-errors.config.ts --coverage", "test:coverage:usage-logs-sessionid-search": "vitest run --config tests/configs/usage-logs-sessionid-search.config.ts --coverage", + "test:coverage:session-binding": "vitest run --config tests/configs/session-binding.config.ts --coverage", "test:ci": "vitest run --reporter=default --reporter=junit --outputFile.junit=reports/vitest-junit.xml", "test:v1": "vitest run --config tests/configs/v1.config.ts --coverage --reporter=verbose && bun scripts/check-v1-critical-coverage.ts", "openapi:generate": "bun scripts/generate-v1-types.ts", diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index 5c42d12d0..4ffbdf87c 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -1,10 +1,12 @@ "use server"; import { revalidatePath } from "next/cache"; +import { ZodError } from "zod"; import { locales } from "@/i18n/config"; import { emitActionAudit } from "@/lib/audit/emit"; import { getSession } from "@/lib/auth"; import { invalidateSystemSettingsCache } from "@/lib/config"; +import { DEFAULT_SETTINGS } from "@/lib/config/system-settings-cache"; import { logger } from "@/lib/logger"; import { publishCurrentPublicStatusConfigProjection } from "@/lib/public-status/config-publisher"; import { schedulePublicStatusRebuild } from "@/lib/public-status/rebuild-hints"; @@ -14,6 +16,10 @@ import { invalidateAllStatisticsCaches, } from "@/lib/redis"; import { resolveSystemTimezone } from "@/lib/utils/timezone"; +import { + DISCOVERY_WINDOW_INVALID_ERROR_CODE, + getDiscoveryValidationErrorCode, +} from "@/lib/validation/discovery-settings"; import { UpdateSystemSettingsSchema } from "@/lib/validation/schemas"; import { getSystemSettings, updateSystemSettings } from "@/repository/system-config"; import type { IpExtractionConfig } from "@/types/ip-extraction"; @@ -25,6 +31,11 @@ import type { } from "@/types/system-config"; import type { ActionResult } from "./types"; +function discoveryValidationErrorCode(error: unknown): string | null { + if (!(error instanceof ZodError)) return null; + return getDiscoveryValidationErrorCode(error.issues) ?? null; +} + export async function fetchSystemSettings(): Promise> { try { const session = await getSession(); @@ -64,6 +75,13 @@ export async function saveSystemSettings(formData: { codexPriorityBillingSource?: CodexPriorityBillingSource; billNonSuccessfulRequests?: boolean; billHedgeLosers?: boolean; + discoveryEnabled?: boolean; + discoveryConcurrency?: number; + maxDiscoveryRounds?: number; + discoverySlaMs?: number; + stickySlaMs?: number; + racingTotalTimeoutMs?: number; + stickyTimeoutCooldownMs?: number; timezone?: string | null; enableAutoCleanup?: boolean; cleanupRetentionDays?: number; @@ -110,6 +128,30 @@ export async function saveSystemSettings(formData: { before = await getSystemSettings(); const validated = UpdateSystemSettingsSchema.parse(formData); + const effectiveDiscoveryWindow = { + discoverySlaMs: + validated.discoverySlaMs ?? before?.discoverySlaMs ?? DEFAULT_SETTINGS.discoverySlaMs, + stickySlaMs: validated.stickySlaMs ?? before?.stickySlaMs ?? DEFAULT_SETTINGS.stickySlaMs, + maxDiscoveryRounds: + validated.maxDiscoveryRounds ?? + before?.maxDiscoveryRounds ?? + DEFAULT_SETTINGS.maxDiscoveryRounds, + racingTotalTimeoutMs: + validated.racingTotalTimeoutMs ?? + before?.racingTotalTimeoutMs ?? + DEFAULT_SETTINGS.racingTotalTimeoutMs, + }; + if ( + effectiveDiscoveryWindow.racingTotalTimeoutMs < + effectiveDiscoveryWindow.stickySlaMs + + effectiveDiscoveryWindow.maxDiscoveryRounds * effectiveDiscoveryWindow.discoverySlaMs + ) { + return { + ok: false, + error: "Discovery window validation failed.", + errorCode: DISCOVERY_WINDOW_INVALID_ERROR_CODE, + }; + } const updated = await updateSystemSettings({ siteTitle: validated.siteTitle?.trim(), allowGlobalUsageView: validated.allowGlobalUsageView, @@ -118,6 +160,13 @@ export async function saveSystemSettings(formData: { codexPriorityBillingSource: validated.codexPriorityBillingSource, billNonSuccessfulRequests: validated.billNonSuccessfulRequests, billHedgeLosers: validated.billHedgeLosers, + discoveryEnabled: validated.discoveryEnabled, + discoveryConcurrency: validated.discoveryConcurrency, + maxDiscoveryRounds: validated.maxDiscoveryRounds, + discoverySlaMs: validated.discoverySlaMs, + stickySlaMs: validated.stickySlaMs, + racingTotalTimeoutMs: validated.racingTotalTimeoutMs, + stickyTimeoutCooldownMs: validated.stickyTimeoutCooldownMs, timezone: validated.timezone, enableAutoCleanup: validated.enableAutoCleanup, cleanupRetentionDays: validated.cleanupRetentionDays, @@ -239,7 +288,12 @@ export async function saveSystemSettings(formData: { return { ok: true, data: { ...updated, publicStatusProjectionWarningCode } }; } catch (error) { logger.error("更新系统设置失败:", error); - const message = error instanceof Error ? error.message : "更新系统设置失败"; + const validationErrorCode = discoveryValidationErrorCode(error); + const message = validationErrorCode + ? "Discovery settings validation failed." + : error instanceof Error + ? error.message + : "更新系统设置失败"; emitActionAudit({ category: "system_settings", action: "system_settings.update", @@ -249,6 +303,10 @@ export async function saveSystemSettings(formData: { success: false, errorMessage: "UPDATE_FAILED", }); - return { ok: false, error: message }; + return { + ok: false, + error: message, + ...(validationErrorCode ? { errorCode: validationErrorCode } : {}), + }; } } diff --git a/src/actions/usage-logs.ts b/src/actions/usage-logs.ts index e75f71fe3..026412b97 100644 --- a/src/actions/usage-logs.ts +++ b/src/actions/usage-logs.ts @@ -701,6 +701,7 @@ export async function getUsageLogsBatch( const snapshot = liveData.get(key); if (snapshot) { row._liveChain = snapshot; + row.routingTrace = snapshot.routingTrace ?? row.routingTrace; } } } diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx index 4926a53e5..4d45b6260 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx @@ -5,8 +5,10 @@ import { act } from "react"; import { NextIntlClientProvider } from "next-intl"; import { Window } from "happy-dom"; import { beforeEach, describe, expect, test, vi } from "vitest"; +import dashboardMessages from "../../../../../../messages/en/dashboard.json"; import ipDetailsMessages from "../../../../../../messages/en/ipDetails.json"; import providerChainMessages from "../../../../../../messages/en/provider-chain.json"; +import type { RoutingTraceV1 } from "@/types/routing-trace"; const hasSessionMessagesMock = vi.fn(); @@ -206,6 +208,8 @@ const messages = { endpoint: "Endpoint", }, details: { + routingTrace: dashboardMessages.logs.details.routingTrace, + modelAudit: dashboardMessages.logs.details.modelAudit, title: "Request Details", inProgress: "In progress", statusTitle: "Status: {status}", @@ -287,6 +291,7 @@ const messages = { }, logicTrace: { title: "Decision Chain", + singleRouteSelectionTitle: "Provider selection under single-route protection", noDecisionData: "No decision data", providersCount: "{count} providers", healthyCount: "{count} healthy", @@ -346,6 +351,7 @@ const messages = { circuitOpen: "Circuit open", }, billingDetails: { + ...dashboardMessages.logs.details.billingDetails, title: "Billing details", input: "Input", output: "Output", @@ -1216,6 +1222,925 @@ describe("error-details-dialog tabs", () => { }); }); +describe("error-details-dialog routing trace", () => { + const discoveryTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 13_000, + discoveryEnabled: true, + eligible: true, + config: { + discoveryConcurrency: 2, + maxDiscoveryRounds: 2, + discoverySlaMs: 10_000, + stickySlaMs: 20_000, + racingTotalTimeoutMs: 60_000, + stickyTimeoutCooldownMs: 300_000, + sessionTtlSeconds: 300, + }, + events: [ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptId: "sticky:1", + attemptKind: "sticky", + provider: { id: 10, name: "sticky-provider", priority: 1 }, + }, + { + type: "attempt_finished", + at: 3_000, + elapsedMs: 2_000, + round: 0, + attemptId: "sticky:1", + attemptKind: "sticky", + provider: { id: 10, name: "sticky-provider", priority: 1 }, + outcome: "cancelled", + cancellationKind: "sticky_timeout", + }, + { + type: "round_started", + at: 3_000, + elapsedMs: 2_000, + round: 1, + }, + { + type: "attempt_started", + at: 3_010, + elapsedMs: 2_010, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 11, name: "candidate-a", priority: 1 }, + }, + { + type: "attempt_held", + at: 3_100, + elapsedMs: 2_100, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 11, name: "candidate-a", priority: 1 }, + }, + { + type: "attempt_started", + at: 3_020, + elapsedMs: 2_020, + round: 1, + attemptId: "normal:2", + attemptKind: "normal", + provider: { id: 12, name: "candidate-b", priority: 2 }, + }, + { + type: "winner_committed", + at: 4_000, + elapsedMs: 3_000, + round: 1, + attemptId: "normal:2", + attemptKind: "normal", + provider: { id: 12, name: "candidate-b", priority: 2 }, + statusCode: 200, + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 12_000, + ttfbMs: 3_000, + attemptsPerRequest: 3, + maxActiveAttempts: 2, + rounds: 1, + providerMs: 5_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "normal", + winnerProviderId: 12, + winnerRound: 1, + }, + }; + + test("renders Sticky and same-round attempts in the Discovery branch", () => { + const html = renderWithIntl( + + ); + const document = parseHtml(html); + + expect(document.querySelector("[data-testid='routing-mode-banner']")?.textContent).toContain( + "Bounded Discovery" + ); + expect(document.querySelector("[data-testid='discovery-round-0']")?.textContent).toContain( + "sticky-provider" + ); + const roundOne = document.querySelector("[data-testid='discovery-round-1']"); + expect(roundOne?.textContent).toContain("candidate-a"); + expect(roundOne?.textContent).toContain("candidate-b"); + expect(roundOne?.textContent).toContain("Candidate"); + expect(roundOne?.textContent).not.toContain("Candidate → Fallback"); + expect(roundOne?.textContent).toContain("Ready, held"); + expect(roundOne?.textContent).toContain("Winner"); + expect(roundOne?.querySelector(".sm\\:grid-cols-2")).not.toBeNull(); + expect(html).toContain("60000ms"); + expect(html).toContain("300000ms"); + expect(html).toContain("300s"); + }); + + test("shows fallback promotion and humanizes the fallback winner binding result", () => { + const fallbackTrace: RoutingTraceV1 = { + ...discoveryTrace, + events: [ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 80, name: "slow-provider", priority: 1 }, + }, + { + type: "fallback_promoted", + at: 11_000, + elapsedMs: 10_000, + round: 1, + attemptId: "normal:1", + attemptKind: "fallback", + provider: { id: 80, name: "slow-provider", priority: 1 }, + outcome: "timeout", + }, + { + type: "winner_committed", + at: 20_000, + elapsedMs: 19_000, + round: 1, + attemptId: "normal:1", + attemptKind: "fallback", + provider: { id: 80, name: "slow-provider", priority: 1 }, + statusCode: 200, + }, + { + type: "request_finished", + at: 21_000, + elapsedMs: 20_000, + outcome: "success", + statusCode: 200, + }, + { + type: "binding_finalized", + at: 21_100, + elapsedMs: 20_100, + provider: { id: 80, name: "slow-provider" }, + bindingAction: "none", + outcome: "skipped", + reason: "fallback_winner", + }, + ], + summary: { + ...discoveryTrace.summary!, + durationMs: 20_000, + ttfbMs: 19_000, + attemptsPerRequest: 1, + rounds: 1, + fallbackPromotions: 1, + winnerOrigin: "fallback", + winnerProviderId: 80, + winnerRound: 1, + }, + }; + + const html = renderWithIntl( + + ); + const document = parseHtml(html); + const fallbackCard = document.querySelector("[data-testid='discovery-attempt']"); + const terminal = document.querySelector("[data-testid='discovery-terminal-status']"); + const bindingSummary = document.querySelector("[data-testid='discovery-binding-summary']"); + + expect(fallbackCard?.textContent).toContain("Candidate → Fallback"); + expect(fallbackCard?.textContent).toContain("Winner"); + expect(terminal?.textContent).toContain("Not written · Fallback winner"); + expect(terminal?.textContent).not.toContain("fallback_winner"); + expect(bindingSummary?.getAttribute("title")).toBe("fallback_winner"); + expect(terminal?.textContent).not.toContain("No change · Skipped"); + + const normalWinnerHtml = renderWithIntl( + + ); + const normalWinnerTerminal = parseHtml(normalWinnerHtml).querySelector( + "[data-testid='discovery-terminal-status']" + ); + expect(normalWinnerTerminal?.textContent).toContain("No change · Skipped"); + expect(normalWinnerTerminal?.textContent).toContain("fallback_winner"); + expect(normalWinnerTerminal?.textContent).not.toContain("Not written · Fallback winner"); + }); + + test("shows Sticky promotion without inventing a source role for truncated traces", () => { + const { summary: _summary, ...traceWithoutSummary } = discoveryTrace; + const renderRole = (events: RoutingTraceV1["events"], truncated = false) => { + const html = renderWithIntl( + + ); + return parseHtml(html).querySelector("[data-testid='discovery-attempt']")?.textContent ?? ""; + }; + + const stickyRole = renderRole([ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptId: "sticky:1", + attemptKind: "sticky", + provider: { id: 80, name: "sticky-provider", priority: 1 }, + }, + { + type: "fallback_promoted", + at: 21_000, + elapsedMs: 20_000, + round: 0, + attemptId: "sticky:1", + attemptKind: "fallback", + provider: { id: 80, name: "sticky-provider", priority: 1 }, + }, + ]); + const truncatedFallbackRole = renderRole( + [ + { + type: "fallback_promoted", + at: 21_000, + elapsedMs: 20_000, + round: 1, + attemptId: "fallback:1", + attemptKind: "fallback", + provider: { id: 80, name: "fallback-provider", priority: 1 }, + }, + ], + true + ); + + expect(stickyRole).toContain("Sticky → Fallback"); + expect(truncatedFallbackRole).toContain("Fallback"); + expect(truncatedFallbackRole).not.toContain("Candidate → Fallback"); + expect(truncatedFallbackRole).not.toContain("Sticky → Fallback"); + }); + + test("expands exact Discovery attempts with sanitized provider details and cancellation reasons", () => { + const longSecondError = + " second-attempt-429 " + "x".repeat(8_200) + "TAIL_NOT_RENDERED"; + const detailedTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 5_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 1, + attemptId: "80:1", + attemptKind: "normal", + provider: { id: 80, name: "same-provider", priority: 1 }, + }, + { + type: "attempt_finished", + at: 1_500, + elapsedMs: 500, + round: 1, + attemptId: "80:1", + attemptKind: "normal", + provider: { id: 80, name: "same-provider", priority: 1 }, + outcome: "failed", + statusCode: 403, + }, + { + type: "attempt_started", + at: 1_600, + elapsedMs: 600, + round: 1, + attemptId: "80:2", + attemptKind: "normal", + provider: { id: 80, name: "same-provider", priority: 1 }, + }, + { + type: "attempt_finished", + at: 2_000, + elapsedMs: 1_000, + round: 1, + attemptId: "80:2", + attemptKind: "normal", + provider: { id: 80, name: "same-provider", priority: 1 }, + outcome: "failed", + statusCode: 429, + }, + { + type: "attempt_started", + at: 2_100, + elapsedMs: 1_100, + round: 1, + attemptId: "91:3", + attemptKind: "normal", + provider: { id: 91, name: "legacy-provider", priority: 2 }, + }, + { + type: "attempt_finished", + at: 2_500, + elapsedMs: 1_500, + round: 1, + attemptId: "91:3", + attemptKind: "normal", + provider: { id: 91, name: "legacy-provider", priority: 2 }, + outcome: "failed", + statusCode: 503, + }, + { + type: "attempt_started", + at: 2_600, + elapsedMs: 1_600, + round: 1, + attemptId: "56:4", + attemptKind: "normal", + provider: { id: 56, name: "cancelled-provider", priority: 3 }, + }, + { + type: "attempt_finished", + at: 5_000, + elapsedMs: 4_000, + round: 1, + attemptId: "56:4", + attemptKind: "normal", + provider: { id: 56, name: "cancelled-provider", priority: 3 }, + outcome: "cancelled", + cancellationKind: "discovery_sla_timeout", + }, + ], + }; + const { container, unmount } = renderClientWithIntl( + + ); + + const attempts = Array.from( + container.querySelectorAll("[data-testid='discovery-attempt']") + ); + const secondAttempt = attempts.find((card) => card.textContent?.includes("HTTP 429")); + const secondToggle = + secondAttempt?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null; + expect(secondToggle?.getAttribute("aria-expanded")).toBe("false"); + click(secondToggle); + expect(secondToggle?.getAttribute("aria-expanded")).toBe("true"); + expect(secondAttempt?.textContent).toContain("second-attempt-429"); + expect(secondAttempt?.textContent).not.toContain("first-attempt-403"); + expect(secondAttempt?.textContent).toContain("https://api.example.com/v2"); + expect(secondAttempt?.textContent).not.toContain("secret-two"); + expect(secondAttempt?.textContent).not.toContain("TAIL_NOT_RENDERED"); + expect(secondAttempt?.querySelector("script")).toBeNull(); + + const legacyAttempt = attempts.find((card) => card.textContent?.includes("HTTP 503")); + click(legacyAttempt?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + expect(legacyAttempt?.textContent).toContain("third-attempt-503"); + expect(legacyAttempt?.textContent).toContain("https://api.example.com/v3"); + expect(legacyAttempt?.textContent).not.toContain("endpoint-user"); + expect(legacyAttempt?.textContent).not.toContain("endpoint-secret"); + expect(legacyAttempt?.textContent).not.toContain("abcdefghijklmnopqrstuvwxyz123456"); + expect(legacyAttempt?.textContent).not.toContain("bearer-secret-value"); + + const cancelledAttempt = attempts.find((card) => + card.textContent?.includes("cancelled-provider") + ); + click(cancelledAttempt?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + expect(cancelledAttempt?.textContent).toContain("Discovery SLA expired"); + expect(cancelledAttempt?.textContent).not.toContain("Upstream error"); + unmount(); + }); + + test("maps billed Discovery losers to attempt cards without treating cancelled usage as zero", () => { + const billingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 8_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 1, + attemptId: "101:1", + attemptKind: "normal", + provider: { id: 101, name: "billed-loser", priority: 1 }, + }, + { + type: "attempt_ready", + at: 2_000, + elapsedMs: 1_000, + round: 1, + attemptId: "101:1", + attemptKind: "normal", + provider: { id: 101, name: "billed-loser", priority: 1 }, + }, + { + type: "attempt_finished", + at: 3_000, + elapsedMs: 2_000, + round: 1, + attemptId: "101:1", + attemptKind: "normal", + provider: { id: 101, name: "billed-loser", priority: 1 }, + outcome: "cancelled", + cancellationKind: "winner_committed", + }, + { + type: "attempt_started", + at: 1_100, + elapsedMs: 100, + round: 1, + attemptId: "102:2", + attemptKind: "normal", + provider: { id: 102, name: "winner-provider", priority: 1 }, + }, + { + type: "winner_committed", + at: 2_500, + elapsedMs: 1_500, + round: 1, + attemptId: "102:2", + attemptKind: "normal", + provider: { id: 102, name: "winner-provider", priority: 1 }, + statusCode: 200, + }, + { + type: "attempt_started", + at: 1_200, + elapsedMs: 200, + round: 1, + attemptId: "103:3", + attemptKind: "normal", + provider: { id: 103, name: "cancelled-without-usage", priority: 2 }, + }, + { + type: "attempt_finished", + at: 3_100, + elapsedMs: 2_100, + round: 1, + attemptId: "103:3", + attemptKind: "normal", + provider: { id: 103, name: "cancelled-without-usage", priority: 2 }, + outcome: "cancelled", + cancellationKind: "discovery_sla_timeout", + }, + { + type: "attempt_started", + at: 1_300, + elapsedMs: 300, + round: 1, + attemptId: "101:4", + attemptKind: "normal", + provider: { id: 101, name: "billed-loser", priority: 1 }, + }, + { + type: "attempt_finished", + at: 3_200, + elapsedMs: 2_200, + round: 1, + attemptId: "101:4", + attemptKind: "normal", + provider: { id: 101, name: "billed-loser", priority: 1 }, + outcome: "cancelled", + cancellationKind: "discovery_sla_timeout", + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 7_000, + ttfbMs: 1_500, + attemptsPerRequest: 4, + maxActiveAttempts: 4, + rounds: 1, + providerMs: 5_100, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "normal", + winnerProviderId: 102, + winnerRound: 1, + }, + }; + const { container, unmount } = renderClientWithIntl( + + ); + + const cards = Array.from( + container.querySelectorAll("[data-testid='discovery-attempt']") + ); + const repeatedProviderCards = cards.filter((card) => + card.textContent?.includes("billed-loser") + ); + const billedLoser = repeatedProviderCards[0]; + const sameProviderWithoutUsage = repeatedProviderCards[1]; + const winner = cards.find((card) => card.textContent?.includes("winner-provider")); + const cancelled = cards.find((card) => card.textContent?.includes("cancelled-without-usage")); + click(billedLoser?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + click( + sameProviderWithoutUsage?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null + ); + click(winner?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + click(cancelled?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + + expect(billedLoser?.textContent).toContain("Included in total cost"); + expect(billedLoser?.textContent).toContain("$0.006000"); + expect(billedLoser?.textContent).toContain("Input 50"); + expect(billedLoser?.textContent).toContain("Output 12"); + expect(billedLoser?.textContent).toContain("Cache Write 10"); + expect(sameProviderWithoutUsage?.textContent).toContain("Cancelled; usage unavailable"); + expect(sameProviderWithoutUsage?.textContent).not.toContain("$0.006000"); + expect(winner?.textContent).toContain("Winner cost"); + expect(winner?.textContent).toContain("$0.009000"); + expect(winner?.textContent).toContain("Input 100"); + expect(winner?.textContent).toContain("Output 30"); + expect(winner?.textContent).toContain("Cache Write 20"); + expect(cancelled?.textContent).toContain("Cancelled; usage unavailable"); + expect(cancelled?.textContent).not.toContain("$0.000000"); + + const summaryTable = container.querySelector( + "[data-testid='provider-racing-billing-table']" + ); + expect(summaryTable?.textContent).toContain("winner-provider"); + expect(summaryTable?.textContent).toContain("#2"); + expect(summaryTable?.textContent).toContain("$0.009000"); + expect(summaryTable?.textContent).toContain("billed-loser"); + expect(summaryTable?.textContent).toContain("#1"); + expect(summaryTable?.textContent).toContain("$0.006000"); + expect(summaryTable?.textContent).toContain("$0.015000"); + unmount(); + }); + + test("does not attach an older loser cost to a later winner from the same provider", () => { + const trace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "attempt_started", + at: 1_000, + elapsedMs: 0, + round: 1, + attemptId: "101:4", + attemptKind: "normal", + provider: { id: 101, name: "same-provider-winner", priority: 1 }, + }, + { + type: "attempt_ready", + at: 2_000, + elapsedMs: 1_000, + round: 1, + attemptId: "101:4", + attemptKind: "normal", + provider: { id: 101, name: "same-provider-winner", priority: 1 }, + }, + { + type: "winner_committed", + at: 2_100, + elapsedMs: 1_100, + round: 1, + attemptId: "101:4", + attemptKind: "normal", + provider: { id: 101, name: "same-provider-winner", priority: 1 }, + outcome: "winner", + statusCode: 200, + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 2_000, + ttfbMs: 1_100, + attemptsPerRequest: 2, + maxActiveAttempts: 2, + rounds: 1, + providerMs: 3_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "normal", + winnerProviderId: 101, + winnerRound: 1, + }, + }; + const { container, unmount } = renderClientWithIntl( + + ); + + const winner = container.querySelector("[data-testid='discovery-attempt']"); + click(winner?.querySelector("[data-testid='discovery-attempt-toggle']") ?? null); + expect(winner?.textContent).toContain("Winner cost"); + expect(winner?.textContent).toContain("$0.009000"); + expect(winner?.textContent).not.toContain("$0.006000"); + unmount(); + }); + + test("labels a lease conflict as single-route protection while retaining provider selection", () => { + const protectedTrace: RoutingTraceV1 = { + version: 1, + mode: "single_upstream", + startedAt: 1_000, + updatedAt: 2_000, + discoveryEnabled: true, + eligible: false, + bypassReason: "lease_conflict", + events: [], + }; + const html = renderWithIntl( + + ); + + expect(html).toContain("Single-route protection"); + expect(html).toContain("Provider selection under single-route protection"); + expect(html).toContain("protected-provider"); + }); + + test("shows a legacy mode and bypass reason while retaining the old chain", () => { + const legacyTrace: RoutingTraceV1 = { + version: 1, + mode: "legacy_serial", + startedAt: 1_000, + updatedAt: 2_000, + discoveryEnabled: true, + eligible: false, + bypassReason: "non_streaming", + events: [], + }; + const html = renderWithIntl( + + ); + + expect(html).toContain("Legacy serial fallback"); + expect(html).toContain("Non-streaming request"); + expect(html).toContain("legacy-provider"); + expect(html).not.toContain("Discovery rounds"); + }); + + test("shows late terminal failure and Sticky binding result after a first-byte winner", () => { + const failedTrace: RoutingTraceV1 = { + ...discoveryTrace, + events: [ + ...discoveryTrace.events, + { + type: "request_finished", + at: 5_000, + elapsedMs: 4_000, + outcome: "failed", + statusCode: 502, + }, + { + type: "binding_finalized", + at: 5_100, + elapsedMs: 4_100, + provider: { id: 12, name: "candidate-b" }, + bindingAction: "renew", + outcome: "skipped", + reason: "generation_conflict", + }, + ], + summary: { ...discoveryTrace.summary!, outcome: "failed", statusCode: 502 }, + }; + const html = renderWithIntl( + + ); + const document = parseHtml(html); + const terminal = document.querySelector("[data-testid='discovery-terminal-status']"); + + expect(terminal?.textContent).toContain("Request result"); + expect(terminal?.textContent).toContain("Failed"); + expect(terminal?.textContent).toContain("HTTP 502"); + expect(terminal?.textContent).toContain("Sticky binding"); + expect(terminal?.textContent).toContain("Renew"); + expect(terminal?.textContent).toContain("generation_conflict"); + expect(document.querySelector("[data-testid='discovery-round-1']")?.textContent).toContain( + "Failed" + ); + }); + + test("derives live attempt concurrency while the terminal summary is not available", () => { + const { summary: _summary, ...liveTrace } = discoveryTrace; + const html = renderWithIntl( + + ); + const document = parseHtml(html); + const discoveryView = document.querySelector("[data-testid='discovery-trace-view']"); + + expect(discoveryView?.textContent).toContain("1 rounds · 3 attempts · max 2 active"); + expect(discoveryView?.textContent).not.toContain("0 attempts · max 0 active"); + }); + + test("falls back to the old chain for an unsupported trace version", () => { + const html = renderWithIntl( + + ); + + expect(html).toContain("old-provider"); + expect(html).not.toContain("Bounded Discovery"); + }); +}); + describe("error-details-dialog origin decision chain", () => { test("shows origin chain trigger for session reuse flow with sessionId", () => { const html = renderWithIntl( diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/DiscoveryTraceView.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/DiscoveryTraceView.tsx new file mode 100644 index 000000000..48fd063bf --- /dev/null +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/DiscoveryTraceView.tsx @@ -0,0 +1,1039 @@ +"use client"; + +import { + CheckCircle, + ChevronDown, + ChevronRight, + CircleDot, + Clock3, + GitBranch, + Link2, + Server, + ShieldCheck, + XCircle, + Zap, +} from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; +import { Badge } from "@/components/ui/badge"; +import { cn, formatTokenAmount } from "@/lib/utils"; +import { formatCurrency } from "@/lib/utils/currency"; +import { summarizeHedgeBilling } from "@/lib/utils/hedge-billing"; +import { redactJsonString } from "@/lib/utils/message-redaction"; +import { sanitizeErrorTextForDetail } from "@/lib/utils/upstream-error-detection"; +import type { HedgeLoserBilling } from "@/types/cost-breakdown"; +import type { ProviderChainItem } from "@/types/message"; +import type { RoutingTraceV1 } from "@/types/routing-trace"; + +const KNOWN_BYPASS_REASONS = new Set([ + "disabled", + "non_streaming", + "retry_not_allowed", + "provider_switch_not_allowed", + "raw_passthrough", + "unsupported_protocol", + "websocket", + "streaming_hedge_disabled", + "raw_cross_provider_fallback", + "missing_session", + "missing_key", + "rollout_ineligible", + "redis_capability_unavailable", + "binding_conflict", + "lease_conflict", + "lease_unavailable", +]); + +const KNOWN_CANCELLATION_KINDS = new Set([ + "discovery_loser", + "discovery_sla_timeout", + "round_timeout", + "sticky_timeout", + "request_deadline", + "client_abort", + "winner_committed", +]); + +type TraceRecord = Record; + +type AttemptView = { + id: string; + providerId: number | null; + providerName: string | null; + sequence: number | null; + round: number; + role: "sticky" | "normal" | "fallback"; + promotedFrom: "sticky" | "normal" | null; + priority: number | null; + startedAt: number | null; + elapsedMs: number | null; + outcome: + | "pending" + | "ready" + | "held" + | "winner" + | "failed" + | "cancelled" + | "timeout" + | "client_abort" + | "deadline"; + statusCode: number | null; + cancellationKind: string | null; + reason: string | null; + fallbackPromoted: boolean; + winnerCommitted: boolean; + chainItem: ProviderChainItem | null; + billingEntry: HedgeLoserBilling | null; + billingStatus: "none" | "billed" | "not_obtained"; + winnerCostUsd: string | null; + history: Array<{ + type: string; + elapsedMs: number | null; + outcome: AttemptView["outcome"] | null; + cancellationKind: string | null; + reason: string | null; + }>; +}; + +function findDiscoveryBillingEntry( + hedgeLosers: HedgeLoserBilling[] | null | undefined, + attempt: Pick +): HedgeLoserBilling | null { + if (!hedgeLosers || hedgeLosers.length === 0 || attempt.providerId == null) return null; + const candidates = hedgeLosers.filter((entry) => entry.providerId === attempt.providerId); + if (candidates.length === 0) return null; + + const attemptNumbers = [attempt.sequence ?? attempt.chainItem?.attemptNumber].filter( + (number): number is number => number != null && Number.isFinite(number) + ); + for (const attemptNumber of attemptNumbers) { + const exact = candidates.find((entry) => entry.attemptNumber === attemptNumber); + if (exact) return exact; + } + return null; +} + +function inferMissingBillingStatus(attempt: AttemptView): AttemptView["billingStatus"] { + if (attempt.winnerCommitted || attempt.outcome === "winner") return "none"; + if ( + attempt.outcome === "cancelled" || + attempt.outcome === "timeout" || + attempt.outcome === "client_abort" || + attempt.outcome === "deadline" || + attempt.outcome === "failed" + ) { + return "not_obtained"; + } + return "none"; +} + +function asRecord(value: unknown): TraceRecord { + return value && typeof value === "object" && !Array.isArray(value) ? (value as TraceRecord) : {}; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.length > 0 ? value : null; +} + +function asNumber(value: unknown): number | null { + return typeof value === "number" && Number.isFinite(value) ? value : null; +} + +function isDisplayableCost(value: unknown): value is string { + if (typeof value !== "string" || value.trim() === "") return false; + const parsed = Number(value); + return Number.isFinite(parsed) && parsed >= 0; +} + +function parseAttemptSequence(attemptId: string): number | null { + const match = /:(\d+)$/.exec(attemptId); + return match ? Number(match[1]) : null; +} + +function truncateForDisplay(value: string, maxLength = 8_192): string { + return value.length > maxLength ? `${value.slice(0, maxLength)}\n...` : value; +} + +function sanitizeEndpoint(value: string | undefined): string | null { + if (!value) return null; + try { + const url = new URL(value); + url.username = ""; + url.password = ""; + url.search = ""; + url.hash = ""; + return truncateForDisplay(url.toString(), 2_048); + } catch { + return truncateForDisplay(value.split(/[?#]/, 1)[0], 2_048); + } +} + +function getChainErrorMessage(item: ProviderChainItem | null): string | null { + if (!item) return null; + const sanitize = (value: string) => + truncateForDisplay(sanitizeErrorTextForDetail(redactJsonString(value))); + if (item.errorMessage) return sanitize(item.errorMessage); + if (item.errorDetails?.provider?.upstreamBody) { + return sanitize(item.errorDetails.provider.upstreamBody); + } + if (item.errorDetails?.system?.errorMessage) { + return sanitize(item.errorDetails.system.errorMessage); + } + if (item.errorDetails?.clientError) return sanitize(item.errorDetails.clientError); + return null; +} + +function buildProviderChainLookup(providerChain: ProviderChainItem[]): { + byAttempt: Map; + uniqueByProvider: Map; +} { + const byAttempt = new Map(); + const byProvider = new Map(); + for (const item of providerChain) { + const items = byProvider.get(item.id) ?? []; + items.push(item); + byProvider.set(item.id, items); + if (Number.isFinite(item.attemptNumber)) { + byAttempt.set(`${item.id}:${item.attemptNumber}`, item); + } + } + + const uniqueByProvider = new Map(); + for (const [providerId, items] of byProvider) { + if (items.length === 1) uniqueByProvider.set(providerId, items[0]); + } + return { byAttempt, uniqueByProvider }; +} + +function findChainItem( + attemptId: string, + providerId: number | null, + lookup: ReturnType +): ProviderChainItem | null { + if (providerId == null) return null; + const sequence = parseAttemptSequence(attemptId); + if (sequence != null) { + const exact = lookup.byAttempt.get(`${providerId}:${sequence}`); + if (exact) return exact; + } + return lookup.uniqueByProvider.get(providerId) ?? null; +} + +function eventType(event: TraceRecord): string { + return asString(event.type) ?? asString(event.event) ?? "unknown"; +} + +function eventProvider(event: TraceRecord): { + id: number | null; + name: string | null; + priority: number | null; +} { + const provider = asRecord(event.provider); + return { + id: asNumber(event.providerId) ?? asNumber(provider.id), + name: asString(event.providerName) ?? asString(provider.name), + priority: asNumber(event.priority) ?? asNumber(provider.priority), + }; +} + +function normalizeRole(value: unknown, round: number): AttemptView["role"] { + if (value === "fallback") return "fallback"; + if (value === "sticky" || round === 0) return "sticky"; + return "normal"; +} + +function normalizeOutcome(value: unknown): AttemptView["outcome"] | null { + switch (value) { + case "pending": + case "ready": + case "held": + case "winner": + case "failed": + case "cancelled": + case "timeout": + case "client_abort": + case "deadline": + return value; + case "sla_timeout": + return "timeout"; + default: + return null; + } +} + +function normalizeTerminalOutcome( + value: unknown +): "success" | "failed" | "client_abort" | "deadline" | "pending" { + switch (value) { + case "success": + case "failed": + case "client_abort": + case "deadline": + return value; + default: + return "pending"; + } +} + +function applyEventOutcome( + attempt: AttemptView, + type: string, + event: TraceRecord, + roleBeforeEvent: AttemptView["role"] | null +): void { + const explicit = normalizeOutcome(event.outcome); + if (explicit) attempt.outcome = explicit; + + switch (type) { + case "attempt_ready": + attempt.outcome = "ready"; + break; + case "attempt_held": + attempt.outcome = "held"; + break; + case "attempt_failed": + attempt.outcome = "failed"; + break; + case "attempt_cancelled": + attempt.outcome = "cancelled"; + break; + case "fallback_promoted": + if (roleBeforeEvent && roleBeforeEvent !== "fallback") { + attempt.promotedFrom = roleBeforeEvent; + } + attempt.role = "fallback"; + attempt.fallbackPromoted = true; + break; + case "winner_committed": + attempt.outcome = "winner"; + attempt.winnerCommitted = true; + break; + } + + const cancellationKind = asString(event.cancellationKind); + if (cancellationKind === "client_abort") attempt.outcome = "client_abort"; + if (cancellationKind === "request_deadline") attempt.outcome = "deadline"; + if ( + cancellationKind === "discovery_sla_timeout" || + cancellationKind === "round_timeout" || + cancellationKind === "sticky_timeout" + ) { + attempt.outcome = "timeout"; + } +} + +function buildAttempts( + trace: RoutingTraceV1, + providerChain: ProviderChainItem[], + hedgeLosers: HedgeLoserBilling[] | null | undefined, + costUsd: string | null | undefined +): AttemptView[] { + const attempts = new Map(); + const lookup = buildProviderChainLookup(providerChain); + + for (const rawEvent of trace.events) { + const event = asRecord(rawEvent); + const type = eventType(event); + const provider = eventProvider(event); + const attemptId = + asString(event.attemptId) ?? + (provider.id != null && type.startsWith("attempt_") + ? `${provider.id}:${asNumber(event.sequence) ?? attempts.size + 1}` + : null); + if (!attemptId) continue; + + const round = Math.max(0, asNumber(event.round) ?? 0); + const existing = attempts.get(attemptId); + const initialRole = normalizeRole(event.attemptKind ?? event.role ?? event.kind, round); + const attempt: AttemptView = existing ?? { + id: attemptId, + providerId: provider.id, + providerName: provider.name, + sequence: parseAttemptSequence(attemptId), + round, + role: initialRole, + promotedFrom: null, + priority: provider.priority, + startedAt: null, + elapsedMs: null, + outcome: "pending", + statusCode: null, + cancellationKind: null, + reason: null, + fallbackPromoted: false, + winnerCommitted: false, + chainItem: findChainItem(attemptId, provider.id, lookup), + billingEntry: null, + billingStatus: "none", + winnerCostUsd: null, + history: [], + }; + + attempt.providerId ??= provider.id; + attempt.providerName ??= provider.name; + attempt.sequence ??= parseAttemptSequence(attempt.id); + attempt.chainItem ??= findChainItem(attempt.id, attempt.providerId, lookup); + attempt.round = Math.max(attempt.round, round); + attempt.priority ??= provider.priority; + attempt.statusCode ??= asNumber(event.statusCode); + attempt.cancellationKind ??= asString(event.cancellationKind); + attempt.reason ??= asString(event.reason); + const elapsedMs = asNumber(event.elapsedMs); + if (type === "attempt_started") attempt.startedAt = elapsedMs; + if (elapsedMs != null) attempt.elapsedMs = elapsedMs; + const roleBeforeEvent = existing ? attempt.role : null; + attempt.role = + attempt.role === "fallback" + ? "fallback" + : normalizeRole( + event.attemptKind ?? event.role ?? event.kind ?? attempt.role, + attempt.round + ); + applyEventOutcome(attempt, type, event, roleBeforeEvent); + if ( + type === "attempt_started" || + type === "attempt_ready" || + type === "attempt_held" || + type === "attempt_finished" || + type === "fallback_promoted" || + type === "winner_committed" + ) { + attempt.history.push({ + type, + elapsedMs, + outcome: normalizeOutcome(event.outcome), + cancellationKind: asString(event.cancellationKind), + reason: asString(event.reason), + }); + } + attempts.set(attemptId, attempt); + } + + const terminalEvent = trace.events.findLast((event) => event.type === "request_finished"); + const terminalOutcome = normalizeTerminalOutcome(terminalEvent?.outcome); + if (terminalEvent && terminalOutcome !== "success") { + for (const attempt of attempts.values()) { + if (!attempt.winnerCommitted) continue; + attempt.outcome = + terminalOutcome === "client_abort" || terminalOutcome === "deadline" + ? terminalOutcome + : "failed"; + attempt.statusCode = terminalEvent?.statusCode ?? attempt.statusCode; + } + } + + const sortedAttempts = [...attempts.values()].sort((a, b) => { + if (a.round !== b.round) return a.round - b.round; + return (a.startedAt ?? Number.MAX_SAFE_INTEGER) - (b.startedAt ?? Number.MAX_SAFE_INTEGER); + }); + + const hedgeSummary = summarizeHedgeBilling(costUsd, hedgeLosers); + for (const attempt of sortedAttempts) { + const billingEntry = findDiscoveryBillingEntry(hedgeLosers, attempt); + if (billingEntry) { + attempt.billingEntry = billingEntry; + attempt.billingStatus = "billed"; + continue; + } + + attempt.billingStatus = inferMissingBillingStatus(attempt); + if (attempt.winnerCommitted || attempt.outcome === "winner") { + // When billed losers exist, subtract them from the persisted request total + // so the winner card agrees with the existing hedge billing table. With no + // loser entry, costUsd is the only safe winner amount available. + attempt.winnerCostUsd = hedgeSummary?.winnerCost ?? costUsd ?? null; + } + } + + return sortedAttempts; +} + +function numberFrom(record: TraceRecord, ...keys: string[]): number | null { + for (const key of keys) { + const value = asNumber(record[key]); + if (value != null) return value; + } + return null; +} + +function deriveRuntimeStats(trace: RoutingTraceV1): { + rounds: number; + attemptCount: number; + maxActive: number; +} { + const startedAttempts = new Set(); + const activeAttempts = new Set(); + let anonymousAttempts = 0; + let maxActive = 0; + let maxRound = 0; + + for (const event of trace.events) { + if (typeof event.round === "number" && Number.isFinite(event.round)) { + maxRound = Math.max(maxRound, event.round); + } + + if (event.type === "attempt_started") { + if (!event.attemptId) { + anonymousAttempts += 1; + continue; + } + if (!startedAttempts.has(event.attemptId)) { + startedAttempts.add(event.attemptId); + activeAttempts.add(event.attemptId); + maxActive = Math.max(maxActive, activeAttempts.size); + } + continue; + } + + if (event.type === "attempt_finished" && event.attemptId) { + activeAttempts.delete(event.attemptId); + } + } + + return { + rounds: maxRound, + attemptCount: startedAttempts.size + anonymousAttempts, + maxActive, + }; +} + +function outcomeStyle(outcome: AttemptView["outcome"]): { + icon: typeof Server; + className: string; +} { + switch (outcome) { + case "winner": + return { + icon: CheckCircle, + className: + "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/20 dark:text-emerald-300", + }; + case "failed": + return { + icon: XCircle, + className: + "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-800 dark:bg-rose-950/20 dark:text-rose-300", + }; + case "cancelled": + case "client_abort": + case "deadline": + return { + icon: XCircle, + className: + "border-slate-200 bg-slate-50 text-slate-600 dark:border-slate-700 dark:bg-slate-900/40 dark:text-slate-300", + }; + case "held": + case "timeout": + return { + icon: Clock3, + className: + "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950/20 dark:text-amber-300", + }; + case "ready": + return { + icon: ShieldCheck, + className: + "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/20 dark:text-blue-300", + }; + default: + return { + icon: Server, + className: "border-border bg-muted/30 text-muted-foreground dark:bg-slate-900/30", + }; + } +} + +export function RoutingModeBanner({ trace }: { trace: RoutingTraceV1 }) { + const t = useTranslations("dashboard.logs.details.routingTrace"); + const isLeaseConflict = + trace.mode === "single_upstream" && trace.bypassReason === "lease_conflict"; + const Icon = + trace.mode === "discovery" + ? Zap + : isLeaseConflict + ? ShieldCheck + : trace.mode === "single_upstream" + ? Server + : GitBranch; + const rawReason = trace.bypassReason; + const reason = + rawReason && KNOWN_BYPASS_REASONS.has(rawReason) ? rawReason : rawReason ? "unknown" : null; + + return ( +
+
+ + {t("title")} + + {isLeaseConflict ? t("modes.lease_conflict") : t(`modes.${trace.mode}`)} + +
+ {reason && ( +

+ {t("bypassed", { reason: t(`bypassReasons.${reason}`) })} +

+ )} +
+ ); +} + +export function DiscoveryTraceView({ + trace, + providerChain = [], + hedgeLosers, + costUsd, + winnerUsage, +}: { + trace: RoutingTraceV1; + providerChain?: ProviderChainItem[]; + hedgeLosers?: HedgeLoserBilling[] | null; + costUsd?: string | null; + winnerUsage?: { + inputTokens?: number | null; + outputTokens?: number | null; + cacheCreationInputTokens?: number | null; + cacheReadInputTokens?: number | null; + }; +}) { + const t = useTranslations("dashboard.logs.details.routingTrace"); + const attempts = buildAttempts(trace, providerChain, hedgeLosers, costUsd); + const grouped = new Map(); + for (const attempt of attempts) { + const group = grouped.get(attempt.round) ?? []; + group.push(attempt); + grouped.set(attempt.round, group); + } + + const summary = asRecord(trace.summary); + const config = asRecord(trace.config); + const runtimeStats = deriveRuntimeStats(trace); + const rounds = + numberFrom(summary, "rounds", "roundsVisited") ?? + Math.max(runtimeStats.rounds, ...attempts.map((attempt) => attempt.round)); + const attemptCount = + numberFrom(summary, "attemptsPerRequest", "attempts", "attemptsStarted") ?? + Math.max(runtimeStats.attemptCount, attempts.length); + const maxActive = numberFrom(summary, "maxActive", "maxActiveAttempts") ?? runtimeStats.maxActive; + const terminalEvent = trace.events.findLast((event) => event.type === "request_finished"); + const bindingEvent = trace.events.findLast((event) => event.type === "binding_finalized"); + const terminalOutcome = normalizeTerminalOutcome(terminalEvent?.outcome); + const bindingOutcome = + bindingEvent?.outcome === "updated" || + bindingEvent?.outcome === "cleared" || + bindingEvent?.outcome === "skipped" || + bindingEvent?.outcome === "failed" + ? bindingEvent.outcome + : "unknown"; + const isFallbackWinnerBinding = + bindingEvent?.bindingAction === "none" && + bindingOutcome === "skipped" && + bindingEvent.reason === "fallback_winner" && + asString(summary.winnerOrigin) === "fallback"; + const bindingSummary = isFallbackWinnerBinding + ? t("bindingFallbackWinner") + : bindingEvent + ? `${t(`bindingActions.${bindingEvent.bindingAction ?? "none"}`)} · ${t(`bindingOutcomes.${bindingOutcome}`)}` + : null; + + return ( +
+
+
+

+ + {t("discoveryTitle")} +

+

+ {t("summary", { rounds, attempts: attemptCount, maxActive })} +

+
+ {trace.truncated && ( + + {t("traceTruncated")} + + )} +
+ + {(terminalEvent || bindingEvent) && ( +
+ {terminalEvent && ( +
+ {t("terminalOutcome")} +
+ {t(`outcomes.${terminalOutcome}`)} + {terminalEvent.statusCode != null && ( + HTTP {terminalEvent.statusCode} + )} +
+
+ )} + {bindingEvent && ( +
+ {t("bindingResult")} +
+
+ {bindingSummary} +
+ {bindingEvent.reason && !isFallbackWinnerBinding && ( +
+ {bindingEvent.reason} +
+ )} +
+
+ )} +
+ )} + + {Object.keys(config).length > 0 && ( +
+
{t("config")}
+
+ {numberFrom(config, "discoveryConcurrency", "concurrency") != null && ( + + )} + {numberFrom(config, "maxDiscoveryRounds", "maxRounds") != null && ( + + )} + {numberFrom(config, "discoverySlaMs") != null && ( + + )} + {numberFrom(config, "stickySlaMs") != null && ( + + )} + {numberFrom(config, "racingTotalTimeoutMs", "totalTimeoutMs") != null && ( + + )} + {numberFrom(config, "stickyTimeoutCooldownMs") != null && ( + + )} + {numberFrom(config, "sessionTtlSeconds") != null && ( + + )} +
+
+ )} + + {grouped.size === 0 ? ( +
+ + {t("noAttempts")} +
+ ) : ( +
+ {[...grouped.entries()].map(([round, roundAttempts]) => ( +
+
+
+ {round === 0 ? ( + + ) : ( + + )} +
+ {round === 0 ? t("stickyPhase") : t("round", { round })} +
+
+ + {t("attempts", { count: roundAttempts.length })} + +
+
+ {roundAttempts.map((attempt) => ( + + ))} +
+
+ ))} +
+ )} +
+ ); +} + +function TraceValue({ label, value }: { label: string; value: string | number | null }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function AttemptCard({ + attempt, + winnerUsage, +}: { + attempt: AttemptView; + winnerUsage?: { + inputTokens?: number | null; + outputTokens?: number | null; + cacheCreationInputTokens?: number | null; + cacheReadInputTokens?: number | null; + }; +}) { + const t = useTranslations("dashboard.logs.details.routingTrace"); + const tDetails = useTranslations("dashboard.logs.details"); + const [expanded, setExpanded] = useState(false); + const style = outcomeStyle(attempt.outcome); + const Icon = style.icon; + const providerName = + attempt.providerName ?? t("providerFallback", { id: attempt.providerId ?? "-" }); + const chainItem = attempt.chainItem; + const statusCode = chainItem?.statusCode ?? attempt.statusCode; + const endpoint = sanitizeEndpoint(chainItem?.endpointUrl); + const errorMessage = getChainErrorMessage(chainItem); + const roleLabel = attempt.promotedFrom + ? t("roleTransition", { + from: t(`roles.${attempt.promotedFrom}`), + to: t(`roles.${attempt.role}`), + }) + : t(`roles.${attempt.role}`); + const cancellationLabel = attempt.cancellationKind + ? KNOWN_CANCELLATION_KINDS.has(attempt.cancellationKind) + ? t(`cancellationKinds.${attempt.cancellationKind}`) + : attempt.cancellationKind + : null; + + const billingEntry = attempt.billingEntry; + const billedCost = attempt.winnerCostUsd ?? billingEntry?.costUsd; + const tokenRows = billingEntry + ? [ + ["input", billingEntry.inputTokens], + ["output", billingEntry.outputTokens], + ["cacheWrite", billingEntry.cacheCreationInputTokens], + ["cacheRead", billingEntry.cacheReadInputTokens], + ].filter(([, value]) => typeof value === "number" && Number.isFinite(value)) + : []; + const winnerTokenRows = attempt.winnerCommitted + ? [ + ["input", winnerUsage?.inputTokens], + ["output", winnerUsage?.outputTokens], + ["cacheWrite", winnerUsage?.cacheCreationInputTokens], + ["cacheRead", winnerUsage?.cacheReadInputTokens], + ].filter(([, value]) => typeof value === "number" && Number.isFinite(value)) + : []; + const renderBillingDetails = () => { + const isWinner = attempt.winnerCommitted || attempt.outcome === "winner"; + const effectiveStatus = + isWinner && attempt.winnerCostUsd != null ? "billed" : attempt.billingStatus; + if (effectiveStatus === "none") return null; + const statusKey = effectiveStatus === "billed" ? "billed" : "notObtained"; + const statusClass = + effectiveStatus === "billed" + ? "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/20 dark:text-emerald-300" + : "border-slate-200 bg-slate-50 text-slate-600 dark:border-slate-700 dark:bg-slate-900/40 dark:text-slate-300"; + const rows = isWinner ? winnerTokenRows : tokenRows; + + return ( +
+
+ {t("attemptDetails.billing")} + + {t(`attemptDetails.${statusKey}`)} + +
+ {effectiveStatus === "billed" && isDisplayableCost(billedCost) && ( +
+ + {isWinner ? t("attemptDetails.winnerCost") : t("attemptDetails.billedCost")} + + {formatCurrency(billedCost, "USD", 6)} +
+ )} + {effectiveStatus === "billed" && rows.length > 0 && ( +
+ {rows.map(([kind, value]) => ( + + {tDetails(`billingDetails.${kind}`)} {formatTokenAmount(value as number)} + + ))} +
+ )} +
+ ); + }; + + return ( +
+ + {expanded && ( +
+
+ {attempt.providerId != null && ( + + )} + {attempt.sequence != null && ( + + )} + {statusCode != null && ( + + )} +
+ {endpoint && ( +
+
{t("attemptDetails.endpoint")}
+ {endpoint} +
+ )} + {errorMessage && ( +
+
+ {t("attemptDetails.error")} +
+
+                {errorMessage}
+              
+
+ )} + {cancellationLabel && ( +
+ {t("attemptDetails.cancellation")}:{" "} + {cancellationLabel} +
+ )} + {renderBillingDetails()} + {attempt.history.length > 0 && ( +
+
{t("attemptDetails.timeline")}
+ {attempt.history.map((event, index) => ( +
+ + + {formatAttemptEvent(t, event)} + + {event.elapsedMs != null && ( + + {t("elapsed", { elapsed: Math.max(0, Math.round(event.elapsedMs)) })} + + )} +
+ ))} +
+ )} +
+ )} +
+ ); +} + +function formatAttemptEvent( + t: ReturnType>, + event: AttemptView["history"][number] +): string { + switch (event.type) { + case "attempt_started": + return t("events.started"); + case "attempt_ready": + return t("events.ready"); + case "attempt_held": + return t("events.held"); + case "fallback_promoted": + return t("events.fallbackPromoted"); + case "winner_committed": + return t("events.winnerCommitted"); + case "attempt_finished": + return t("events.finished", { + outcome: t(`outcomes.${event.outcome ?? "pending"}`), + }); + default: + return event.type; + } +} diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx index 90c1ece95..c03f50fe5 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx @@ -15,6 +15,7 @@ import { Link2, RefreshCw, Server, + ShieldCheck, XCircle, Zap, } from "lucide-react"; @@ -28,7 +29,9 @@ import { formatCurrency } from "@/lib/utils/currency"; import { findHedgeLoserCost, summarizeHedgeBilling } from "@/lib/utils/hedge-billing"; import { formatProbability, formatProviderTimeline } from "@/lib/utils/provider-chain-formatter"; import type { ProviderChainItem } from "@/types/message"; +import { normalizeRoutingTrace } from "@/types/routing-trace"; import { type LogicTraceTabProps, parseBlockedReason } from "../types"; +import { DiscoveryTraceView, RoutingModeBanner } from "./DiscoveryTraceView"; import { StepCard, type StepStatus } from "./StepCard"; function getRequestStatus(item: ProviderChainItem): StepStatus { @@ -63,6 +66,7 @@ function getRequestStatus(item: ProviderChainItem): StepStatus { export function LogicTraceTab({ statusCode: _statusCode, providerChain, + routingTrace, sessionId, blockedBy, blockedReason, @@ -184,10 +188,34 @@ export function LogicTraceTab({ // Count providers at each stage const totalProviders = decisionContext?.totalProviders || 0; const afterHealthCheck = decisionContext?.afterHealthCheck || 0; + const normalizedRoutingTrace = normalizeRoutingTrace(routingTrace); + const isLeaseConflictProtection = + normalizedRoutingTrace?.mode === "single_upstream" && + normalizedRoutingTrace.bypassReason === "lease_conflict"; // Calculate step offset for session reuse flow const sessionReuseStepOffset = isSessionReuseFlow ? 1 : 0; + if (normalizedRoutingTrace?.mode === "discovery") { + return ( +
+ + +
+ ); + } + return (
{/* Warmup Skip Info */} @@ -243,17 +271,23 @@ export function LogicTraceTab({
)} + {normalizedRoutingTrace && } + {/* Decision Chain Header */} {providerChain && providerChain.length > 0 && (

- {isSessionReuseFlow ? ( + {isLeaseConflictProtection ? ( + + ) : isSessionReuseFlow ? ( ) : ( )} - {t("logicTrace.title")} + {isLeaseConflictProtection + ? t("logicTrace.singleRouteSelectionTitle") + : t("logicTrace.title")}

{isSessionReuseFlow ? ( diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx index 313df803d..9fce9036a 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx @@ -67,6 +67,7 @@ export function SummaryTab({ costBreakdown, hedgeLosers, providerChain, + routingTrace, context1mApplied, durationMs, ttfbMs, @@ -654,10 +655,23 @@ export function SummaryTab({ {(() => { const hedgeWinnerStep = providerChain?.find((item) => item.reason === "hedge_winner") ?? null; + const discoveryWinnerEvent = + routingTrace?.mode === "discovery" + ? routingTrace.events.findLast( + (event) => + event.type === "winner_committed" && + (routingTrace.summary?.winnerProviderId == null || + event.provider?.id === routingTrace.summary.winnerProviderId) + ) + : null; + const discoveryWinner = discoveryWinnerEvent?.provider; + const discoveryWinnerAttempt = discoveryWinnerEvent?.attemptId?.match(/:(\d+)$/)?.[1]; const hedgeTable = buildHedgeBillingTable(costUsd, hedgeLosers, { - providerId: hedgeWinnerStep?.id ?? null, - providerName: hedgeWinnerStep?.name ?? null, - attemptNumber: hedgeWinnerStep?.attemptNumber ?? null, + providerId: hedgeWinnerStep?.id ?? discoveryWinner?.id ?? null, + providerName: hedgeWinnerStep?.name ?? discoveryWinner?.name ?? null, + attemptNumber: + hedgeWinnerStep?.attemptNumber ?? + (discoveryWinnerAttempt ? Number(discoveryWinnerAttempt) : null), inputTokens, outputTokens, cacheCreationInputTokens, @@ -676,7 +690,10 @@ export function SummaryTab({ {t("billingDetails.hedgeMergedCount", { count: hedgeTable.count })}
-
+
diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts index 641795a9a..67223ae82 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts @@ -1,3 +1,4 @@ +export { DiscoveryTraceView, RoutingModeBanner } from "./DiscoveryTraceView"; export { LatencyBreakdownBar } from "./LatencyBreakdownBar"; export { LogicTraceTab } from "./LogicTraceTab"; export { PerformanceTab } from "./PerformanceTab"; diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx index 74a66d790..901dd1a75 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx @@ -11,6 +11,7 @@ import { hasSessionMessages } from "@/lib/api-client/v1/actions/active-sessions" import { cn } from "@/lib/utils"; import type { HedgeLoserBilling, StoredCostBreakdown } from "@/types/cost-breakdown"; import type { ProviderChainItem } from "@/types/message"; +import type { RoutingTraceV1 } from "@/types/routing-trace"; import type { SpecialSetting } from "@/types/special-settings"; import type { BillingModelSource } from "@/types/system-config"; import { LogicTraceTab, PerformanceTab, SummaryTab } from "./components"; @@ -19,6 +20,7 @@ interface ErrorDetailsDialogProps { statusCode: number | null; errorMessage: string | null; providerChain: ProviderChainItem[] | null; + routingTrace?: RoutingTraceV1 | null; sessionId: string | null; requestSequence?: number | null; blockedBy?: string | null; @@ -63,6 +65,7 @@ export function ErrorDetailsDialog({ statusCode, errorMessage, providerChain, + routingTrace, sessionId, requestSequence, blockedBy, @@ -212,6 +215,7 @@ export function ErrorDetailsDialog({ statusCode, errorMessage, providerChain, + routingTrace, sessionId, requestSequence, blockedBy, diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts index ad65c070a..dc1ea1247 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts @@ -1,5 +1,6 @@ import type { HedgeLoserBilling, StoredCostBreakdown } from "@/types/cost-breakdown"; import type { ProviderChainItem } from "@/types/message"; +import type { RoutingTraceV1 } from "@/types/routing-trace"; import type { SpecialSetting } from "@/types/special-settings"; import type { BillingModelSource } from "@/types/system-config"; @@ -13,6 +14,8 @@ export interface TabSharedProps { errorMessage: string | null; /** Provider decision chain */ providerChain: ProviderChainItem[] | null; + /** Versioned routing trace for Discovery and legacy routing decisions */ + routingTrace?: RoutingTraceV1 | null; /** Session ID */ sessionId: string | null; /** Request sequence number within session */ diff --git a/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx b/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx index 4d65e49e2..8644b2ef9 100644 --- a/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx +++ b/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx @@ -3,7 +3,9 @@ import { renderToStaticMarkup } from "react-dom/server"; import { NextIntlClientProvider } from "next-intl"; import { Window } from "happy-dom"; import { describe, expect, test, vi } from "vitest"; +import dashboardMessages from "../../../../../../messages/en/dashboard.json"; import providerChainMessages from "../../../../../../messages/en/provider-chain.json"; +import type { RoutingTraceV1 } from "@/types/routing-trace"; vi.mock("@/lib/utils/provider-chain-formatter", async (importOriginal) => { const actual = await importOriginal(); @@ -66,8 +68,13 @@ vi.mock("@/components/ui/button", () => ({ })); vi.mock("@/components/ui/badge", () => ({ - Badge: ({ children, className }: React.ComponentProps<"span"> & { variant?: string }) => ( - + Badge: ({ + children, + className, + variant: _variant, + ...props + }: React.ComponentProps<"span"> & { variant?: string }) => ( + {children} ), @@ -85,6 +92,7 @@ const messages = { decisionChain: "Decision chain", }, details: { + routingTrace: dashboardMessages.logs.details.routingTrace, clickStatusCode: "Click status code", fake200ForwardedNotice: "Note: payload may have been forwarded", fake200DetectedReason: "Detected reason: {reason}", @@ -131,6 +139,10 @@ function parseHtml(html: string) { return window.document; } +function getCompactDiscoveryRouteBadge(html: string) { + return parseHtml(html).querySelector("[data-testid='discovery-route-badge']"); +} + describe("provider-chain-popover probability formatting", () => { test("renders probability 0.5 as 50% in tooltip", () => { const html = renderWithIntl( @@ -480,3 +492,679 @@ describe("provider-chain-popover hedge/abort reason handling", () => { expect(html).toContain("p1"); }); }); + +describe("provider-chain-popover Discovery summary", () => { + test("shows cold start, rounds, attempts and fallback winner source", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 2_000, + ttfbMs: 1_500, + attemptsPerRequest: 4, + maxActiveAttempts: 2, + rounds: 2, + providerMs: 3_400, + fallbackPromotions: 1, + cancelFailures: 0, + winnerOrigin: "fallback", + winnerProviderId: 2, + winnerRound: 1, + }, + }; + + const html = renderWithIntl( + undefined} + /> + ); + + expect(html).toContain("Cold start"); + expect(html).toContain("2R · 4 tries"); + expect(html).toContain("Final provider"); + expect(html).toContain("winner"); + expect(html).toContain("Route mode"); + expect(html).toContain("Winner origin"); + expect(html).toContain("Fallback takeover"); + expect(html).toContain("View Discovery details"); + expect(html).not.toContain("1 times"); + + const document = parseHtml(html); + const coldStartBadges = Array.from(document.querySelectorAll("[data-slot='badge']")).filter( + (node) => node.textContent === "Cold start" + ); + expect(coldStartBadges.some((node) => node.classList.contains("max-w-[45%]"))).toBe(true); + expect(coldStartBadges.some((node) => node.classList.contains("whitespace-normal"))).toBe(true); + const fallbackBadge = Array.from(document.querySelectorAll("[data-slot='badge']")).find( + (node) => node.textContent === "Fallback takeover" + ); + expect(fallbackBadge?.classList.contains("whitespace-normal")).toBe(true); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.classList.contains("bg-amber-50")).toBe(true); + expect(compactRouteBadge?.classList.contains("text-amber-800")).toBe(true); + expect(compactRouteBadge?.getAttribute("data-route-mode")).toBe("cold_start"); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("fallback"); + expect(compactRouteBadge?.getAttribute("title")).toBe("Cold start · Fallback takeover"); + expect(document.querySelector("button")?.getAttribute("aria-label")).toContain( + "Cold start · Fallback takeover" + ); + }); + + test("labels a healthy Sticky request and shows the full winner name from the trace", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "sticky_probe_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptKind: "sticky", + provider: { id: 9, name: "full-sticky-provider-name" }, + }, + { + type: "winner_committed", + at: 2_000, + elapsedMs: 1_000, + round: 0, + attemptId: "sticky:1", + attemptKind: "sticky", + provider: { id: 9, name: "full-sticky-provider-name" }, + statusCode: 200, + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 2_000, + ttfbMs: 1_000, + attemptsPerRequest: 1, + maxActiveAttempts: 1, + rounds: 0, + providerMs: 1_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "sticky", + winnerProviderId: 9, + winnerRound: 0, + }, + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("full-sticky-provider-name"); + expect(html).toContain("Route mode"); + expect(html).toContain("Sticky"); + expect(html).toContain("0R · 1 tries"); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.classList.contains("bg-violet-50")).toBe(true); + expect(compactRouteBadge?.classList.contains("text-violet-700")).toBe(true); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("sticky"); + }); + + test("labels Discovery after a Sticky timeout as rediscovery", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 4_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "sticky_probe_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptKind: "sticky", + provider: { id: 1, name: "old-sticky" }, + }, + { + type: "sticky_timeout", + at: 2_000, + elapsedMs: 1_000, + round: 0, + attemptKind: "sticky", + provider: { id: 1, name: "old-sticky" }, + outcome: "timeout", + }, + { type: "round_started", at: 2_001, elapsedMs: 1_001, round: 1 }, + { + type: "winner_committed", + at: 3_000, + elapsedMs: 2_000, + round: 1, + attemptId: "fallback:2", + attemptKind: "fallback", + provider: { id: 2, name: "new-winner" }, + statusCode: 200, + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 3_000, + ttfbMs: 2_000, + attemptsPerRequest: 2, + maxActiveAttempts: 2, + rounds: 1, + providerMs: 3_000, + fallbackPromotions: 1, + cancelFailures: 0, + winnerOrigin: "fallback", + winnerProviderId: 2, + winnerRound: 1, + }, + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("Rediscovery"); + expect(html).toContain("Fallback takeover"); + expect(html).not.toContain("Cold start"); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.classList.contains("bg-slate-100")).toBe(true); + expect(compactRouteBadge?.classList.contains("text-slate-700")).toBe(true); + expect(compactRouteBadge?.getAttribute("data-route-mode")).toBe("rediscovery"); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("fallback"); + }); + + test("labels a Sticky attempt that fails before discovery as Sticky", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "sticky_probe_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptKind: "sticky", + provider: { id: 1, name: "sticky-provider" }, + }, + { + type: "attempt_finished", + at: 2_000, + elapsedMs: 1_000, + round: 0, + attemptKind: "sticky", + outcome: "failed", + provider: { id: 1, name: "sticky-provider" }, + }, + { + type: "request_finished", + at: 3_000, + elapsedMs: 2_000, + outcome: "failed", + statusCode: 503, + }, + ], + summary: { + outcome: "failed", + statusCode: 503, + durationMs: 2_000, + ttfbMs: null, + attemptsPerRequest: 1, + maxActiveAttempts: 1, + rounds: 0, + providerMs: 1_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "none", + winnerProviderId: null, + winnerRound: 0, + }, + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("Sticky"); + expect(html).not.toContain("Rediscovery"); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("none"); + expect(compactRouteBadge?.classList.contains("bg-violet-50")).toBe(false); + expect(compactRouteBadge?.classList.contains("bg-blue-50")).toBe(false); + expect(compactRouteBadge?.classList.contains("bg-amber-50")).toBe(false); + expect(compactRouteBadge?.classList.contains("bg-teal-50")).toBe(false); + expect(compactRouteBadge?.classList.contains("bg-slate-100")).toBe(false); + }); + + test("does not infer Rediscovery from a stale session_reuse chain", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [ + { type: "round_started", at: 1_000, elapsedMs: 0, round: 1 }, + { + type: "attempt_started", + at: 1_010, + elapsedMs: 10, + round: 1, + attemptKind: "normal", + provider: { id: 2, name: "cold-start-provider" }, + }, + ], + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("Cold start"); + expect(html).not.toContain("Rediscovery"); + }); + + test("uses session reuse only as a fallback for a truncated trace", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + truncated: true, + events: [{ type: "round_started", at: 2_000, elapsedMs: 1_000, round: 1 }], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 2_000, + ttfbMs: 1_000, + attemptsPerRequest: 2, + maxActiveAttempts: 2, + rounds: 1, + providerMs: 2_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "normal", + winnerProviderId: 2, + winnerRound: 1, + }, + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("Rediscovery"); + }); + + test("recognizes Rediscovery from a retained non-Sticky winner event", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + truncated: true, + events: [ + { + type: "sticky_probe_started", + at: 1_000, + elapsedMs: 0, + round: 0, + attemptKind: "sticky", + provider: { id: 1, name: "old-sticky" }, + }, + { + type: "winner_committed", + at: 3_000, + elapsedMs: 2_000, + round: 1, + attemptKind: "normal", + provider: { id: 2, name: "new-winner" }, + statusCode: 200, + }, + ], + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("Rediscovery"); + expect(html).toContain("Normal candidate"); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.classList.contains("bg-teal-50")).toBe(true); + expect(compactRouteBadge?.classList.contains("text-teal-700")).toBe(true); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("normal"); + }); + + test("uses live winner events when the final summary has not been persisted yet", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 2_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "winner_committed", + at: 1_500, + elapsedMs: 500, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 7, name: "live-winner-name" }, + statusCode: 200, + }, + ], + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("live-winner-name"); + expect(html).toContain("Normal candidate"); + expect(html).toContain("Cold start"); + const compactRouteBadge = getCompactDiscoveryRouteBadge(html); + expect(compactRouteBadge?.classList.contains("bg-blue-50")).toBe(true); + expect(compactRouteBadge?.classList.contains("text-blue-700")).toBe(true); + expect(compactRouteBadge?.getAttribute("data-winner-origin")).toBe("normal"); + }); + + test("derives live rounds and attempt count before a terminal summary exists", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: true, + events: [ + { type: "round_started", at: 1_000, elapsedMs: 0, round: 1 }, + { + type: "attempt_started", + at: 1_010, + elapsedMs: 10, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 1, name: "candidate-a" }, + }, + { + type: "attempt_started", + at: 1_020, + elapsedMs: 20, + round: 1, + attemptId: "normal:2", + attemptKind: "normal", + provider: { id: 2, name: "candidate-b" }, + }, + { + type: "attempt_finished", + at: 2_000, + elapsedMs: 1_000, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + outcome: "cancelled", + }, + { type: "round_started", at: 2_010, elapsedMs: 1_010, round: 2 }, + { + type: "attempt_started", + at: 2_020, + elapsedMs: 1_020, + round: 2, + attemptId: "normal:3", + attemptKind: "normal", + provider: { id: 3, name: "candidate-c" }, + }, + ], + }; + + const html = renderWithIntl( + + ); + + expect(html).toContain("2R · 3 tries"); + expect(html).toContain("Cold start"); + expect(html).toContain("Request result"); + expect(html).toContain("Pending"); + expect(html).not.toContain("0R · 0 tries"); + }); + + test("uses the terminal failure over a committed winner and preserves fake-200 warnings", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "discovery", + startedAt: 1_000, + updatedAt: 5_000, + discoveryEnabled: true, + eligible: true, + events: [ + { + type: "winner_committed", + at: 2_000, + elapsedMs: 1_000, + round: 1, + attemptId: "normal:1", + attemptKind: "normal", + provider: { id: 1, name: "candidate-a" }, + statusCode: 200, + }, + { + type: "request_finished", + at: 5_000, + elapsedMs: 4_000, + outcome: "failed", + statusCode: 502, + reason: "FAKE_200_EMPTY_BODY", + }, + ], + summary: { + outcome: "success", + statusCode: 200, + durationMs: 1_000, + ttfbMs: 1_000, + attemptsPerRequest: 1, + maxActiveAttempts: 1, + rounds: 1, + providerMs: 1_000, + fallbackPromotions: 0, + cancelFailures: 0, + winnerOrigin: "normal", + winnerProviderId: 1, + winnerRound: 1, + }, + }; + + const html = renderWithIntl( + + ); + const document = parseHtml(html); + const terminal = document.querySelector("[data-testid='discovery-compact-terminal']"); + + expect(terminal?.textContent).toContain("Failed"); + expect(terminal?.textContent).toContain("HTTP 502"); + expect(terminal?.innerHTML).toContain("text-rose-600"); + expect(terminal?.innerHTML).not.toContain("text-emerald-600"); + expect(html).toContain("Detected reason: Empty response body"); + expect(html).toContain("Note: payload may have been forwarded"); + expect(html).toContain("Why CCH cannot retry this response on the server"); + }); + + test("marks lease-conflict single-upstream routing without hiding selector priority", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "single_upstream", + startedAt: 1_000, + updatedAt: 2_000, + discoveryEnabled: true, + eligible: false, + bypassReason: "lease_conflict", + events: [], + }; + const html = renderWithIntl( + + ); + + expect(html).toContain("Single-route protection"); + expect(html).toContain("Another request owns the Discovery lease"); + expect(html).toContain("P1"); + expect(html).toContain("lucide-shield-check"); + const document = parseHtml(html); + const protectionBadge = Array.from(document.querySelectorAll("[data-slot='badge']")).find( + (node) => node.textContent === "Single-route protection" + ); + expect(protectionBadge?.classList.contains("bg-amber-50")).toBe(true); + expect(protectionBadge?.classList.contains("text-amber-700")).toBe(true); + }); + + test("keeps lease-conflict protection visible after serial provider fallback", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "single_upstream", + startedAt: 1_000, + updatedAt: 3_000, + discoveryEnabled: true, + eligible: false, + bypassReason: "lease_conflict", + events: [], + }; + const html = renderWithIntl( + + ); + + expect(html).toContain("Single-route protection"); + expect(html).toContain("lucide-shield-check"); + expect(html).toContain("primary"); + expect(html).toContain("backup"); + }); + + test("does not label a binding conflict as single-route protection", () => { + const routingTrace: RoutingTraceV1 = { + version: 1, + mode: "single_upstream", + startedAt: 1_000, + updatedAt: 2_000, + discoveryEnabled: true, + eligible: false, + bypassReason: "binding_conflict", + events: [], + }; + const html = renderWithIntl( + + ); + + expect(html).not.toContain("Single-route protection"); + expect(html).not.toContain("lucide-shield-check"); + }); +}); diff --git a/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx b/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx index 958257769..3b5e5b6f2 100644 --- a/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx +++ b/src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx @@ -4,11 +4,14 @@ import { AlertTriangle, CheckCircle, ChevronRight, + Clock3, GitBranch, InfoIcon, Link2, MinusCircle, RefreshCw, + Server, + ShieldCheck, XCircle, Zap, } from "lucide-react"; @@ -26,11 +29,17 @@ import { } from "@/lib/utils/provider-chain-formatter"; import { parseProviderGroups } from "@/lib/utils/provider-group"; import type { ProviderChainItem } from "@/types/message"; +import { + normalizeRoutingTrace, + type RoutingTraceV1, + type RoutingTraceWinnerOrigin, +} from "@/types/routing-trace"; import { getFake200ReasonKey } from "./fake200-reason"; import { Fake200RetryTooltip } from "./fake200-retry-tooltip"; interface ProviderChainPopoverProps { chain: ProviderChainItem[]; + routingTrace?: RoutingTraceV1 | null; finalProvider: string; /** Whether a cost badge is displayed, affects name max width */ hasCostBadge?: boolean; @@ -42,6 +51,166 @@ function parseGroupTags(groupTag?: string | null): string[] { return Array.from(new Set(parseProviderGroups(groupTag))); } +function deriveDiscoveryStats(trace: RoutingTraceV1): { + rounds: number; + attempts: number; +} { + const startedAttempts = new Set(); + let anonymousAttempts = 0; + let maxRound = 0; + + for (const event of trace.events) { + if (typeof event.round === "number" && Number.isFinite(event.round)) { + maxRound = Math.max(maxRound, event.round); + } + if (event.type !== "attempt_started") continue; + if (event.attemptId) startedAttempts.add(event.attemptId); + else anonymousAttempts += 1; + } + + return { + rounds: trace.summary?.rounds ?? maxRound, + attempts: trace.summary?.attemptsPerRequest ?? startedAttempts.size + anonymousAttempts, + }; +} + +type DiscoveryTerminalOutcome = "success" | "failed" | "client_abort" | "deadline" | "pending"; + +function getDiscoveryTerminal(trace: RoutingTraceV1): { + outcome: DiscoveryTerminalOutcome; + statusCode: number | null; +} { + const terminalEvent = trace.events.findLast((event) => event.type === "request_finished"); + const rawOutcome = terminalEvent?.outcome ?? trace.summary?.outcome; + const outcome: DiscoveryTerminalOutcome = + rawOutcome === "success" || + rawOutcome === "failed" || + rawOutcome === "client_abort" || + rawOutcome === "deadline" + ? rawOutcome + : "pending"; + + return { + outcome, + statusCode: terminalEvent?.statusCode ?? trace.summary?.statusCode ?? null, + }; +} + +type DiscoveryRouteMode = "sticky" | "cold_start" | "rediscovery"; + +function getStickyEvidenceIndex(trace: RoutingTraceV1): number { + return trace.events.findIndex( + (event) => + event.type === "sticky_probe_started" || + event.type === "sticky_timeout" || + event.attemptKind === "sticky" + ); +} + +function deriveDiscoveryRouteMode( + trace: RoutingTraceV1, + chain: ProviderChainItem[] +): DiscoveryRouteMode { + const stickyEvidenceIndex = getStickyEvidenceIndex(trace); + if (stickyEvidenceIndex < 0) { + if (trace.summary?.winnerOrigin === "sticky") return "sticky"; + + const truncatedStickyFallback = + trace.truncated === true && + chain.some( + (item) => item.reason === "session_reuse" || item.selectionMethod === "session_reuse" + ) && + ((trace.summary?.rounds ?? 0) >= 1 || (trace.summary?.winnerRound ?? 0) >= 1); + return truncatedStickyFallback ? "rediscovery" : "cold_start"; + } + + const transitionEvents = trace.events.slice(stickyEvidenceIndex); + const enteredRediscovery = + transitionEvents.some( + (event) => + event.type === "sticky_timeout" || + (typeof event.round === "number" && + event.round >= 1 && + (event.type === "round_started" || + (event.attemptKind !== undefined && event.attemptKind !== "sticky"))) + ) || + (trace.summary?.rounds ?? 0) >= 1 || + (trace.summary?.winnerRound ?? 0) >= 1; + return enteredRediscovery ? "rediscovery" : "sticky"; +} + +function getDiscoveryWinnerOrigin(trace: RoutingTraceV1): RoutingTraceWinnerOrigin { + const summaryOrigin = trace.summary?.winnerOrigin; + if (summaryOrigin === "sticky" || summaryOrigin === "normal" || summaryOrigin === "fallback") { + return summaryOrigin; + } + + const winnerKind = trace.events.findLast( + (event) => event.type === "winner_committed" + )?.attemptKind; + return winnerKind === "sticky" || winnerKind === "normal" || winnerKind === "fallback" + ? winnerKind + : "none"; +} + +function getCompactDiscoveryRouteBadgeClass( + routeMode: DiscoveryRouteMode, + winnerOrigin: RoutingTraceWinnerOrigin +): string { + if (winnerOrigin === "none") { + return ""; + } + + if (routeMode === "sticky" && winnerOrigin === "sticky") { + return "border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-800 dark:bg-violet-950/20 dark:text-violet-300"; + } + + if (routeMode === "cold_start" && winnerOrigin === "normal") { + return "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/20 dark:text-blue-300"; + } + + if (routeMode === "cold_start" && winnerOrigin === "fallback") { + return "border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/20 dark:text-amber-300"; + } + + if (routeMode === "rediscovery" && winnerOrigin === "normal") { + return "border-teal-200 bg-teal-50 text-teal-700 dark:border-teal-800 dark:bg-teal-950/20 dark:text-teal-300"; + } + + if (routeMode === "rediscovery" && winnerOrigin === "fallback") { + return "border-slate-300 bg-slate-100 text-slate-700 dark:border-slate-600 dark:bg-slate-800/50 dark:text-slate-200"; + } + + return ""; +} + +function getDiscoveryFinalProviderName( + trace: RoutingTraceV1, + chain: ProviderChainItem[], + fallbackName: string, + winnerOrigin: RoutingTraceWinnerOrigin +): string | null { + const winnerProviderId = trace.summary?.winnerProviderId; + const winnerEvent = [...trace.events] + .reverse() + .find( + (event) => + (event.type === "winner_committed" || + (event.type === "attempt_finished" && event.outcome === "winner")) && + (winnerProviderId == null || event.provider?.id === winnerProviderId) && + event.provider?.name + ); + if (winnerEvent?.provider?.name) return winnerEvent.provider.name; + const hasSuccessfulChainItem = chain.some( + (item) => + (item.reason === "request_success" || + item.reason === "retry_success" || + item.reason === "hedge_winner") && + item.statusCode != null + ); + return winnerOrigin !== "none" || hasSuccessfulChainItem ? fallbackName || null : null; +} + /** * Get status icon and color for a provider chain item */ @@ -126,12 +295,15 @@ function getItemStatus(item: ProviderChainItem): { export function ProviderChainPopover({ chain, + routingTrace, finalProvider, hasCostBadge = false, onChainItemClick, }: ProviderChainPopoverProps) { const t = useTranslations("dashboard"); const tChain = useTranslations("provider-chain"); + const tRouting = useTranslations("dashboard.logs.details.routingTrace"); + const normalizedRoutingTrace = normalizeRoutingTrace(routingTrace); // “假 200”识别发生在 SSE 流式结束后:此时响应内容可能已透传给客户端,但内部会按失败统计/熔断。 const hasFake200PostStreamFailure = chain.some( @@ -151,8 +323,190 @@ export function ProviderChainPopover({ // Fallback for empty string const displayName = finalProvider || "-"; + if (normalizedRoutingTrace?.mode === "discovery") { + const { rounds, attempts } = deriveDiscoveryStats(normalizedRoutingTrace); + const routeMode = deriveDiscoveryRouteMode(normalizedRoutingTrace, chain); + const winnerOrigin = getDiscoveryWinnerOrigin(normalizedRoutingTrace); + const discoveryFinalProvider = getDiscoveryFinalProviderName( + normalizedRoutingTrace, + chain, + displayName, + winnerOrigin + ); + const triggerProviderName = discoveryFinalProvider ?? displayName; + const terminal = getDiscoveryTerminal(normalizedRoutingTrace); + const terminalPresentation = + terminal.outcome === "success" + ? { icon: CheckCircle, className: "text-emerald-600" } + : terminal.outcome === "failed" + ? { icon: XCircle, className: "text-rose-600" } + : terminal.outcome === "deadline" + ? { icon: Clock3, className: "text-amber-600" } + : terminal.outcome === "client_abort" + ? { icon: MinusCircle, className: "text-amber-600" } + : { icon: RefreshCw, className: "text-muted-foreground" }; + const TerminalIcon = terminalPresentation.icon; + const routeLabel = tRouting(`routeModes.${routeMode}`); + const winnerSourceLabel = + winnerOrigin === "none" ? null : tRouting(`winnerSources.${winnerOrigin}`); + const compactRouteDescription = + winnerSourceLabel && winnerSourceLabel !== routeLabel + ? `${routeLabel} · ${winnerSourceLabel}` + : routeLabel; + return ( + + + + + +
+

+ + {tRouting("discoveryTitle")} +

+ + {tRouting("modes.discovery")} + +
+
+
+ + {tRouting("finalProvider")}: + + {discoveryFinalProvider ?? "-"} + +
+
+ +
+ {tRouting("routeMode")}: + + {routeLabel} + +
+
+
+
+
{tRouting("roundsLabel")}
+
{rounds}
+
+
+
{tRouting("attemptsLabel")}
+
{attempts}
+
+
+
+ +
+ {tRouting("terminalOutcome")}: + + {tRouting(`outcomes.${terminal.outcome}`)} + + {terminal.statusCode != null && ( + HTTP {terminal.statusCode} + )} +
+
+ {winnerOrigin !== "none" && ( +
+ +
+ {tRouting("winnerOrigin")}: + + {winnerSourceLabel} + +
+
+ )} +
+ {(hasFake200PostStreamFailure || onChainItemClick) && ( +
+ {hasFake200PostStreamFailure && ( +
+
+ )} + {onChainItemClick && ( + + )} +
+ )} +
+
+ ); + } + // Determine max width based on whether cost badge is present const maxWidthClass = hasCostBadge ? "max-w-[140px]" : "max-w-[180px]"; + const isLeaseConflictProtection = + normalizedRoutingTrace?.mode === "single_upstream" && + normalizedRoutingTrace.bypassReason === "lease_conflict"; // Check if this is a session reuse const isSessionReuse = @@ -176,22 +530,41 @@ export function ProviderChainPopover({ - + + {isLeaseConflictProtection && ( +
{/* Provider name */}
{displayName}
+ {isLeaseConflictProtection && ( +
+
+ )} {singleRequestItem?.statusCode && (
{/* Request count badge */} - {isHedge ? ( + {isLeaseConflictProtection ? ( +