diff --git a/drizzle/0116_gigantic_zombie.sql b/drizzle/0116_gigantic_zombie.sql new file mode 100644 index 000000000..4fdc1189c --- /dev/null +++ b/drizzle/0116_gigantic_zombie.sql @@ -0,0 +1,212 @@ +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "session_identity" varchar(64);--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "session_identity_kind" varchar(20);--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "affinity_scope_tag" varchar(16);--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "affinity_fingerprint" varchar(64);--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "affinity_fingerprint_chain" jsonb;--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "is_replay" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "replay_source_request_id" integer;--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "session_identity" varchar(64);--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "session_identity_kind" varchar(20);--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "affinity_scope_tag" varchar(16);--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "affinity_fingerprint" varchar(64);--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "affinity_fingerprint_chain" jsonb;--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "is_replay" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "usage_ledger" ADD COLUMN IF NOT EXISTS "replay_source_request_id" integer;--> statement-breakpoint + +-- Before 0116, Replay audit rows were identified by blocked_by='replay_serve'. +-- Convert that legacy marker to the formal audit fields without inventing unavailable provenance. +UPDATE message_request +SET is_replay = true, + blocked_by = NULL, + cost_usd = 0, + cost_breakdown = NULL +WHERE blocked_by = 'replay_serve';--> statement-breakpoint +UPDATE usage_ledger +SET is_replay = true, + blocked_by = NULL, + cost_usd = 0 +WHERE blocked_by = 'replay_serve';--> statement-breakpoint + +-- Existing ledger rows must receive the same identity and Replay provenance as their source request. +-- Replay cost is normalized at this projection boundary as an additional accounting safeguard. +UPDATE usage_ledger AS ul +SET session_identity = mr.session_identity, + session_identity_kind = mr.session_identity_kind, + affinity_scope_tag = mr.affinity_scope_tag, + affinity_fingerprint = mr.affinity_fingerprint, + affinity_fingerprint_chain = mr.affinity_fingerprint_chain, + is_replay = mr.is_replay, + replay_source_request_id = mr.replay_source_request_id, + cost_usd = CASE WHEN mr.is_replay THEN 0 ELSE ul.cost_usd END +FROM message_request AS mr +WHERE ul.request_id = mr.id + AND ( + ul.session_identity IS DISTINCT FROM mr.session_identity + OR ul.session_identity_kind IS DISTINCT FROM mr.session_identity_kind + OR ul.affinity_scope_tag IS DISTINCT FROM mr.affinity_scope_tag + OR ul.affinity_fingerprint IS DISTINCT FROM mr.affinity_fingerprint + OR ul.affinity_fingerprint_chain IS DISTINCT FROM mr.affinity_fingerprint_chain + OR ul.is_replay IS DISTINCT FROM mr.is_replay + OR ul.replay_source_request_id IS DISTINCT FROM mr.replay_source_request_id + OR (mr.is_replay AND ul.cost_usd IS DISTINCT FROM 0) + );--> statement-breakpoint + +CREATE OR REPLACE FUNCTION fn_upsert_usage_ledger() +RETURNS TRIGGER AS $$ +DECLARE + v_final_provider_id integer; + v_is_success boolean; + v_success_rate_outcome varchar; +BEGIN + v_success_rate_outcome := fn_compute_message_request_success_rate_outcome( + NEW.blocked_by, + NEW.status_code, + NEW.error_message, + NEW.provider_chain + ); + + IF NEW.blocked_by = 'warmup' THEN + UPDATE usage_ledger + SET blocked_by = 'warmup', + success_rate_outcome = v_success_rate_outcome, + actual_response_model = NEW.actual_response_model + WHERE request_id = NEW.id; + RETURN NEW; + END IF; + + IF LOWER(REGEXP_REPLACE(COALESCE(NEW.endpoint, ''), '/+$', '')) + IN ('/v1/messages/count_tokens', '/v1/responses/compact') THEN + DELETE FROM usage_ledger WHERE request_id = NEW.id; + RETURN NEW; + END IF; + + IF NEW.provider_chain IS NOT NULL + AND jsonb_typeof(NEW.provider_chain) = 'array' + AND jsonb_array_length(NEW.provider_chain) > 0 + AND jsonb_typeof(NEW.provider_chain -> -1) = 'object' + AND (NEW.provider_chain -> -1 ? 'id') + AND (NEW.provider_chain -> -1 ->> 'id') ~ '^[0-9]+$' THEN + v_final_provider_id := (NEW.provider_chain -> -1 ->> 'id')::integer; + ELSE + v_final_provider_id := NEW.provider_id; + END IF; + + v_is_success := (NEW.error_message IS NULL OR NEW.error_message = '') + AND (NEW.status_code IS NULL OR NEW.status_code < 400); + + INSERT INTO usage_ledger ( + request_id, user_id, key, provider_id, final_provider_id, + model, original_model, actual_response_model, endpoint, api_type, session_id, + session_identity, session_identity_kind, affinity_scope_tag, + affinity_fingerprint, affinity_fingerprint_chain, is_replay, replay_source_request_id, + status_code, is_success, success_rate_outcome, blocked_by, + 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, first_byte_ms, client_ip, created_at + ) VALUES ( + NEW.id, NEW.user_id, NEW.key, NEW.provider_id, v_final_provider_id, + NEW.model, NEW.original_model, NEW.actual_response_model, NEW.endpoint, NEW.api_type, NEW.session_id, + NEW.session_identity, NEW.session_identity_kind, NEW.affinity_scope_tag, + NEW.affinity_fingerprint, NEW.affinity_fingerprint_chain, NEW.is_replay, NEW.replay_source_request_id, + NEW.status_code, v_is_success, v_success_rate_outcome, NEW.blocked_by, + CASE WHEN NEW.is_replay THEN 0 ELSE NEW.cost_usd END, + NEW.cost_multiplier, NEW.group_cost_multiplier, + NEW.input_tokens, NEW.output_tokens, + NEW.cache_creation_input_tokens, NEW.cache_read_input_tokens, + NEW.cache_creation_5m_input_tokens, NEW.cache_creation_1h_input_tokens, + NEW.cache_ttl_applied, NEW.context_1m_applied, NEW.swap_cache_ttl_applied, + NEW.duration_ms, NEW.ttfb_ms, NEW.first_byte_ms, NEW.client_ip, NEW.created_at + ) + ON CONFLICT (request_id) DO UPDATE SET + user_id = EXCLUDED.user_id, + key = EXCLUDED.key, + provider_id = EXCLUDED.provider_id, + final_provider_id = EXCLUDED.final_provider_id, + model = EXCLUDED.model, + original_model = EXCLUDED.original_model, + actual_response_model = EXCLUDED.actual_response_model, + endpoint = EXCLUDED.endpoint, + api_type = EXCLUDED.api_type, + session_id = EXCLUDED.session_id, + session_identity = EXCLUDED.session_identity, + session_identity_kind = EXCLUDED.session_identity_kind, + affinity_scope_tag = EXCLUDED.affinity_scope_tag, + affinity_fingerprint = EXCLUDED.affinity_fingerprint, + affinity_fingerprint_chain = EXCLUDED.affinity_fingerprint_chain, + is_replay = EXCLUDED.is_replay, + replay_source_request_id = EXCLUDED.replay_source_request_id, + status_code = EXCLUDED.status_code, + is_success = EXCLUDED.is_success, + success_rate_outcome = EXCLUDED.success_rate_outcome, + blocked_by = EXCLUDED.blocked_by, + cost_usd = EXCLUDED.cost_usd, + cost_multiplier = EXCLUDED.cost_multiplier, + group_cost_multiplier = EXCLUDED.group_cost_multiplier, + input_tokens = EXCLUDED.input_tokens, + output_tokens = EXCLUDED.output_tokens, + cache_creation_input_tokens = EXCLUDED.cache_creation_input_tokens, + cache_read_input_tokens = EXCLUDED.cache_read_input_tokens, + cache_creation_5m_input_tokens = EXCLUDED.cache_creation_5m_input_tokens, + cache_creation_1h_input_tokens = EXCLUDED.cache_creation_1h_input_tokens, + cache_ttl_applied = EXCLUDED.cache_ttl_applied, + context_1m_applied = EXCLUDED.context_1m_applied, + swap_cache_ttl_applied = EXCLUDED.swap_cache_ttl_applied, + duration_ms = EXCLUDED.duration_ms, + ttfb_ms = EXCLUDED.ttfb_ms, + first_byte_ms = EXCLUDED.first_byte_ms, + client_ip = EXCLUDED.client_ip; + + RETURN NEW; +EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'fn_upsert_usage_ledger failed for request_id=%: %', NEW.id, SQLERRM; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +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, + session_identity, + session_identity_kind, + affinity_scope_tag, + affinity_fingerprint, + affinity_fingerprint_chain, + is_replay, + replay_source_request_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, + first_byte_ms, + client_ip, + created_at +ON message_request +FOR EACH ROW +EXECUTE FUNCTION fn_upsert_usage_ledger(); diff --git a/drizzle/meta/0116_snapshot.json b/drizzle/meta/0116_snapshot.json new file mode 100644 index 000000000..a84b8ecea --- /dev/null +++ b/drizzle/meta/0116_snapshot.json @@ -0,0 +1,5305 @@ +{ + "id": "e98c1a23-aea9-459e-a634-fb82f3dacfbf", + "prevId": "6ef3f512-a210-4d69-801a-9cd1ee9e1e52", + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "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_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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "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_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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": "'CC 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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 45894cb76..eb8f7542a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -813,6 +813,13 @@ "when": 1785418573335, "tag": "0115_breezy_polaris", "breakpoints": true + }, + { + "idx": 116, + "version": "7", + "when": 1785563419224, + "tag": "0116_gigantic_zombie", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 5fbc5d622..3cb603209 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -88,6 +88,12 @@ "minRetryCount": "Retry Count ≥", "minRetryCountPlaceholder": "Enter minimum retries", "enabled": "On", + "replay": { + "label": "Replay", + "all": "All requests", + "only": "Replay only", + "exclude": "Exclude Replay" + }, "apply": "Apply Filter", "reset": "Reset", "last7days": "7d", @@ -164,6 +170,7 @@ "prevPage": "Previous Page", "nextPage": "Next Page", "blocked": "Blocked", + "replay": "Replay", "nonBilling": "Non-Billing", "skipped": "Skipped", "specialSettings": "Special", @@ -349,6 +356,7 @@ "title": "Performance", "ttfb": "TTFB", "tfft": "TFFT", + "tfftShort": "TFFT", "duration": "Total Duration", "outputRate": "Output Rate", "outputTokens": "Output Tokens" @@ -554,7 +562,8 @@ "replayServe": { "title": "Served from Replay Cache", "desc": "This request was served from the replay cache (identical request already in flight or completed). No upstream provider call was made and no cost was incurred.", - "replayId": "Replay ID" + "replayId": "Replay ID", + "sourceRequestId": "Source request" } }, "providerChain": { diff --git a/messages/en/errors.json b/messages/en/errors.json index d51d86dd9..47202ff9a 100644 --- a/messages/en/errors.json +++ b/messages/en/errors.json @@ -30,6 +30,8 @@ "PERMISSION_DENIED": "Permission denied", "TOKEN_REQUIRED": "Authentication token required", "INVALID_TOKEN": "Invalid authentication token", + "SESSION_REQUEST_SOURCE_MISMATCH": "The request source does not belong to this session", + "SESSION_REQUEST_SELECTOR_INCOMPLETE": "Prefix Session requests must specify both the physical source and request sequence", "PROXY_INVALID_API_KEY": "Invalid API key. The provided key does not exist or has been deleted.", "PROXY_API_KEY_DISABLED": "This API key has been disabled. Please contact your administrator to re-enable it, or use a different key.", "PROXY_API_KEY_EXPIRED": "This API key has expired. Please contact your administrator to renew it or rotate to a new key.", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index 2176634b5..ae7fd69a3 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -88,6 +88,12 @@ "minRetryCount": "リトライ回数≥", "minRetryCountPlaceholder": "回数を入力(0 で制限なし)", "enabled": "オン", + "replay": { + "label": "Replay", + "all": "すべてのリクエスト", + "only": "Replay のみ", + "exclude": "Replay を除外" + }, "apply": "フィルターを適用", "reset": "リセット", "last7days": "7日", @@ -164,6 +170,7 @@ "prevPage": "前へ", "nextPage": "次へ", "blocked": "ブロック済み", + "replay": "Replay", "nonBilling": "非課金", "skipped": "スキップ", "specialSettings": "特殊設定", @@ -349,6 +356,7 @@ "title": "パフォーマンス", "ttfb": "TTFB", "tfft": "TFFT", + "tfftShort": "TFFT", "duration": "総所要時間", "outputRate": "出力速度", "outputTokens": "出力トークン" @@ -554,7 +562,8 @@ "replayServe": { "title": "Replay キャッシュから応答", "desc": "このリクエストは Replay キャッシュから直接応答されました(同一リクエストが進行中または完了済み)。上流プロバイダーへの呼び出しは行われず、費用は発生しません。", - "replayId": "Replay ID" + "replayId": "Replay ID", + "sourceRequestId": "元のリクエスト" } }, "providerChain": { diff --git a/messages/ja/errors.json b/messages/ja/errors.json index 6ea618bcf..d6bc74d16 100644 --- a/messages/ja/errors.json +++ b/messages/ja/errors.json @@ -30,6 +30,8 @@ "PERMISSION_DENIED": "アクセス権限がありません", "TOKEN_REQUIRED": "認証トークンが必要です", "INVALID_TOKEN": "無効な認証トークン", + "SESSION_REQUEST_SOURCE_MISMATCH": "リクエスト元はこの Session に属していません", + "SESSION_REQUEST_SELECTOR_INCOMPLETE": "プレフィックス Session のリクエストでは、物理ソースとリクエスト番号の両方を指定する必要があります", "PROXY_INVALID_API_KEY": "API キーが無効です。指定されたキーは存在しないか、削除されています。", "PROXY_API_KEY_DISABLED": "この API キーは無効化されています。管理者に再有効化を依頼するか、別のキーをご使用ください。", "PROXY_API_KEY_EXPIRED": "この API キーは期限切れです。管理者に更新を依頼するか、新しいキーへ切り替えてください。", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 454fe0803..ff0db2cbd 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -88,6 +88,12 @@ "minRetryCount": "Количество ретраев ≥", "minRetryCountPlaceholder": "Введите минимум (0 — без ограничения)", "enabled": "Вкл.", + "replay": { + "label": "Replay", + "all": "Все запросы", + "only": "Только Replay", + "exclude": "Исключить Replay" + }, "apply": "Применить фильтр", "reset": "Сброс", "last7days": "7д", @@ -164,6 +170,7 @@ "prevPage": "Предыдущая", "nextPage": "Следующая", "blocked": "Заблокировано", + "replay": "Replay", "nonBilling": "Не тарифицируется", "skipped": "Пропущено", "specialSettings": "Особые", @@ -349,6 +356,7 @@ "title": "Производительность", "ttfb": "TTFB", "tfft": "TFFT", + "tfftShort": "TFFT", "duration": "Общее время", "outputRate": "Скорость вывода", "outputTokens": "Токены вывода" @@ -554,7 +562,8 @@ "replayServe": { "title": "Обслужено из Replay-кэша", "desc": "Запрос обслужен из Replay-кэша (идентичный запрос уже выполняется или завершён). Обращение к провайдеру не выполнялось, затрат нет.", - "replayId": "Replay ID" + "replayId": "Replay ID", + "sourceRequestId": "Исходный запрос" } }, "providerChain": { diff --git a/messages/ru/errors.json b/messages/ru/errors.json index 51c0b0888..29b2f58ae 100644 --- a/messages/ru/errors.json +++ b/messages/ru/errors.json @@ -30,6 +30,8 @@ "PERMISSION_DENIED": "Доступ запрещен", "TOKEN_REQUIRED": "Требуется токен аутентификации", "INVALID_TOKEN": "Недействительный токен аутентификации", + "SESSION_REQUEST_SOURCE_MISMATCH": "Источник запроса не относится к этой сессии", + "SESSION_REQUEST_SELECTOR_INCOMPLETE": "Для запроса префиксной Session необходимо указать физический источник и порядковый номер запроса", "PROXY_INVALID_API_KEY": "Неверный API-ключ. Указанный ключ не существует или был удалён.", "PROXY_API_KEY_DISABLED": "Этот API-ключ отключён. Обратитесь к администратору, чтобы повторно включить его, или используйте другой ключ.", "PROXY_API_KEY_EXPIRED": "Срок действия этого API-ключа истёк. Обратитесь к администратору, чтобы продлить срок, или замените ключ.", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index f314c58ec..1c0cc3e9a 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -88,6 +88,12 @@ "minRetryCount": "重试次数≥", "minRetryCountPlaceholder": "输入次数(0 表示不限)", "enabled": "已开启", + "replay": { + "label": "Replay", + "all": "全部请求", + "only": "仅 Replay 请求", + "exclude": "排除 Replay 请求" + }, "apply": "应用筛选", "reset": "重置", "last7days": "近7天", @@ -164,6 +170,7 @@ "prevPage": "上一页", "nextPage": "下一页", "blocked": "被拦截", + "replay": "Replay", "nonBilling": "非计费", "skipped": "已跳过", "specialSettings": "特殊设置", @@ -349,6 +356,7 @@ "title": "性能数据", "ttfb": "首字节时间(TTFB)", "tfft": "首 Token 时间(TFFT)", + "tfftShort": "TFFT", "duration": "总耗时", "outputRate": "输出速率", "outputTokens": "输出 Tokens" @@ -554,7 +562,8 @@ "replayServe": { "title": "由 Replay 缓存服务", "desc": "该请求由 Replay 缓存直接服务(相同请求正在进行或已完成),未发起上游供应商调用,不产生费用。", - "replayId": "Replay ID" + "replayId": "Replay ID", + "sourceRequestId": "源请求" } }, "providerChain": { diff --git a/messages/zh-CN/errors.json b/messages/zh-CN/errors.json index 7846d95c8..1ed2e29cc 100644 --- a/messages/zh-CN/errors.json +++ b/messages/zh-CN/errors.json @@ -30,6 +30,8 @@ "PERMISSION_DENIED": "权限不足", "TOKEN_REQUIRED": "需要提供认证令牌", "INVALID_TOKEN": "无效的认证令牌", + "SESSION_REQUEST_SOURCE_MISMATCH": "请求来源不属于该 Session", + "SESSION_REQUEST_SELECTOR_INCOMPLETE": "前缀 Session 请求必须同时指定物理来源和请求序号", "PROXY_INVALID_API_KEY": "API 密钥无效。提供的密钥不存在或已被删除。", "PROXY_API_KEY_DISABLED": "API 密钥已被禁用。请联系管理员重新启用,或使用其他可用密钥。", "PROXY_API_KEY_EXPIRED": "API 密钥已过期。请联系管理员续期或更换密钥。", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index e8e5fee6f..ebb995e21 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -88,6 +88,12 @@ "minRetryCount": "重試次數≥", "minRetryCountPlaceholder": "輸入次數(0 表示不限)", "enabled": "已開啟", + "replay": { + "label": "Replay", + "all": "全部請求", + "only": "僅 Replay 請求", + "exclude": "排除 Replay 請求" + }, "apply": "套用篩選", "reset": "重設", "last7days": "近 7 天", @@ -164,6 +170,7 @@ "prevPage": "上一頁", "nextPage": "下一頁", "blocked": "已攔截", + "replay": "Replay", "nonBilling": "非計費", "skipped": "已跳過", "specialSettings": "特殊設定", @@ -349,6 +356,7 @@ "title": "效能資料", "ttfb": "首字節時間(TTFB)", "tfft": "首 Token 時間(TFFT)", + "tfftShort": "TFFT", "duration": "總耗時", "outputRate": "輸出速率", "outputTokens": "輸出 Tokens" @@ -554,7 +562,8 @@ "replayServe": { "title": "由 Replay 快取服務", "desc": "該請求由 Replay 快取直接服務(相同請求正在進行或已完成),未發起上游供應商呼叫,不產生費用。", - "replayId": "Replay ID" + "replayId": "Replay ID", + "sourceRequestId": "來源請求" } }, "providerChain": { diff --git a/messages/zh-TW/errors.json b/messages/zh-TW/errors.json index 1ad09a848..ba8dbe595 100644 --- a/messages/zh-TW/errors.json +++ b/messages/zh-TW/errors.json @@ -30,6 +30,8 @@ "PERMISSION_DENIED": "權限不足", "TOKEN_REQUIRED": "需要提供認證令牌", "INVALID_TOKEN": "無效的認證令牌", + "SESSION_REQUEST_SOURCE_MISMATCH": "請求來源不屬於此 Session", + "SESSION_REQUEST_SELECTOR_INCOMPLETE": "前綴 Session 請求必須同時指定實體來源與請求序號", "PROXY_INVALID_API_KEY": "API 金鑰無效。提供的金鑰不存在或已被刪除。", "PROXY_API_KEY_DISABLED": "API 金鑰已被停用。請聯絡管理員重新啟用,或使用其他可用金鑰。", "PROXY_API_KEY_EXPIRED": "API 金鑰已過期。請聯絡管理員續期或更換金鑰。", diff --git a/package.json b/package.json index bc60d55ca..8b5b3aa3a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "openapi:lint": "bun scripts/lint-openapi.ts", "cui": "npx cui-server --host 0.0.0.0 --port 30000 --token a7564bc8882aa9a2d25d8b4ea6ea1e2e", "db:generate": "drizzle-kit generate && node scripts/validate-migrations.js", - "db:migrate": "drizzle-kit migrate", + "db:migrate": "bun --conditions=react-server scripts/migrate.ts", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "validate:migrations": "node scripts/validate-migrations.js", diff --git a/scripts/migrate.ts b/scripts/migrate.ts new file mode 100644 index 000000000..08b8d8997 --- /dev/null +++ b/scripts/migrate.ts @@ -0,0 +1,3 @@ +import { runMigrations } from "@/lib/migrate"; + +await runMigrations(); diff --git a/src/actions/active-sessions-utils.ts b/src/actions/active-sessions-utils.ts index 5dd4cae8d..4abd6e8b6 100644 --- a/src/actions/active-sessions-utils.ts +++ b/src/actions/active-sessions-utils.ts @@ -19,13 +19,18 @@ export function summarizeTerminateSessionsBatch( ): BatchTerminationSummary { const uniqueRequestedIds = Array.from(new Set(requestedSessionIds)); - const sessionIdSet = new Set(sessionsData.map((session) => session.sessionId)); - const missingSessionIds = uniqueRequestedIds.filter((id) => !sessionIdSet.has(id)); + const claimedRequestedIds = new Set(); const allowedSessions: AggregateSessionStatsEntry[] = []; const unauthorizedSessions: AggregateSessionStatsEntry[] = []; for (const session of sessionsData) { + const requestedIds = session.requestedSessionIds?.length + ? session.requestedSessionIds + : [session.sessionId]; + for (const requestedId of requestedIds) { + claimedRequestedIds.add(requestedId); + } if (isAdmin || session.userId === currentUserId) { allowedSessions.push(session); } else { @@ -33,6 +38,8 @@ export function summarizeTerminateSessionsBatch( } } + const missingSessionIds = uniqueRequestedIds.filter((id) => !claimedRequestedIds.has(id)); + return { uniqueRequestedIds, allowedSessionIds: allowedSessions.map((session) => session.sessionId), diff --git a/src/actions/active-sessions.ts b/src/actions/active-sessions.ts index db9868f54..a88e25056 100644 --- a/src/actions/active-sessions.ts +++ b/src/actions/active-sessions.ts @@ -9,6 +9,7 @@ import { } from "@/lib/cache/session-cache"; import { logger } from "@/lib/logger"; import { extractAfterRequestMessages, isSessionMessages } from "@/lib/session-detail-snapshots"; +import { resolveSessionRequestLocator } from "@/lib/session-request-locator"; import { normalizeRequestSequence } from "@/lib/utils/request-sequence"; import { buildUnifiedSpecialSettings } from "@/lib/utils/special-settings"; import { @@ -22,6 +23,88 @@ import type { SpecialSetting } from "@/types/special-settings"; import { summarizeTerminateSessionsBatch } from "./active-sessions-utils"; import type { ActionResult } from "./types"; +type ResolvedSessionIdentity = NonNullable< + Awaited> +>; + +type SessionTerminationDependencies = { + SessionManager: typeof import("@/lib/session-manager").SessionManager; + SessionTracker: typeof import("@/lib/session-tracker").SessionTracker; + getAffinityStore: typeof import("@/app/v1/_lib/proxy/affinity/affinity-store").getAffinityStore; + listPhysicalSessionSourcesForIdentity: typeof import("@/repository/message").listPhysicalSessionSourcesForIdentity; +}; + +async function loadSessionTerminationDependencies(): Promise { + const [sessionManagerModule, sessionTrackerModule, affinityStoreModule, messageRepository] = + await Promise.all([ + import("@/lib/session-manager"), + import("@/lib/session-tracker"), + import("@/app/v1/_lib/proxy/affinity/affinity-store"), + import("@/repository/message"), + ]); + + return { + SessionManager: sessionManagerModule.SessionManager, + SessionTracker: sessionTrackerModule.SessionTracker, + getAffinityStore: affinityStoreModule.getAffinityStore, + listPhysicalSessionSourcesForIdentity: messageRepository.listPhysicalSessionSourcesForIdentity, + }; +} + +async function terminateResolvedSessionIdentity( + identity: string, + resolution: ResolvedSessionIdentity | null, + dependencies?: SessionTerminationDependencies +): Promise { + const { + SessionManager, + SessionTracker, + getAffinityStore, + listPhysicalSessionSourcesForIdentity, + } = dependencies ?? (await loadSessionTerminationDependencies()); + + if ( + resolution?.identityKind !== "prefix_affinity" || + !resolution.scopeTag || + !resolution.fingerprint + ) { + const terminated = await SessionManager.terminateSession( + resolution?.sourceSessionId ?? identity + ); + if (terminated) { + await SessionTracker.terminateObservedSession(identity); + } + return terminated; + } + + const invalidated = await getAffinityStore().invalidate( + resolution.scopeTag, + resolution.fingerprint, + [...new Set([resolution.fingerprint, ...resolution.fingerprints])] + ); + if (!invalidated) return false; + + const physicalSources = await listPhysicalSessionSourcesForIdentity(identity); + + for (const source of physicalSources) { + if ( + !(await SessionManager.terminateSession( + source.sessionId, + source.providerIds.length > 0 ? source.providerIds : undefined, + source.keyId + )) + ) { + logger.debug("[ActiveSessions] Physical Session state already absent or superseded", { + identity, + sourceSessionId: source.sessionId, + }); + } + } + + await SessionTracker.terminateObservedSession(identity); + return true; +} + function normalizeRequestSnapshot( snapshot: Awaited< ReturnType @@ -177,7 +260,8 @@ export async function getActiveSessions(): Promise s.sessionId); - const concurrentCounts = await SessionTracker.getConcurrentCountBatch(cachedSessionIds); + const concurrentCounts = + await SessionTracker.getObservedConcurrentCountBatch(cachedSessionIds); return { ok: true, @@ -185,6 +269,8 @@ export async function getActiveSessions(): Promise s.sessionId); - const concurrentCounts = await SessionTracker.getConcurrentCountBatch(allSessionIds); + const concurrentCounts = await SessionTracker.getObservedConcurrentCountBatch(allSessionIds); // 4. 写入缓存 setActiveSessionsCache(sessionsData); @@ -242,6 +328,8 @@ export async function getActiveSessions(): Promise> { +export async function getSessionMessages( + sessionId: string, + requestSequence?: number, + requestedSourceSessionId?: string +): Promise> { try { // 0. 验证用户权限 const authSession = await getSession(); @@ -553,8 +654,18 @@ export async function getSessionMessages(sessionId: string): Promise> { try { // 验证用户权限 @@ -622,11 +734,22 @@ export async function hasSessionMessages( }; } + const locatorResult = await resolveSessionRequestLocator( + sessionId, + requestSequence, + requestedSourceSessionId + ); + if (!locatorResult.ok) return locatorResult; + const { SessionManager } = await import("@/lib/session-manager"); + const sourceSessionId = locatorResult.locator.sourceSessionId; - // 如果指定了序号,检查特定请求 - if (requestSequence !== undefined) { - const messages = await SessionManager.getSessionMessages(sessionId, requestSequence); + // 只有有效的显式序号才检查特定请求;非法值按未指定序号处理。 + if (normalizeRequestSequence(requestSequence) !== null) { + const messages = await SessionManager.getSessionMessages( + sourceSessionId, + locatorResult.locator.requestSequence + ); return { ok: true, data: messages !== null, @@ -634,7 +757,7 @@ export async function hasSessionMessages( } // 否则检查 Session 是否有任意请求的 messages - const hasAny = await SessionManager.hasAnySessionMessages(sessionId); + const hasAny = await SessionManager.hasAnySessionMessages(sourceSessionId); return { ok: true, data: hasAny, @@ -656,12 +779,15 @@ export async function hasSessionMessages( * * @param sessionId - Session ID * @param requestSequence - 请求序号(可选,用于获取 Session 内特定请求的消息) + * @param requestedSourceSessionId - 聚合 identity 下的物理 Session ID * * 安全修复:添加用户权限检查 */ export async function getSessionDetails( sessionId: string, - requestSequence?: number + requestSequence?: number, + requestedSourceSessionId?: string, + requestId?: number ): Promise< ActionResult<{ requestBody: unknown | null; @@ -676,7 +802,10 @@ export async function getSessionDetails( sessionStats: Awaited< ReturnType > | null; + currentSourceSessionId: string; currentSequence: number | null; + prevRequest: { requestId: number; sourceSessionId: string; requestSequence: number } | null; + nextRequest: { requestId: number; sourceSessionId: string; requestSequence: number } | null; prevSequence: number | null; nextSequence: number | null; }> @@ -735,18 +864,27 @@ export async function getSessionDetails( }; } - // 5. 解析 requestSequence:未指定时默认取当前最新请求序号 + const locatorResult = await resolveSessionRequestLocator( + sessionId, + requestSequence, + requestedSourceSessionId, + requestId + ); + if (!locatorResult.ok) return locatorResult; + + const sourceSessionId = locatorResult.locator.sourceSessionId; + const effectiveSequence = locatorResult.locator.requestSequence; + const effectiveRequestId = locatorResult.locator.requestId; + + // 5. 请求 locator 已同时验证 identity、物理 Session 和序号,后续所有读取必须复用它。 const { SessionManager } = await import("@/lib/session-manager"); - const requestCount = await SessionManager.getSessionRequestCount(sessionId); - const normalizedSequence = normalizeRequestSequence(requestSequence); - const effectiveSequence = normalizedSequence ?? (requestCount > 0 ? requestCount : undefined); - const { findAdjacentRequestSequences, findMessageRequestAuditBySessionIdAndSequence } = + const { findAdjacentSessionRequests, findMessageRequestAuditBySessionIdAndSequence } = await import("@/repository/message"); const adjacent = effectiveSequence == null - ? { prevSequence: null, nextSequence: null } - : await findAdjacentRequestSequences(sessionId, effectiveSequence); + ? { prevRequest: null, nextRequest: null } + : await findAdjacentSessionRequests(sessionId, effectiveRequestId); const parseJsonStringOrNull = (value: unknown): unknown => { if (typeof value !== "string") return value; @@ -787,22 +925,22 @@ export async function getSessionDetails( responseSnapshotBefore, responseSnapshotAfter, ] = await Promise.all([ - SessionManager.getSessionRequestBody(sessionId, effectiveSequence), - SessionManager.getSessionMessages(sessionId, effectiveSequence), - SessionManager.getSessionResponse(sessionId, effectiveSequence), - SessionManager.getSessionRequestHeaders(sessionId, effectiveSequence), - SessionManager.getSessionResponseHeaders(sessionId, effectiveSequence), - SessionManager.getSessionClientRequestMeta(sessionId, effectiveSequence), - SessionManager.getSessionUpstreamRequestMeta(sessionId, effectiveSequence), - SessionManager.getSessionUpstreamResponseMeta(sessionId, effectiveSequence), - SessionManager.getSessionSpecialSettings(sessionId, effectiveSequence), + SessionManager.getSessionRequestBody(sourceSessionId, effectiveSequence), + SessionManager.getSessionMessages(sourceSessionId, effectiveSequence), + SessionManager.getSessionResponse(sourceSessionId, effectiveSequence), + SessionManager.getSessionRequestHeaders(sourceSessionId, effectiveSequence), + SessionManager.getSessionResponseHeaders(sourceSessionId, effectiveSequence), + SessionManager.getSessionClientRequestMeta(sourceSessionId, effectiveSequence), + SessionManager.getSessionUpstreamRequestMeta(sourceSessionId, effectiveSequence), + SessionManager.getSessionUpstreamResponseMeta(sourceSessionId, effectiveSequence), + SessionManager.getSessionSpecialSettings(sourceSessionId, effectiveSequence), effectiveSequence - ? findMessageRequestAuditBySessionIdAndSequence(sessionId, effectiveSequence) + ? findMessageRequestAuditBySessionIdAndSequence(sourceSessionId, effectiveSequence) : Promise.resolve(null), - SessionManager.getSessionRequestPhaseSnapshot(sessionId, "before", effectiveSequence), - SessionManager.getSessionRequestPhaseSnapshot(sessionId, "after", effectiveSequence), - SessionManager.getSessionResponsePhaseSnapshot(sessionId, "before", effectiveSequence), - SessionManager.getSessionResponsePhaseSnapshot(sessionId, "after", effectiveSequence), + SessionManager.getSessionRequestPhaseSnapshot(sourceSessionId, "before", effectiveSequence), + SessionManager.getSessionRequestPhaseSnapshot(sourceSessionId, "after", effectiveSequence), + SessionManager.getSessionResponsePhaseSnapshot(sourceSessionId, "before", effectiveSequence), + SessionManager.getSessionResponsePhaseSnapshot(sourceSessionId, "after", effectiveSequence), ]); // 兼容:历史/异常数据可能是 JSON 字符串(前端需要根级对象/数组) @@ -896,9 +1034,12 @@ export async function getSessionDetails( snapshots: effectiveSnapshots, specialSettings: unifiedSpecialSettings, sessionStats, + currentSourceSessionId: sourceSessionId, currentSequence: effectiveSequence ?? null, - prevSequence: adjacent.prevSequence, - nextSequence: adjacent.nextSequence, + prevRequest: adjacent.prevRequest, + nextRequest: adjacent.nextRequest, + prevSequence: adjacent.prevRequest?.requestSequence ?? null, + nextSequence: adjacent.nextRequest?.requestSequence ?? null, }, }; } catch (error) { @@ -930,6 +1071,7 @@ export async function getSessionRequests( ActionResult<{ requests: Array<{ id: number; + sourceSessionId: string; sequence: number; model: string | null; statusCode: number | null; @@ -978,9 +1120,9 @@ export async function getSessionRequests( } // 2. 查询请求列表 - const { findRequestsBySessionId } = await import("@/repository/message"); + const { findRequestsBySessionIdentity } = await import("@/repository/message"); const offset = (page - 1) * pageSize; - const { requests, total } = await findRequestsBySessionId(sessionId, { + const { requests, total } = await findRequestsBySessionIdentity(sessionId, { limit: pageSize, offset, order, @@ -1026,8 +1168,10 @@ export async function terminateActiveSession(sessionId: string): Promise { + const resolution = await resolveSessionIdentity(identity); + return terminateResolvedSessionIdentity(identity, resolution, terminationDependencies); + }) + ); + for (const [index, outcome] of outcomes.entries()) { + if (outcome.status === "fulfilled" && outcome.value) { + successCount += 1; + } else if (outcome.status === "rejected") { + logger.warn("Batch Session termination item failed", { + identity: chunk[index], + error: outcome.reason, + }); + } + } + } const processedCount = allowedSessionIds.length; const allowedFailedCount = Math.max(processedCount - successCount, 0); const failedCount = allowedFailedCount + unauthorizedCount + missingCount; diff --git a/src/actions/concurrent-sessions.ts b/src/actions/concurrent-sessions.ts index dc8c9e32b..86291a657 100644 --- a/src/actions/concurrent-sessions.ts +++ b/src/actions/concurrent-sessions.ts @@ -1,7 +1,7 @@ "use server"; import { logger } from "@/lib/logger"; -import { getActiveConcurrentSessions } from "@/lib/redis"; +import { SessionTracker } from "@/lib/session-tracker"; import type { ActionResult } from "./types"; /** @@ -9,7 +9,7 @@ import type { ActionResult } from "./types"; */ export async function getConcurrentSessions(): Promise> { try { - const count = await getActiveConcurrentSessions(); + const count = await SessionTracker.getObservedGlobalSessionCount(); return { ok: true, data: count, diff --git a/src/actions/session-origin-chain.ts b/src/actions/session-origin-chain.ts index 904b7b23d..b95c1d301 100644 --- a/src/actions/session-origin-chain.ts +++ b/src/actions/session-origin-chain.ts @@ -1,17 +1,16 @@ "use server"; -import { and, eq, inArray, isNull, or } from "drizzle-orm"; -import { db } from "@/drizzle/db"; -import { messageRequest } from "@/drizzle/schema"; import { getSession } from "@/lib/auth"; import { logger } from "@/lib/logger"; -import { findKeyList } from "@/repository/key"; -import { findSessionOriginChain } from "@/repository/message"; +import { resolveSessionRequestLocator } from "@/lib/session-request-locator"; +import { aggregateSessionStats, findSessionOriginChain } from "@/repository/message"; import type { ProviderChainItem } from "@/types/message"; import type { ActionResult } from "./types"; export async function getSessionOriginChain( - sessionId: string + sessionId: string, + requestSequence?: number, + requestedSourceSessionId?: string ): Promise> { try { const session = await getSession(); @@ -19,36 +18,23 @@ export async function getSessionOriginChain( return { ok: false, error: "未登录" }; } - if (session.user.role !== "admin") { - const userKeys = await findKeyList(session.user.id); - const userKeyValues = userKeys.map((key) => key.key); - - const ownershipCondition = - userKeyValues.length > 0 - ? or( - eq(messageRequest.userId, session.user.id), - inArray(messageRequest.key, userKeyValues) - ) - : eq(messageRequest.userId, session.user.id); - - const [ownedSession] = await db - .select({ id: messageRequest.id }) - .from(messageRequest) - .where( - and( - eq(messageRequest.sessionId, sessionId), - isNull(messageRequest.deletedAt), - ownershipCondition - ) - ) - .limit(1); + const sessionStats = await aggregateSessionStats(sessionId); + if (!sessionStats) { + return { ok: false, error: "Session 不存在" }; + } - if (!ownedSession) { - return { ok: false, error: "无权访问该 Session" }; - } + if (session.user.role !== "admin" && sessionStats.userId !== session.user.id) { + return { ok: false, error: "无权访问该 Session" }; } - const chain = await findSessionOriginChain(sessionId); + const locatorResult = await resolveSessionRequestLocator( + sessionId, + requestSequence, + requestedSourceSessionId + ); + if (!locatorResult.ok) return locatorResult; + + const chain = await findSessionOriginChain(locatorResult.locator.sourceSessionId); return { ok: true, data: chain ?? null }; } catch (error) { logger.error("获取会话来源链失败:", error); diff --git a/src/actions/session-response.ts b/src/actions/session-response.ts index 99196d8da..e00de0e8c 100644 --- a/src/actions/session-response.ts +++ b/src/actions/session-response.ts @@ -1,8 +1,10 @@ "use server"; +import type { ActionResult } from "@/actions/types"; import { getSession } from "@/lib/auth"; import { logger } from "@/lib/logger"; import { SessionManager } from "@/lib/session-manager"; +import { resolveSessionRequestLocator } from "@/lib/session-request-locator"; /** * 获取 session 响应体内容 @@ -13,8 +15,10 @@ import { SessionManager } from "@/lib/session-manager"; * 安全修复:添加用户权限检查 */ export async function getSessionResponse( - sessionId: string -): Promise<{ ok: true; data: string } | { ok: false; error: string }> { + sessionId: string, + requestSequence?: number, + requestedSourceSessionId?: string +): Promise> { try { // 0. 验证用户权限 const authSession = await getSession(); @@ -50,10 +54,20 @@ export async function getSessionResponse( }; } - // 3. 获取响应体 - const response = await SessionManager.getSessionResponse(sessionId); + const locatorResult = await resolveSessionRequestLocator( + sessionId, + requestSequence, + requestedSourceSessionId + ); + if (!locatorResult.ok) return locatorResult; - if (!response) { + // 3. 只读取 locator 已授权的物理请求响应体 + const response = await SessionManager.getSessionResponse( + locatorResult.locator.sourceSessionId, + locatorResult.locator.requestSequence + ); + + if (response === null) { return { ok: false, error: "响应体已过期(5分钟 TTL)或尚未记录", diff --git a/src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx b/src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx index a309623c5..2b31cffea 100644 --- a/src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx +++ b/src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx @@ -30,7 +30,8 @@ function SessionItem({ session }: { session: ActiveSessionInfo }) { status: session.status, }); - const shortId = session.sessionId.slice(-6); + const displayIdentity = session.sessionFingerprint ?? session.sessionId; + const shortId = displayIdentity.slice(-6); const userName = session.userName || t("unknownUser"); // Determine ping animation color based on status 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 66f1e9ff6..0410bd2bd 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 @@ -12,14 +12,16 @@ import type { RoutingTraceV1 } from "@/types/routing-trace"; const hasSessionMessagesMock = vi.fn(); -vi.mock("@/actions/active-sessions", () => ({ - hasSessionMessages: (...args: [string, number | undefined]) => hasSessionMessagesMock(...args), +vi.mock("@/lib/api-client/v1/actions/active-sessions", () => ({ + hasSessionMessages: (...args: [string, number | undefined, string | undefined]) => + hasSessionMessagesMock(...args), })); const getSessionOriginChainMock = vi.fn(); -vi.mock("@/actions/session-origin-chain", () => ({ - getSessionOriginChain: (...args: [string]) => getSessionOriginChainMock(...args), +vi.mock("@/lib/api-client/v1/actions/session-origin-chain", () => ({ + getSessionOriginChain: (...args: [string, number | undefined, string | undefined]) => + getSessionOriginChainMock(...args), })); const useRealProviderTimelineMock = vi.fn(() => false); @@ -237,6 +239,13 @@ const messages = { warmup: "Warmup", desc: "Warmup skipped", }, + replayServe: { + ...dashboardMessages.logs.details.replayServe, + title: "Replay", + desc: "Served from Replay without a new upstream charge.", + replayId: "Replay ID", + sourceRequestId: "Source request", + }, blocked: { title: "Blocked", sensitiveWord: "Sensitive word", @@ -422,6 +431,71 @@ function click(element: Element | null) { } describe("error-details-dialog layout", () => { + test("uses the physical source when checking messages for a prefix Session request", async () => { + const { unmount } = renderClientWithIntl( + + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(hasSessionMessagesMock).toHaveBeenCalledWith("pfx:scope:root", 3, "physical-a"); + unmount(); + }); + + test("includes the physical source in the Session detail link", async () => { + hasSessionMessagesMock.mockResolvedValue({ ok: true, data: true }); + const { container, unmount } = renderClientWithIntl( + + ); + + await act(async () => { + await Promise.resolve(); + await Promise.resolve(); + }); + + expect(container.querySelector('a[href*="sourceSessionId=physical-a"]')).toBeTruthy(); + expect(container.querySelector('a[href*="seq=3"]')).toBeTruthy(); + unmount(); + }); + + test("marks Replay requests and shows their source request", () => { + const html = renderWithIntl( + + ); + + expect(html).toContain("Replay"); + expect(html).toContain("Source request"); + expect(html).toContain("7"); + expect(html).not.toContain(">Blocked<"); + }); + test("renders fake-200 forwarded notice when errorMessage is a FAKE_200_* code", () => { const html = renderWithIntl( { externalOpen statusCode={200} errorMessage={null} - sessionId={"sess-origin-3"} + sessionId={"pfx:scope:root"} + sourceSessionId="physical-origin" + requestSequence={3} providerChain={ [ { @@ -2224,7 +2300,7 @@ describe("error-details-dialog origin decision chain", () => { await Promise.resolve(); }); - expect(getSessionOriginChainMock).toHaveBeenCalledWith("sess-origin-3"); + expect(getSessionOriginChainMock).toHaveBeenCalledWith("pfx:scope:root", 3, "physical-origin"); expect(getSessionOriginChainMock).toHaveBeenCalledTimes(1); expect(container.textContent).toContain("Original decision record unavailable"); unmount(); 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 0a143e9ef..0479ec22e 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 @@ -73,8 +73,11 @@ export function LogicTraceTab({ providerChain, routingTrace, sessionId, + sourceSessionId, blockedBy, blockedReason, + isReplay, + replaySourceRequestId, requestSequence, hedgeLosers, costUsd, @@ -167,7 +170,7 @@ export function LogicTraceTab({ }; const isWarmupSkipped = blockedBy === "warmup"; - const isBlocked = !!blockedBy && !isWarmupSkipped; + const isBlocked = !!blockedBy && !isWarmupSkipped && !isReplay; const parsedBlockedReason = parseBlockedReason(blockedReason); // Check if this is a session reuse flow (provider reused from session cache) @@ -246,8 +249,8 @@ export function LogicTraceTab({ )} - {/* F2 Replay Serve Info (cache hit served without upstream call) */} - {blockedBy === "replay_serve" && ( + {/* Replay audit info (served without a new upstream charge) */} + {isReplay && (
@@ -271,11 +274,21 @@ export function LogicTraceTab({
)} + {replaySourceRequestId != null && ( +
+ + {t("replayServe.sourceRequestId")}: + + + {replaySourceRequestId} + +
+ )}
)} {/* Block Info */} - {isBlocked && blockedBy && blockedBy !== "replay_serve" && ( + {isBlocked && blockedBy && (
@@ -468,7 +481,11 @@ export function LogicTraceTab({ setOriginOpen(open); if (open && originChain === undefined && !originLoading) { setOriginLoading(true); - getSessionOriginChain(sessionId) + getSessionOriginChain( + sessionId, + requestSequence ?? undefined, + sourceSessionId ?? undefined + ) .then((result) => { setOriginChain(result.ok ? result.data : null); }) diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx index bca50207a..6a6294d27 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx @@ -28,6 +28,7 @@ import type { MetadataTabProps } from "../types"; export function MetadataTab({ sessionId, + sourceSessionId, requestSequence, userAgent, endpoint, @@ -48,6 +49,16 @@ export function MetadataTab({ checkingMessages, }: MetadataTabProps) { const t = useTranslations("dashboard.logs.details"); + const sessionRequestParams = new URLSearchParams(); + if (requestSequence != null) { + sessionRequestParams.set("seq", String(requestSequence)); + } + if (sourceSessionId) { + sessionRequestParams.set("sourceSessionId", sourceSessionId); + } + const sessionMessagesHref = sessionId + ? `/dashboard/sessions/${sessionId}/messages${sessionRequestParams.size > 0 ? `?${sessionRequestParams.toString()}` : ""}` + : ""; const tChain = useTranslations("provider-chain"); const [timelineCopied, setTimelineCopied] = useState(false); @@ -104,13 +115,7 @@ export function MetadataTab({
{hasMessages && !checkingMessages && ( - + - - - - ), + }) => { + filterPropMocks.controls = filters; + return ( +
+ + + +
+ ); + }, })); import { UsageLogsViewVirtualized } from "./usage-logs-view-virtualized"; @@ -177,6 +195,9 @@ describe("UsageLogsViewVirtualized filter navigation", () => { routerMocks.pushedHref = ""; routerMocks.push.mockClear(); searchParamMocks.value = new URLSearchParams(); + filterPropMocks.panel = undefined; + filterPropMocks.table = undefined; + filterPropMocks.controls = undefined; document.body.innerHTML = ""; }); @@ -186,12 +207,37 @@ describe("UsageLogsViewVirtualized filter navigation", () => { clickButton(container, "apply all filters"); expect(routerMocks.pushedHref).toBe( - "/zh-CN/dashboard/logs?userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&actualResponseModelMismatch=true&endpoint=%2Fv1%2Fmessages&minRetry=1" + "/zh-CN/dashboard/logs?userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&actualResponseModelMismatch=true&endpoint=%2Fv1%2Fmessages&minRetry=1&replayFilter=replay" ); unmount(); }); + it("propagates the Replay URL filter to controls, stats, and table", () => { + searchParamMocks.value = new URLSearchParams("replayFilter=non-replay"); + + const { unmount } = renderUsageLogsView(); + + expect(filterPropMocks.controls?.replayFilter).toBe("non-replay"); + expect(filterPropMocks.panel?.replayFilter).toBe("non-replay"); + expect(filterPropMocks.table?.replayFilter).toBe("non-replay"); + + unmount(); + }); + + it('treats replayFilter="all" as no statistics filter', () => { + searchParamMocks.value = new URLSearchParams("replayFilter=all"); + + const { container, unmount } = renderUsageLogsView(); + + expect(filterPropMocks.controls?.replayFilter).toBe("all"); + expect(filterPropMocks.table?.replayFilter).toBe("all"); + expect(filterPropMocks.panel).toBeUndefined(); + expect(container.querySelector('[data-testid="usage-logs-stats-panel"]')).toBeNull(); + + unmount(); + }); + it("applies exclude-200 status filter through the locale-aware dashboard route", () => { const { container, unmount } = renderUsageLogsView(); diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx index 918ca5d33..04b6eb959 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx @@ -169,6 +169,7 @@ function UsageLogsViewContent({ actualResponseModelMismatch: _params.get("actualResponseModelMismatch") ?? undefined, endpoint: _params.get("endpoint") ?? undefined, minRetry: _params.get("minRetry") ?? undefined, + replayFilter: _params.get("replayFilter") ?? undefined, page: _params.get("page") ?? undefined, }); @@ -255,7 +256,10 @@ function UsageLogsViewContent({ const statsFilters = filters; - const hasStatsFilters = Object.values(statsFilters).some((v) => v !== undefined && v !== false); + const hasStatsFilters = Object.entries(statsFilters).some( + ([key, value]) => + value !== undefined && value !== false && !(key === "replayFilter" && value === "all") + ); const activeFilterCount = useMemo(() => { let count = 0; @@ -269,6 +273,7 @@ function UsageLogsViewContent({ if (statsFilters.actualResponseModelMismatch) count++; if (statsFilters.endpoint) count++; if (statsFilters.minRetryCount !== undefined && statsFilters.minRetryCount > 0) count++; + if (statsFilters.replayFilter && statsFilters.replayFilter !== "all") count++; return count; }, [statsFilters]); const [isFilterOpen, setIsFilterOpen] = useState(activeFilterCount > 0); diff --git a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx index e7f57f5b2..b7a6bd093 100644 --- a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx +++ b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx @@ -104,8 +104,21 @@ vi.mock("./model-display-with-redirect", () => ({ ), })); +const dialogProps = vi.hoisted(() => ({ latest: null as Record | null })); + vi.mock("./error-details-dialog", () => ({ - ErrorDetailsDialog: () =>
, + ErrorDetailsDialog: (props: Record) => { + dialogProps.latest = props; + return ( +
+ ); + }, })); let mockIsProviderFinalized = true; @@ -120,6 +133,7 @@ function makeLog(overrides: Partial): UsageLogRow { id: 1, createdAt: new Date(), sessionId: null, + sourceSessionId: null, requestSequence: null, userName: "u", keyName: "k", @@ -149,6 +163,8 @@ function makeLog(overrides: Partial): UsageLogRow { providerChain: null, blockedBy: null, blockedReason: null, + isReplay: false, + replaySourceRequestId: null, userAgent: null, clientIp: null, messagesCount: null, @@ -196,7 +212,37 @@ function renderCostTooltipWithLog(overrides: Partial) { return tooltip; } +function renderPerformanceWithLog(overrides: Partial) { + const html = renderTableWithLog(overrides); + const container = document.createElement("div"); + container.innerHTML = html; + + const tooltip = [...container.querySelectorAll('[data-slot="tooltip-content"]')].find((node) => + node.textContent?.includes("logs.details.performance.duration") + ); + + if (!(tooltip instanceof HTMLDivElement) || !(tooltip.parentElement instanceof HTMLDivElement)) { + throw new Error("Performance tooltip content not found"); + } + + const trigger = tooltip.parentElement.firstElementChild; + if (!(trigger instanceof HTMLDivElement)) { + throw new Error("Performance tooltip trigger not found"); + } + + return { tooltip, trigger }; +} + describe("virtualized-logs-table thinking effort", () => { + test("forwards Replay provenance to the details dialog", () => { + const html = renderTableWithLog({ isReplay: true, replaySourceRequestId: 7 }); + + expect(dialogProps.latest).toEqual( + expect.objectContaining({ isReplay: true, replaySourceRequestId: 7 }) + ); + expect(html).toContain('data-replay-source-request-id="7"'); + }); + test("在计费模型右侧显示思考强度列", () => { const html = renderTableWithLog({ model: "gpt-5.4", @@ -441,6 +487,22 @@ describe("virtualized-logs-table multiplier badge", () => { expect(html).toContain("animate-spin"); }); + test("renders Replay badge without treating the request as blocked", () => { + mockIsLoading = false; + mockIsError = false; + mockError = null; + mockHasNextPage = false; + mockIsFetchingNextPage = false; + + mockLogs = [makeLog({ id: 1, isReplay: true, replaySourceRequestId: 7 })]; + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("logs.table.replay"); + expect(html).not.toContain("logs.table.blocked"); + }); + test("hides provider column when hiddenColumns includes provider", () => { mockIsLoading = false; mockIsError = false; @@ -530,6 +592,27 @@ describe("virtualized-logs-table multiplier badge", () => { expect(html).toContain("logs.details.performance.tfft"); }); + test("性能列使用 TFFT 缩写", () => { + const { trigger } = renderPerformanceWithLog({ + durationMs: 1000, + tfftMs: 500, + firstByteMs: 250, + }); + + expect(trigger.textContent).toContain("logs.details.performance.tfftShort"); + }); + + test("性能 Tooltip 保留 TFFT 和 TTFB 完整术语", () => { + const { tooltip } = renderPerformanceWithLog({ + durationMs: 1000, + tfftMs: 500, + firstByteMs: 250, + }); + + expect(tooltip.textContent).toContain("logs.details.performance.tfft"); + expect(tooltip.textContent).toContain("logs.details.performance.ttfb"); + }); + test("renders swap indicator on cacheTtl badge when swapCacheTtlApplied is true", () => { mockIsLoading = false; mockIsError = false; diff --git a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx index e0f150534..2502e7b77 100644 --- a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx +++ b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx @@ -70,6 +70,7 @@ export interface VirtualizedLogsTableFilters { actualResponseModelMismatch?: boolean; endpoint?: string; minRetryCount?: number; + replayFilter?: "all" | "replay" | "non-replay"; } const STATUS_BADGE_FALLBACK = @@ -872,7 +873,12 @@ export function VirtualizedLogsTable({ {/* Provider */} {hideProviderColumn ? null : (
- {log.blockedBy ? ( + {log.isReplay ? ( + + + {t("logs.table.replay")} + + ) : log.blockedBy ? ( {t("logs.table.blocked")} @@ -1166,7 +1172,7 @@ export function VirtualizedLogsTable({ ); const tfftLine = log.tfftMs != null && log.tfftMs > 0 - ? `${t("logs.details.performance.tfft")} ${formatDuration(log.tfftMs)}` + ? `${t("logs.details.performance.tfftShort")} ${formatDuration(log.tfftMs)}` : null; const rateLine = rate !== null && !hideRate ? `${rate.toFixed(0)} tok/s` : null; @@ -1231,9 +1237,12 @@ export function VirtualizedLogsTable({ providerChain={log.providerChain} routingTrace={log.routingTrace} sessionId={log.sessionId} + sourceSessionId={log.sourceSessionId} requestSequence={log.requestSequence} blockedBy={log.blockedBy} blockedReason={log.blockedReason} + isReplay={log.isReplay} + replaySourceRequestId={log.replaySourceRequestId} originalModel={log.originalModel} currentModel={log.model} actualResponseModel={log.actualResponseModel} diff --git a/src/app/[locale]/dashboard/logs/_utils/logs-query.test.ts b/src/app/[locale]/dashboard/logs/_utils/logs-query.test.ts index dca1e9749..5b3c6714c 100644 --- a/src/app/[locale]/dashboard/logs/_utils/logs-query.test.ts +++ b/src/app/[locale]/dashboard/logs/_utils/logs-query.test.ts @@ -12,13 +12,15 @@ describe("logs-query", () => { endTime: 2000, statusCode: 500, model: "claude-sonnet", + actualResponseModelMismatch: undefined, endpoint: "/v1/messages", minRetryCount: 1, + replayFilter: "replay", page: 3, }); expect(query.toString()).toBe( - "userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&endpoint=%2Fv1%2Fmessages&minRetry=1&page=3" + "userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&endpoint=%2Fv1%2Fmessages&minRetry=1&replayFilter=replay&page=3" ); }); @@ -44,6 +46,7 @@ describe("logs-query", () => { model: "claude-sonnet", endpoint: "/v1/messages", minRetry: "1", + replayFilter: "non-replay", page: "3", }) ).toEqual({ @@ -56,9 +59,15 @@ describe("logs-query", () => { statusCode: undefined, excludeStatusCode200: true, model: "claude-sonnet", + actualResponseModelMismatch: undefined, endpoint: "/v1/messages", minRetryCount: 1, + replayFilter: "non-replay", page: 3, }); }); + + it("ignores invalid Replay filter values", () => { + expect(parseLogsUrlFilters({ replayFilter: "invalid" }).replayFilter).toBeUndefined(); + }); }); diff --git a/src/app/[locale]/dashboard/logs/_utils/logs-query.ts b/src/app/[locale]/dashboard/logs/_utils/logs-query.ts index 8e068e995..9fc0ac6ec 100644 --- a/src/app/[locale]/dashboard/logs/_utils/logs-query.ts +++ b/src/app/[locale]/dashboard/logs/_utils/logs-query.ts @@ -11,6 +11,7 @@ export interface LogsUrlFilters { actualResponseModelMismatch?: boolean; endpoint?: string; minRetryCount?: number; + replayFilter?: "all" | "replay" | "non-replay"; page?: number; } @@ -46,6 +47,13 @@ export function parseLogsUrlFilters(searchParams: { const actualResponseModelMismatch = parseStringParam(searchParams.actualResponseModelMismatch) === "true" ? true : undefined; + const replayFilterParam = parseStringParam(searchParams.replayFilter); + const replayFilter = + replayFilterParam === "all" || + replayFilterParam === "replay" || + replayFilterParam === "non-replay" + ? replayFilterParam + : undefined; return { userId: parseIntParam(searchParams.userId), @@ -60,6 +68,7 @@ export function parseLogsUrlFilters(searchParams: { actualResponseModelMismatch, endpoint: parseStringParam(searchParams.endpoint), minRetryCount: parseIntParam(searchParams.minRetry), + replayFilter, page, }; } @@ -91,6 +100,10 @@ export function buildLogsUrlQuery(filters: LogsUrlFilters): URLSearchParams { query.set("minRetry", filters.minRetryCount.toString()); } + if (filters.replayFilter && filters.replayFilter !== "all") { + query.set("replayFilter", filters.replayFilter); + } + if (filters.page !== undefined && filters.page > 1) { query.set("page", filters.page.toString()); } diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx index 3eddefc61..2e2233ae0 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx @@ -20,6 +20,7 @@ import { cn } from "@/lib/utils"; interface RequestItem { id: number; + sourceSessionId: string; sequence: number; model: string | null; statusCode: number | null; @@ -33,7 +34,8 @@ interface RequestItem { interface RequestListSidebarProps { sessionId: string; selectedSeq: number | null; - onSelect: (seq: number) => void; + selectedSourceSessionId: string | null; + onSelect: (sourceSessionId: string, seq: number, requestId: number) => void; collapsed?: boolean; className?: string; } @@ -41,6 +43,7 @@ interface RequestListSidebarProps { export function RequestListSidebar({ sessionId, selectedSeq, + selectedSourceSessionId, onSelect, collapsed = false, className, @@ -117,10 +120,11 @@ export function RequestListSidebar({ +
+ ), }; }); @@ -168,7 +180,10 @@ function buildDetailsData( overrides: Partial<{ snapshots: SessionDetailSnapshots | null; sessionStats: unknown | null; + currentSourceSessionId: string | null; currentSequence: number | null; + prevRequest: { requestId: number; sourceSessionId: string; requestSequence: number } | null; + nextRequest: { requestId: number; sourceSessionId: string; requestSequence: number } | null; prevSequence: number | null; nextSequence: number | null; }> = {} @@ -188,7 +203,10 @@ function buildDetailsData( snapshots: createSnapshots(), specialSettings: null, sessionStats: null, + currentSourceSessionId: "physical-current", currentSequence: 7, + prevRequest: null, + nextRequest: null, prevSequence: null, nextSequence: null, ...overrides, @@ -330,6 +348,8 @@ describe("SessionMessagesClient (request export actions)", () => { cacheTtlApplied: "mixed", totalCostUsd: "0.123456", }, + prevRequest: { requestId: 206, sourceSessionId: "physical-prev", requestSequence: 6 }, + nextRequest: { requestId: 208, sourceSessionId: "physical-next", requestSequence: 8 }, prevSequence: 6, nextSequence: 8, }), @@ -351,16 +371,36 @@ describe("SessionMessagesClient (request export actions)", () => { click(nextBtn as HTMLButtonElement); expect(routerReplaceMock).toHaveBeenCalledWith( - "/dashboard/sessions/0123456789abcdef/messages?seq=6" + "/dashboard/sessions/0123456789abcdef/messages?seq=6&sourceSessionId=physical-prev&requestId=206" ); expect(routerReplaceMock).toHaveBeenCalledWith( - "/dashboard/sessions/0123456789abcdef/messages?seq=8" + "/dashboard/sessions/0123456789abcdef/messages?seq=8&sourceSessionId=physical-next&requestId=208" ); expect(container.querySelector("[data-testid='mock-view-mode']")?.textContent).toBe("before"); unmount(); }); + test("stores the physical source Session together with the selected sequence", async () => { + getSessionDetailsMock.mockResolvedValue({ + ok: true, + data: buildDetailsData({ currentSequence: 1 }), + }); + + const { container, unmount } = renderClient(); + await flushEffects(); + + click( + container.querySelector("[data-testid='mock-select-physical-request']") as HTMLButtonElement + ); + + expect(routerReplaceMock).toHaveBeenCalledWith( + "/dashboard/sessions/0123456789abcdef/messages?seq=1&sourceSessionId=physical-selected&requestId=201" + ); + + unmount(); + }); + test("copy and download request payloads from the active view", async () => { const snapshots = createSnapshots(); getSessionDetailsMock.mockResolvedValue({ @@ -525,13 +565,15 @@ describe("SessionMessagesClient (request export actions)", () => { test("shows error when getSessionDetails returns ok:false", async () => { getSessionDetailsMock.mockResolvedValue({ ok: false, - error: "ERR_FETCH", + error: "legacy fallback", + errorCode: "SESSION_REQUEST_SOURCE_MISMATCH", }); const { container, unmount } = renderClient(); await flushEffects(); - expect(container.textContent).toContain("ERR_FETCH"); + expect(container.textContent).toContain("SESSION_REQUEST_SOURCE_MISMATCH"); + expect(container.textContent).not.toContain("legacy fallback"); unmount(); }); diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx index c1b5058c5..9ee35839c 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx @@ -42,6 +42,7 @@ import { terminateActiveSession, } from "@/lib/api-client/v1/actions/active-sessions"; import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; +import { getErrorMessage } from "@/lib/utils/error-messages"; import { DEFAULT_SESSION_DETAIL_VIEW_MODE, type SessionDetailSnapshots, @@ -54,6 +55,7 @@ import { SessionStats } from "./session-stats"; export function SessionMessagesClient() { const t = useTranslations("dashboard.sessions"); + const tErrors = useTranslations("errors"); const params = useParams(); const searchParams = useSearchParams(); @@ -63,12 +65,20 @@ export function SessionMessagesClient() { // URL state const seqParam = searchParams.get("seq"); + const selectedSourceSessionId = searchParams.get("sourceSessionId"); + const requestIdParam = searchParams.get("requestId"); const selectedSeq = (() => { if (!seqParam) return null; const parsed = Number.parseInt(seqParam, 10); if (!Number.isFinite(parsed) || parsed <= 0) return null; return parsed; })(); + const selectedRequestId = (() => { + if (!requestIdParam) return null; + const parsed = Number.parseInt(requestIdParam, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return null; + return parsed; + })(); // Data State const [snapshots, setSnapshots] = useState(null); @@ -84,8 +94,16 @@ export function SessionMessagesClient() { Extract>, { ok: true }>["data"]["sessionStats"] >(null); const [currentSequence, setCurrentSequence] = useState(null); - const [prevSequence, setPrevSequence] = useState(null); - const [nextSequence, setNextSequence] = useState(null); + const [prevRequest, setPrevRequest] = useState<{ + requestId: number; + sourceSessionId: string; + requestSequence: number; + } | null>(null); + const [nextRequest, setNextRequest] = useState<{ + requestId: number; + sourceSessionId: string; + requestSequence: number; + } | null>(null); // UI State const [isLoading, setIsLoading] = useState(true); @@ -104,8 +122,8 @@ export function SessionMessagesClient() { setSpecialSettings(null); setSessionStats(null); setCurrentSequence(null); - setPrevSequence(null); - setNextSequence(null); + setPrevRequest(null); + setNextRequest(null); }, []); const { data: systemSettings } = useQuery({ @@ -116,9 +134,19 @@ export function SessionMessagesClient() { const currencyCode = systemSettings?.currencyDisplay || "USD"; const handleSelectRequest = useCallback( - (seq: number) => { + (sourceSessionId: string | null, seq: number, requestId?: number) => { const params = new URLSearchParams(window.location.search); params.set("seq", seq.toString()); + if (sourceSessionId) { + params.set("sourceSessionId", sourceSessionId); + } else { + params.delete("sourceSessionId"); + } + if (requestId) { + params.set("requestId", requestId.toString()); + } else { + params.delete("requestId"); + } router.replace(`${pathname}?${params.toString()}`); setIsMobileMenuOpen(false); }, @@ -133,7 +161,12 @@ export function SessionMessagesClient() { setError(null); try { - const result = await getSessionDetails(sessionId, selectedSeq ?? undefined); + const result = await getSessionDetails( + sessionId, + selectedSeq ?? undefined, + selectedSourceSessionId ?? undefined, + selectedRequestId ?? undefined + ); if (cancelled) return; if (result.ok) { @@ -141,11 +174,15 @@ export function SessionMessagesClient() { setSpecialSettings(result.data.specialSettings); setSessionStats(result.data.sessionStats); setCurrentSequence(result.data.currentSequence); - setPrevSequence(result.data.prevSequence); - setNextSequence(result.data.nextSequence); + setPrevRequest(result.data.prevRequest); + setNextRequest(result.data.nextRequest); } else { resetDetailsState(); - setError(result.error || t("status.fetchFailed")); + setError( + result.errorCode + ? getErrorMessage(tErrors, result.errorCode, result.errorParams) + : result.error || t("status.fetchFailed") + ); } } catch (err) { if (cancelled) return; @@ -163,7 +200,15 @@ export function SessionMessagesClient() { return () => { cancelled = true; }; - }, [resetDetailsState, sessionId, selectedSeq, t]); + }, [ + resetDetailsState, + selectedRequestId, + selectedSeq, + selectedSourceSessionId, + sessionId, + t, + tErrors, + ]); const currentRequestSnapshot = snapshots?.request[viewMode] ?? null; const currentResponseSnapshot = snapshots?.response[viewMode] ?? null; @@ -264,6 +309,7 @@ export function SessionMessagesClient() { @@ -289,6 +335,7 @@ export function SessionMessagesClient() { prevSequence && handleSelectRequest(prevSequence)} + disabled={!prevRequest} + onClick={() => + prevRequest && + handleSelectRequest( + prevRequest.sourceSessionId, + prevRequest.requestSequence, + prevRequest.requestId + ) + } > {t("details.prevRequest")} @@ -476,8 +530,15 @@ export function SessionMessagesClient() {