diff --git a/deploy/Dockerfile b/deploy/Dockerfile index b426332f4..9486cdb59 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,7 +1,10 @@ # syntax=docker/dockerfile:1 -# 构建阶段:使用 Bun(快速包管理和构建) -FROM --platform=$BUILDPLATFORM oven/bun:debian AS build-base +# 构建阶段:使用真实 Node.js 执行 Next.js,Bun 仅负责包管理和 script 调度 +FROM --platform=$BUILDPLATFORM oven/bun:1.3.10-debian AS bun-runtime + +FROM --platform=$BUILDPLATFORM node:trixie-slim AS build-base +COPY --from=bun-runtime /usr/local/bin/bun /usr/local/bin/bun WORKDIR /app FROM build-base AS deps @@ -28,8 +31,7 @@ ENV CI=true RUN bun run build # 运行阶段:使用 Node.js(避免 Bun 流式响应内存泄漏 Issue #18488) -# 要求 Node >= 22.15(入站请求体 zstd 解压依赖 node:zlib 原生 zstd,见 -# src/app/v1/_lib/proxy/request-body-codec.ts 与 package.json engines)。 +# 要求 Node >= 22.19(undici 8 和入站请求体 zstd 解压依赖,见 package.json engines)。 # node:trixie-slim 基于 Debian Trixie,提供 Node 24+,满足该要求。 FROM node:trixie-slim AS runner ENV NODE_ENV=production diff --git a/drizzle/0117_loving_whiplash.sql b/drizzle/0117_loving_whiplash.sql new file mode 100644 index 000000000..6da42049a --- /dev/null +++ b/drizzle/0117_loving_whiplash.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS "idx_usage_ledger_session_identity" ON "usage_ledger" USING btree (COALESCE("session_identity", "session_id")); diff --git a/drizzle/meta/0117_snapshot.json b/drizzle/meta/0117_snapshot.json new file mode 100644 index 000000000..0a1774259 --- /dev/null +++ b/drizzle/meta/0117_snapshot.json @@ -0,0 +1,5320 @@ +{ + "id": "f20d88c1-dfee-4399-ae01-147936dc1177", + "prevId": "e98c1a23-aea9-459e-a634-fb82f3dacfbf", + "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_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": 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 eb8f7542a..025658d86 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -820,6 +820,13 @@ "when": 1785563419224, "tag": "0116_gigantic_zombie", "breakpoints": true + }, + { + "idx": 117, + "version": "7", + "when": 1785635169798, + "tag": "0117_loving_whiplash", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 3b8dadac8..10539d048 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.8.0", "private": true, "engines": { - "node": ">=22.15.0" + "node": ">=22.19.0" }, "scripts": { "dev": "tsgo -p tsconfig.json --noEmit && next dev --port 13500", diff --git a/src/actions/active-sessions.ts b/src/actions/active-sessions.ts index a88e25056..215d974d9 100644 --- a/src/actions/active-sessions.ts +++ b/src/actions/active-sessions.ts @@ -425,8 +425,13 @@ export async function getAllSessions( if (cached) { logger.debug("[SessionCache] All sessions cache hit"); + const { SessionTracker } = await import("@/lib/session-tracker"); + // 过滤:管理员可查看所有,普通用户只能查看自己的 const filteredCached = isAdmin ? cached : cached.filter((s) => s.userId === currentUserId); + const concurrentCounts = await SessionTracker.getObservedConcurrentCountBatch( + filteredCached.map((s) => s.sessionId) + ); // 分离活跃和非活跃(5 分钟内有请求为活跃) const now = Date.now(); @@ -437,6 +442,7 @@ export async function getAllSessions( for (const s of filteredCached) { const lastRequestTime = s.lastRequestAt ? new Date(s.lastRequestAt).getTime() : 0; + const concurrentCount = concurrentCounts.get(s.sessionId) ?? 0; const sessionInfo: ActiveSessionInfo = { sessionId: s.sessionId, sessionIdentityKind: s.sessionIdentityKind, @@ -460,12 +466,14 @@ export async function getAllSessions( s.totalCacheCreationTokens + s.totalCacheReadTokens, costUsd: s.totalCostUsd, - status: "completed", + status: concurrentCount > 0 ? "in_progress" : "completed", durationMs: s.totalDurationMs, requestCount: s.requestCount, + concurrentCount, }; - if (lastRequestTime >= fiveMinutesAgo) { + const isConcurrent = concurrentCount > 0; + if (isConcurrent || lastRequestTime >= fiveMinutesAgo) { active.push(sessionInfo); } else { inactive.push(sessionInfo); @@ -520,6 +528,10 @@ export async function getAllSessions( const { aggregateMultipleSessionStats } = await import("@/repository/message"); const sessionsData = await aggregateMultipleSessionStats(allSessionIds); + const concurrentCounts = await SessionTracker.getObservedConcurrentCountBatch( + sessionsData.map((s) => s.sessionId) + ); + // 4. 写入缓存 setActiveSessionsCache(sessionsData, cacheKey); @@ -537,6 +549,7 @@ export async function getAllSessions( for (const s of filteredSessions) { const lastRequestTime = s.lastRequestAt ? new Date(s.lastRequestAt).getTime() : 0; + const concurrentCount = concurrentCounts.get(s.sessionId) ?? 0; const sessionInfo: ActiveSessionInfo = { sessionId: s.sessionId, sessionIdentityKind: s.sessionIdentityKind, @@ -560,12 +573,14 @@ export async function getAllSessions( s.totalCacheCreationTokens + s.totalCacheReadTokens, costUsd: s.totalCostUsd, - status: "completed", + status: concurrentCount > 0 ? "in_progress" : "completed", durationMs: s.totalDurationMs, requestCount: s.requestCount, + concurrentCount, }; - if (lastRequestTime >= fiveMinutesAgo) { + const isConcurrent = concurrentCount > 0; + if (isConcurrent || lastRequestTime >= fiveMinutesAgo) { active.push(sessionInfo); } else { inactive.push(sessionInfo); diff --git a/src/actions/usage-logs.ts b/src/actions/usage-logs.ts index 026412b97..d51594c26 100644 --- a/src/actions/usage-logs.ts +++ b/src/actions/usage-logs.ts @@ -157,7 +157,10 @@ async function buildUsageLogsExport( progress: Pick ) => Promise | void ): Promise { - const initialResult = await findUsageLogsWithDetails({ ...filters, page: 1, pageSize: 1 }); + const initialResult = await findUsageLogsWithDetails( + { ...filters, page: 1, pageSize: 1 }, + { includeSourceSessionIds: false } + ); let estimatedTotalRows = initialResult.total; if (estimatedTotalRows === 0) { @@ -178,6 +181,7 @@ async function buildUsageLogsExport( ...filters, cursor, limit: USAGE_LOGS_EXPORT_BATCH_SIZE, + includeSourceSessionIds: false, }); if (batch.logs.length > 0) { diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx index d1a7117a8..f2dd04d7a 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx @@ -24,7 +24,9 @@ vi.mock("@/components/ui/tooltip", () => ({ TooltipProvider: ({ children }: { children?: ReactNode }) =>
{children}
, Tooltip: ({ children }: { children?: ReactNode }) =>
{children}
, TooltipTrigger: ({ children }: { children?: ReactNode }) =>
{children}
, - TooltipContent: ({ children }: { children?: ReactNode }) =>
{children}
, + TooltipContent: ({ children }: { children?: ReactNode }) => ( +
{children}
+ ), })); vi.mock("@/components/ui/relative-time", () => ({ @@ -606,6 +608,57 @@ describe("usage-logs-table multiplier badge", () => { }); container.remove(); }); + + test("shows the client session ID and keeps the canonical prefix identity in the tooltip", () => { + const html = renderToStaticMarkup( + {}} + isPending={false} + /> + ); + + expect(html).toContain('data-session-id="client-session-id"'); + expect(html).toContain("client-session-id"); + expect(html).toContain("pfx:scope:fingerprint"); + }); + + test("does not duplicate the canonical identity when it is the source fallback", () => { + const html = renderToStaticMarkup( + {}} + isPending={false} + /> + ); + const container = document.createElement("div"); + container.innerHTML = html; + const tooltip = [...container.querySelectorAll('[data-slot="tooltip-content"]')].find((node) => + node.textContent?.includes("same-session-id") + ); + + expect( + [...(tooltip?.querySelectorAll("span") ?? [])].filter( + (node) => node.textContent === "same-session-id" + ) + ).toHaveLength(1); + }); }); describe("usage-logs-table pricing resolution", () => { diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx index 535530b1e..02f7bec8e 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx @@ -161,6 +161,9 @@ export function UsageLogsTable({ : Number(actualCostMultiplier); const hasCostBadge = multiplier != null && Number.isFinite(multiplier) && multiplier !== 1; + const displayedSourceSessionIds = ( + log.sourceSessionIds?.length ? log.sourceSessionIds : [log.sourceSessionId] + ).filter((id): id is string => Boolean(id)); return ( - {log.sessionId} + {log.sourceSessionId ?? log.sessionId}

- {log.sessionId} + {displayedSourceSessionIds.map((id) => ( + + {id} + + ))} + {log.sessionId && + !displayedSourceSessionIds.includes(log.sessionId) && ( + + {log.sessionId} + + )}

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 b2459e1b6..9588ba41e 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 @@ -8,6 +8,7 @@ import type { UsageLogRow } from "@/repository/usage-logs"; import type { RoutingTraceV1 } from "@/types/routing-trace"; let mockLogs: UsageLogRow[] = []; +let mockSourceSessionIdsByIdentity: Record = {}; let mockIsLoading = false; let mockIsError = false; let mockError: unknown = null; @@ -24,7 +25,16 @@ vi.mock("@tanstack/react-query", () => ({ useInfiniteQuery: (options: unknown) => { useInfiniteQuerySpy(options); return { - data: { pages: [{ logs: mockLogs, nextCursor: null, hasMore: false }] }, + data: { + pages: [ + { + logs: mockLogs, + sourceSessionIdsByIdentity: mockSourceSessionIdsByIdentity, + nextCursor: null, + hasMore: false, + }, + ], + }, fetchNextPage: vi.fn(), hasNextPage: mockHasNextPage, isFetchingNextPage: mockIsFetchingNextPage, @@ -192,10 +202,46 @@ function renderTableWithLog(overrides: Partial) { mockHasNextPage = false; mockIsFetchingNextPage = false; mockLogs = [makeLog({ id: 1, ...overrides })]; + mockSourceSessionIdsByIdentity = {}; return renderToStaticMarkup(); } +test("shows the client session ID and canonical prefix identity in the virtualized tooltip", () => { + renderTableWithLog({ + sessionId: "pfx:scope:fingerprint", + sourceSessionId: "client-session-id", + }); + mockSourceSessionIdsByIdentity = { + "pfx:scope:fingerprint": ["client-session-id", "client-session-id-2"], + }; + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("client-session-id"); + expect(html).toContain("client-session-id-2"); + expect(html).toContain("pfx:scope:fingerprint"); +}); + +test("does not duplicate the canonical identity when it is the source fallback", () => { + const html = renderTableWithLog({ + sessionId: "same-session-id", + sourceSessionId: "same-session-id", + }); + const container = document.createElement("div"); + container.innerHTML = html; + const tooltip = [...container.querySelectorAll('[data-slot="tooltip-content"]')].find((node) => + node.textContent?.includes("same-session-id") + ); + + expect( + [...(tooltip?.querySelectorAll("span") ?? [])].filter( + (node) => node.textContent === "same-session-id" + ) + ).toHaveLength(1); +}); + function renderCostTooltipWithLog(overrides: Partial) { const html = renderTableWithLog(overrides); const container = document.createElement("div"); 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 6565ad4e7..61d62bada 100644 --- a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx +++ b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx @@ -203,6 +203,13 @@ export function VirtualizedLogsTable({ // Flatten all pages into a single array const pages = data?.pages; const allLogs = useMemo(() => pages?.flatMap((page) => page.logs) ?? [], [pages]); + const sourceSessionIdsByIdentity = useMemo>( + () => + Object.fromEntries( + pages?.flatMap((page) => Object.entries(page.sourceSessionIdsByIdentity ?? {})) ?? [] + ), + [pages] + ); const filtersResetKey = useMemo(() => JSON.stringify(filters), [filters]); const previousFiltersResetKeyRef = useRef(filtersResetKey); @@ -781,6 +788,16 @@ export function VirtualizedLogsTable({ } const isNonBilling = isNonBillingEndpoint(log.endpoint); + const mappedSourceSessionIds = log.sessionId + ? sourceSessionIdsByIdentity[log.sessionId] + : undefined; + const displayedSourceSessionIds = ( + mappedSourceSessionIds?.length + ? mappedSourceSessionIds + : log.sourceSessionIds?.length + ? log.sourceSessionIds + : [log.sourceSessionId] + ).filter((id): id is string => Boolean(id)); const _isWarmupSkipped = log.blockedBy === "warmup"; return (
- {log.sessionId} + {log.sourceSessionId ?? log.sessionId}

- {log.sessionId} + {displayedSourceSessionIds.map((id) => ( + + {id} + + ))} + {log.sessionId && + !displayedSourceSessionIds.includes(log.sessionId) && ( + + {log.sessionId} + + )}

diff --git a/src/app/api/v1/resources/usage-logs/handlers.ts b/src/app/api/v1/resources/usage-logs/handlers.ts index 3b1fc95bd..62811b4db 100644 --- a/src/app/api/v1/resources/usage-logs/handlers.ts +++ b/src/app/api/v1/resources/usage-logs/handlers.ts @@ -209,11 +209,18 @@ function toUsageLogsListResponse(data: unknown, query: UsageLogsActionQueryInput total?: number; page?: number; pageSize?: number; + sourceSessionIdsByIdentity?: Record; }; + const sourceSessionIdsByIdentity = + body.sourceSessionIdsByIdentity !== undefined + ? { sourceSessionIdsByIdentity: body.sourceSessionIdsByIdentity } + : {}; + if (query.cursor || body.nextCursor !== undefined || body.hasMore !== undefined) { return { items: body.logs ?? [], + ...sourceSessionIdsByIdentity, pageInfo: { nextCursor: normalizeUsageLogsCursor(body.nextCursor), hasMore: Boolean(body.hasMore), @@ -227,6 +234,7 @@ function toUsageLogsListResponse(data: unknown, query: UsageLogsActionQueryInput const total = body.total ?? body.logs?.length ?? 0; return { items: body.logs ?? [], + ...sourceSessionIdsByIdentity, pageInfo: { page, pageSize, diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index d40b91a9e..ebb4d600a 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -1230,6 +1230,8 @@ export const usageLedger = pgTable('usage_ledger', { ) .on(sql`COALESCE(${table.sessionIdentity}, ${table.sessionId})`, table.createdAt.desc()) .where(sql`${table.blockedBy} IS NULL AND ${table.isReplay} = false`), + usageLedgerSessionIdentityIdx: index('idx_usage_ledger_session_identity') + .on(sql`COALESCE(${table.sessionIdentity}, ${table.sessionId})`), usageLedgerModelIdx: index('idx_usage_ledger_model') .on(table.model) .where(sql`${table.model} IS NOT NULL`), diff --git a/src/lib/api-client/v1/actions/usage-logs.ts b/src/lib/api-client/v1/actions/usage-logs.ts index 483d5a02e..42a9626c2 100644 --- a/src/lib/api-client/v1/actions/usage-logs.ts +++ b/src/lib/api-client/v1/actions/usage-logs.ts @@ -112,6 +112,7 @@ function toLegacyUsageLogsPage(body: unknown): UsageLogsBatchResult { const page = body as { logs?: UsageLogsBatchResult["logs"]; items?: UsageLogsBatchResult["logs"]; + sourceSessionIdsByIdentity?: UsageLogsBatchResult["sourceSessionIdsByIdentity"]; pageInfo?: { nextCursor?: UsageLogsBatchResult["nextCursor"] | string; hasMore?: boolean; @@ -129,6 +130,7 @@ function toLegacyUsageLogsPage(body: unknown): UsageLogsBatchResult { }; return { logs: page.logs ?? page.items ?? [], + sourceSessionIdsByIdentity: page.sourceSessionIdsByIdentity, nextCursor: normalizeLegacyCursor(page.nextCursor ?? page.pageInfo?.nextCursor), hasMore: page.hasMore ?? page.pageInfo?.hasMore ?? false, ...((page.total ?? page.pageInfo?.total !== undefined) diff --git a/src/lib/migrations/session-replay-index-preflight.ts b/src/lib/migrations/session-replay-index-preflight.ts index 2e9cb00fc..618487bb6 100644 --- a/src/lib/migrations/session-replay-index-preflight.ts +++ b/src/lib/migrations/session-replay-index-preflight.ts @@ -1,4 +1,5 @@ export const SESSION_REPLAY_MIGRATION_CREATED_AT = 1785563419224; +export const SESSION_IDENTITY_INDEX_MIGRATION_CREATED_AT = 1785635169798; export const SESSION_REPLAY_INDEX_MARKER = "cch:migration:0116:session-replay-index:v1"; export type MigrationIndexState = { @@ -31,6 +32,11 @@ export const SESSION_REPLAY_INDEX_SPECS: readonly SessionReplayIndexSpec[] = [ definition: 'ON "usage_ledger" USING btree (COALESCE("session_identity", "session_id"),"created_at" DESC NULLS LAST) WHERE "usage_ledger"."blocked_by" IS NULL AND "usage_ledger"."is_replay" = false', }, + { + canonicalName: "idx_usage_ledger_session_identity", + temporaryName: "cch_0117_tmp_01", + definition: 'ON "usage_ledger" USING btree (COALESCE("session_identity", "session_id"))', + }, { canonicalName: "idx_usage_ledger_user_created_at", temporaryName: "cch_0116_tmp_03", @@ -153,7 +159,7 @@ export async function runSessionReplayMigrationPlan(input: { baseTablesReady && (latestMigrationCreatedAt == null || !Number.isFinite(latestMigrationCreatedAt) || - latestMigrationCreatedAt < SESSION_REPLAY_MIGRATION_CREATED_AT) + latestMigrationCreatedAt < SESSION_IDENTITY_INDEX_MIGRATION_CREATED_AT) ) { await runIndexPreflight({ ensureColumns: true }); } diff --git a/src/repository/_shared/usage-log-filters.ts b/src/repository/_shared/usage-log-filters.ts index 274de058b..b7a54aac9 100644 --- a/src/repository/_shared/usage-log-filters.ts +++ b/src/repository/_shared/usage-log-filters.ts @@ -20,6 +20,10 @@ export interface UsageLogFilterParams { export const DEFAULT_HIDDEN_USAGE_LOG_ENDPOINTS = [...NON_BILLING_ENDPOINTS]; +export function isReservedSessionIdentity(value: string): boolean { + return value.startsWith("pfx:") || value.startsWith("sid:"); +} + function normalizeUsageLogEndpoint(endpoint: string): string { const trimmed = endpoint.trim().toLowerCase(); if (!trimmed) { @@ -141,8 +145,13 @@ export function buildUsageLogConditions(filters: UsageLogFilterParams): SQL[] { const trimmedSessionId = filters.sessionId?.trim(); if (trimmedSessionId) { + const canonicalCondition = sql` + COALESCE(${messageRequest.sessionIdentity}, ${messageRequest.sessionId}) = ${trimmedSessionId} + `; conditions.push( - sql`COALESCE(${messageRequest.sessionIdentity}, ${messageRequest.sessionId}) = ${trimmedSessionId}` + isReservedSessionIdentity(trimmedSessionId) + ? canonicalCondition + : sql`(${canonicalCondition} OR ${messageRequest.sessionId} = ${trimmedSessionId})` ); } diff --git a/src/repository/activity-stream.ts b/src/repository/activity-stream.ts index 9043d8629..d8487745c 100644 --- a/src/repository/activity-stream.ts +++ b/src/repository/activity-stream.ts @@ -5,6 +5,10 @@ import { db } from "@/drizzle/db"; import { keys as keysTable, messageRequest, providers, users } from "@/drizzle/schema"; import { logger } from "@/lib/logger"; +const messageSessionIdentity = sql< + string | null +>`COALESCE(${messageRequest.sessionIdentity}, ${messageRequest.sessionId})`; + /** * 活动流条目(单个请求记录) */ @@ -63,17 +67,17 @@ export async function findRecentActivityStream(limit = 20): Promise 0) { - // 使用窗口函数获取每个 session 的最新请求 - const activeSessionRequests = await db - .select({ + // 先在数据库内按 canonical identity 去重,再限制 session 级结果数量。 + const latestActiveSessionRows = db + .selectDistinctOn([messageSessionIdentity], { id: messageRequest.id, - sessionId: messageRequest.sessionId, + sessionId: messageSessionIdentity.as("session_id"), userName: users.name, userId: messageRequest.userId, keyId: keysTable.id, @@ -90,7 +94,6 @@ export async function findRecentActivityStream(limit = 20): Promise`ROW_NUMBER() OVER (PARTITION BY ${messageRequest.sessionId} ORDER BY ${messageRequest.createdAt} DESC)`, }) .from(messageRequest) .leftJoin(users, eq(messageRequest.userId, users.id)) @@ -100,35 +103,38 @@ export async function findRecentActivityStream(limit = 20): Promise row.rowNum === 1) - .map((row) => ({ - id: row.id, - sessionId: row.sessionId, - userName: row.userName || "Unknown", - userId: row.userId, - keyId: row.keyId ?? 0, - keyName: row.keyName || "Unknown", - providerId: row.providerId, - providerName: row.providerName, - model: row.model, - originalModel: row.originalModel, - statusCode: row.statusCode, - durationMs: row.durationMs, - costUsd: row.costUsd, - startTime: row.createdAt ? new Date(row.createdAt).getTime() : Date.now(), - inputTokens: row.inputTokens, - outputTokens: row.outputTokens, - cacheCreationInputTokens: row.cacheCreationInputTokens, - cacheReadInputTokens: row.cacheReadInputTokens, - })); + .orderBy(messageSessionIdentity, desc(messageRequest.createdAt)) + .as("latest_active_session_rows"); + + const activeSessionRequests = await db + .select() + .from(latestActiveSessionRows) + .orderBy(desc(latestActiveSessionRows.createdAt)) + .limit(limit); + + const latestPerSession = activeSessionRequests.map((row) => ({ + id: row.id, + sessionId: row.sessionId, + userName: row.userName || "Unknown", + userId: row.userId, + keyId: row.keyId ?? 0, + keyName: row.keyName || "Unknown", + providerId: row.providerId, + providerName: row.providerName, + model: row.model, + originalModel: row.originalModel, + statusCode: row.statusCode, + durationMs: row.durationMs, + costUsd: row.costUsd, + startTime: row.createdAt ? new Date(row.createdAt).getTime() : Date.now(), + inputTokens: row.inputTokens, + outputTokens: row.outputTokens, + cacheCreationInputTokens: row.cacheCreationInputTokens, + cacheReadInputTokens: row.cacheReadInputTokens, + })); activityItems = latestPerSession; @@ -147,13 +153,13 @@ export async function findRecentActivityStream(limit = 20): Promise 0) { - conditions.push(notInArray(messageRequest.sessionId, excludedSessionIds)); + conditions.push(notInArray(messageSessionIdentity, excludedSessionIds)); } const recentRequests = await db .select({ id: messageRequest.id, - sessionId: messageRequest.sessionId, + sessionId: messageSessionIdentity, userName: users.name, userId: messageRequest.userId, keyId: keysTable.id, diff --git a/src/repository/usage-logs.ts b/src/repository/usage-logs.ts index 9a5a6121b..a583662a3 100644 --- a/src/repository/usage-logs.ts +++ b/src/repository/usage-logs.ts @@ -1,6 +1,7 @@ import "server-only"; -import { and, desc, eq, gte, isNull, lt, sql } from "drizzle-orm"; +import type { SQL } from "drizzle-orm"; +import { and, desc, eq, gte, inArray, isNull, lt, sql } from "drizzle-orm"; import { db } from "@/drizzle/db"; import { keys as keysTable, messageRequest, providers, usageLedger, users } from "@/drizzle/schema"; import { TTLMap } from "@/lib/cache/ttl-map"; @@ -19,6 +20,7 @@ import { buildDefaultHiddenUsageLogEndpointCondition, buildUsageLogConditions, buildUsageLogEndpointMatchCondition, + isReservedSessionIdentity, RETRY_COUNT_EXPR, type UsageLogReplayFilter, } from "./_shared/usage-log-filters"; @@ -59,6 +61,13 @@ function buildLedgerUsageLogConditions(replayFilter: UsageLogReplayFilter | unde return conditions; } +function buildLedgerSessionIdCondition(sessionId: string) { + const canonicalCondition = sql`${ledgerSessionIdentity} = ${sessionId}`; + return isReservedSessionIdentity(sessionId) + ? canonicalCondition + : sql`(${canonicalCondition} OR ${usageLedger.sessionId} = ${sessionId})`; +} + const messageSessionIdentity = sql< string | null >`COALESCE(${messageRequest.sessionIdentity}, ${messageRequest.sessionId})`; @@ -66,11 +75,146 @@ const ledgerSessionIdentity = sql< string | null >`COALESCE(${usageLedger.sessionIdentity}, ${usageLedger.sessionId})`; +interface UsageLogSourceSessionScope { + userId?: number; + keyId?: number; + keyString?: string; +} + +async function loadMessageSourceSessionIds( + sessionIds: string[], + scope: UsageLogSourceSessionScope +): Promise> { + if (sessionIds.length === 0) return new Map(); + + const conditions = [ + isNull(messageRequest.deletedAt), + inArray(messageSessionIdentity, sessionIds), + ]; + if (scope.userId !== undefined) conditions.push(eq(messageRequest.userId, scope.userId)); + if (scope.keyId !== undefined) conditions.push(eq(keysTable.id, scope.keyId)); + if (scope.keyString !== undefined) conditions.push(eq(messageRequest.key, scope.keyString)); + + const baseQuery = db + .select({ + sessionId: messageSessionIdentity, + sourceSessionIds: sql` + ARRAY_AGG(DISTINCT ${messageRequest.sessionId}) + FILTER (WHERE ${messageRequest.sessionId} IS NOT NULL) + `, + }) + .from(messageRequest); + const query = + scope.keyId !== undefined + ? baseQuery.innerJoin(keysTable, eq(messageRequest.key, keysTable.key)) + : baseQuery; + const rows = await query.where(and(...conditions)).groupBy(messageSessionIdentity); + + return new Map( + rows + .filter((row): row is { sessionId: string; sourceSessionIds: string[] } => + Boolean(row.sessionId) + ) + .map((row) => [row.sessionId, row.sourceSessionIds ?? []]) + ); +} + +async function loadLedgerSourceSessionIds( + sessionIds: string[], + scope: UsageLogSourceSessionScope +): Promise> { + if (sessionIds.length === 0) return new Map(); + + const conditions = [inArray(ledgerSessionIdentity, sessionIds)]; + if (scope.userId !== undefined) conditions.push(eq(usageLedger.userId, scope.userId)); + if (scope.keyId !== undefined) conditions.push(eq(keysTable.id, scope.keyId)); + if (scope.keyString !== undefined) conditions.push(eq(usageLedger.key, scope.keyString)); + + const baseQuery = db + .select({ + sessionId: ledgerSessionIdentity, + sourceSessionIds: sql` + ARRAY_AGG(DISTINCT ${usageLedger.sessionId}) + FILTER (WHERE ${usageLedger.sessionId} IS NOT NULL) + `, + }) + .from(usageLedger); + const query = + scope.keyId !== undefined + ? baseQuery.innerJoin(keysTable, eq(usageLedger.key, keysTable.key)) + : baseQuery; + const rows = await query.where(and(...conditions)).groupBy(ledgerSessionIdentity); + + return new Map( + rows + .filter((row): row is { sessionId: string; sourceSessionIds: string[] } => + Boolean(row.sessionId) + ) + .map((row) => [row.sessionId, row.sourceSessionIds ?? []]) + ); +} + +async function hydrateUsageLogSourceSessionIds( + logs: UsageLogRow[], + scope: UsageLogSourceSessionScope, + sources: { message: boolean; ledger: boolean } +): Promise { + const sessionIds = [ + ...new Set(logs.map((log) => log.sessionId).filter((id): id is string => Boolean(id))), + ]; + if (sessionIds.length === 0) return logs; + + const [messageSources, ledgerSources] = await Promise.all([ + sources.message ? loadMessageSourceSessionIds(sessionIds, scope) : Promise.resolve(new Map()), + sources.ledger ? loadLedgerSourceSessionIds(sessionIds, scope) : Promise.resolve(new Map()), + ]); + + return logs.map((log) => { + if (!log.sessionId) return log; + const sourceSessionIds = [ + ...new Set([ + ...(messageSources.get(log.sessionId) ?? []), + ...(ledgerSources.get(log.sessionId) ?? []), + ]), + ]; + return sourceSessionIds.length > 0 ? { ...log, sourceSessionIds } : log; + }); +} + +async function loadUsageLogSourceSessionIdsByIdentity( + logs: UsageLogRow[], + scope: UsageLogSourceSessionScope, + sources: { message: boolean; ledger: boolean } +): Promise> { + const sessionIds = [ + ...new Set(logs.map((log) => log.sessionId).filter((id): id is string => Boolean(id))), + ]; + if (sessionIds.length === 0) return {}; + + const [messageSources, ledgerSources] = await Promise.all([ + sources.message ? loadMessageSourceSessionIds(sessionIds, scope) : Promise.resolve(new Map()), + sources.ledger ? loadLedgerSourceSessionIds(sessionIds, scope) : Promise.resolve(new Map()), + ]); + + const sourceIdsByIdentity: Array<[string, string[]]> = []; + for (const sessionId of sessionIds) { + const sourceSessionIds = [ + ...new Set([ + ...(messageSources.get(sessionId) ?? []), + ...(ledgerSources.get(sessionId) ?? []), + ]), + ]; + if (sourceSessionIds.length > 0) sourceIdsByIdentity.push([sessionId, sourceSessionIds]); + } + return Object.fromEntries(sourceIdsByIdentity); +} + export interface UsageLogRow { id: number; createdAt: Date | null; sessionId: string | null; // Public Session identity sourceSessionId: string | null; // Physical Session source for request-scoped readback + sourceSessionIds?: string[]; // All physical client session IDs grouped under the public identity requestSequence: number | null; // Request Sequence(Session 内请求序号) userName: string; keyName: string; @@ -161,6 +305,7 @@ export interface UsageLogsPaginatedResult { */ export interface UsageLogsBatchResult { logs: UsageLogRow[]; + sourceSessionIdsByIdentity?: Record; nextCursor: { createdAt: string; id: number } | null; hasMore: boolean; } @@ -171,6 +316,8 @@ export interface UsageLogsBatchResult { export interface UsageLogBatchFilters extends Omit { cursor?: { createdAt: string; id: number }; limit?: number; + /** Export callers can skip the UI-only source identity hydration query. */ + includeSourceSessionIds?: boolean; } /** @@ -180,7 +327,7 @@ export interface UsageLogBatchFilters extends Omit { - const { userId, keyId, providerId, cursor, limit = 50 } = filters; + const { userId, keyId, providerId, cursor, limit = 50, includeSourceSessionIds = true } = filters; const safeLimit = Math.min(100, Math.max(1, limit)); // Build query conditions @@ -313,7 +460,19 @@ export async function findUsageLogsBatch( }); if (logs.length > 0) { - return { logs, nextCursor, hasMore }; + const sourceSessionIdsByIdentity = includeSourceSessionIds + ? await loadUsageLogSourceSessionIdsByIdentity( + logs, + { userId: filters.userId, keyId: filters.keyId }, + { message: true, ledger: false } + ) + : undefined; + return { + logs, + sourceSessionIdsByIdentity, + nextCursor, + hasMore, + }; } if (!(await isLedgerOnlyMode())) { @@ -340,7 +499,7 @@ export async function findUsageLogsBatch( const trimmedSessionId = filters.sessionId?.trim(); if (trimmedSessionId) { - ledgerConditions.push(eq(ledgerSessionIdentity, trimmedSessionId)); + ledgerConditions.push(buildLedgerSessionIdCondition(trimmedSessionId)); } if (filters.startTime !== undefined) { @@ -503,7 +662,18 @@ export async function findUsageLogsBatch( }; }); - return { logs: fallbackLogs, nextCursor: ledgerNextCursor, hasMore: ledgerHasMore }; + return { + logs: fallbackLogs, + sourceSessionIdsByIdentity: includeSourceSessionIds + ? await loadUsageLogSourceSessionIdsByIdentity( + fallbackLogs, + { userId: filters.userId, keyId: filters.keyId }, + { message: false, ledger: true } + ) + : undefined, + nextCursor: ledgerNextCursor, + hasMore: ledgerHasMore, + }; } interface UsageLogSlimFilters { @@ -776,7 +946,7 @@ function buildKeyLedgerConditions( const trimmedSessionId = filters.sessionId?.trim(); if (trimmedSessionId) { - conditions.push(eq(ledgerSessionIdentity, trimmedSessionId)); + conditions.push(buildLedgerSessionIdCondition(trimmedSessionId)); } if (filters.startTime) { @@ -1023,6 +1193,7 @@ function mapUsageLogRowFromMessageResult(row: { createdAt: Date | null; sessionId: string | null; sourceSessionId: string | null; + sourceSessionIds?: string[]; requestSequence: number | null; userName: string; keyName: string; @@ -1079,6 +1250,7 @@ function mapUsageLogRowFromMessageResult(row: { return { ...row, + sourceSessionIds: row.sourceSessionIds ? [...new Set(row.sourceSessionIds)] : undefined, requestSequence: row.requestSequence ?? null, totalTokens: totalRowTokens, costUsd: row.costUsd?.toString() ?? null, @@ -1098,6 +1270,7 @@ function mapUsageLogRowFromLedgerResult(row: { createdAt: Date | null; sessionId: string | null; sourceSessionId: string | null; + sourceSessionIds?: string[]; userId: number; userName: string | null; key: string; @@ -1138,6 +1311,7 @@ function mapUsageLogRowFromLedgerResult(row: { createdAt: row.createdAt, sessionId: row.sessionId, sourceSessionId: row.sourceSessionId, + sourceSessionIds: row.sourceSessionIds ? [...new Set(row.sourceSessionIds)] : undefined, requestSequence: null, userName: row.userName ?? `User #${row.userId}`, keyName: row.keyName ?? row.key, @@ -1184,7 +1358,7 @@ function mapUsageLogRowFromLedgerResult(row: { export async function findReadonlyUsageLogsBatchForKey( filters: Omit & { keyString: string } ): Promise { - const { keyString, cursor, limit = 50 } = filters; + const { keyString, cursor, limit = 50, includeSourceSessionIds = true } = filters; const safeLimit = Math.min(100, Math.max(1, limit)); const fetchLimit = safeLimit + 1; @@ -1310,8 +1484,16 @@ export async function findReadonlyUsageLogsBatchForKey( "findReadonlyUsageLogsBatchForKey" ); + const logs = pageRows.map(({ createdAtRaw: _createdAtRaw, ...log }) => log); return { - logs: pageRows.map(({ createdAtRaw: _createdAtRaw, ...log }) => log), + logs, + sourceSessionIdsByIdentity: includeSourceSessionIds + ? await loadUsageLogSourceSessionIdsByIdentity( + logs, + { keyString }, + { message: true, ledger: true } + ) + : undefined, nextCursor, hasMore, }; @@ -1391,7 +1573,10 @@ export async function getDistinctEndpointsForKey(keyString: string): Promise { +export async function findUsageLogsWithDetails( + filters: UsageLogFilters, + options: { includeSourceSessionIds?: boolean } = {} +): Promise { const { userId, keyId, providerId, page = 1, pageSize = 50 } = filters; const safePage = page > 0 ? page : 1; @@ -1559,7 +1744,14 @@ export async function findUsageLogsWithDetails(filters: UsageLogFilters): Promis }); return { - logs, + logs: + options.includeSourceSessionIds === false + ? logs + : await hydrateUsageLogSourceSessionIds( + logs, + { userId: filters.userId, keyId: filters.keyId }, + { message: true, ledger: false } + ), total, summary: { totalRequests, @@ -1632,6 +1824,11 @@ export interface UsageLogSessionIdSuggestionFilters { limit?: number; } +interface UsageLogSessionIdSuggestionRow { + sessionId: string | null; + firstSeen: Date | null; +} + export async function findUsageLogSessionIdSuggestions( filters: UsageLogSessionIdSuggestionFilters ): Promise { @@ -1641,45 +1838,122 @@ export async function findUsageLogSessionIdSuggestions( if (!trimmedTerm) return []; const pattern = `${escapeLike(trimmedTerm)}%`; - const conditions = [ - isNull(messageRequest.deletedAt), - EXCLUDE_WARMUP_CONDITION, - sql`${messageSessionIdentity} IS NOT NULL`, - sql`length(${messageSessionIdentity}) > 0`, - sql`${messageSessionIdentity} LIKE ${pattern} ESCAPE '\\'`, - ]; + const ledgerOnly = await isLedgerOnlyMode(); + let canonicalResults: UsageLogSessionIdSuggestionRow[]; + let physicalResults: UsageLogSessionIdSuggestionRow[]; - if (userId !== undefined) { - conditions.push(eq(messageRequest.userId, userId)); - } + if (ledgerOnly) { + const sharedConditions = [isNull(usageLedger.blockedBy)]; - if (keyId !== undefined) { - conditions.push(eq(keysTable.id, keyId)); - } + if (userId !== undefined) { + sharedConditions.push(eq(usageLedger.userId, userId)); + } - if (providerId !== undefined) { - conditions.push(eq(messageRequest.providerId, providerId)); - } + if (keyId !== undefined) { + sharedConditions.push(eq(keysTable.id, keyId)); + } - const baseQuery = db - .select({ - sessionId: messageSessionIdentity, - firstSeen: sql`min(${messageRequest.createdAt})`, - }) - .from(messageRequest); + if (providerId !== undefined) { + sharedConditions.push(eq(usageLedger.finalProviderId, providerId)); + } - const query = - keyId !== undefined - ? baseQuery.innerJoin(keysTable, eq(messageRequest.key, keysTable.key)) - : baseQuery; + const queryCandidates = async ( + candidate: SQL | typeof usageLedger.sessionId + ) => { + const baseQuery = db + .select({ + sessionId: candidate, + firstSeen: sql`max(${usageLedger.createdAt})`, + }) + .from(usageLedger); + const query = + keyId !== undefined + ? baseQuery.innerJoin(keysTable, eq(usageLedger.key, keysTable.key)) + : baseQuery; + + return query + .where( + and( + ...sharedConditions, + sql`${candidate} IS NOT NULL`, + sql`length(${candidate}) > 0`, + candidate === usageLedger.sessionId + ? sql`${candidate} NOT LIKE 'pfx:%' AND ${candidate} NOT LIKE 'sid:%'` + : sql`true`, + sql`${candidate} LIKE ${pattern} ESCAPE '\\'` + ) + ) + .groupBy(candidate) + .orderBy(desc(sql`max(${usageLedger.createdAt})`)) + .limit(limit); + }; - const results = await query - .where(and(...conditions)) - .groupBy(messageSessionIdentity) - .orderBy(desc(sql`min(${messageRequest.createdAt})`)) - .limit(limit); + [canonicalResults, physicalResults] = await Promise.all([ + queryCandidates(ledgerSessionIdentity), + queryCandidates(usageLedger.sessionId), + ]); + } else { + const sharedConditions = [isNull(messageRequest.deletedAt), EXCLUDE_WARMUP_CONDITION]; - return results.map((r) => r.sessionId).filter((id): id is string => Boolean(id)); + if (userId !== undefined) { + sharedConditions.push(eq(messageRequest.userId, userId)); + } + + if (keyId !== undefined) { + sharedConditions.push(eq(keysTable.id, keyId)); + } + + if (providerId !== undefined) { + sharedConditions.push(eq(messageRequest.providerId, providerId)); + } + + const queryCandidates = async ( + candidate: SQL | typeof messageRequest.sessionId + ) => { + const baseQuery = db + .select({ + sessionId: candidate, + firstSeen: sql`max(${messageRequest.createdAt})`, + }) + .from(messageRequest); + const query = + keyId !== undefined + ? baseQuery.innerJoin(keysTable, eq(messageRequest.key, keysTable.key)) + : baseQuery; + + return query + .where( + and( + ...sharedConditions, + sql`${candidate} IS NOT NULL`, + sql`length(${candidate}) > 0`, + candidate === messageRequest.sessionId + ? sql`${candidate} NOT LIKE 'pfx:%' AND ${candidate} NOT LIKE 'sid:%'` + : sql`true`, + sql`${candidate} LIKE ${pattern} ESCAPE '\\'` + ) + ) + .groupBy(candidate) + .orderBy(desc(sql`max(${messageRequest.createdAt})`)) + .limit(limit); + }; + + [canonicalResults, physicalResults] = await Promise.all([ + queryCandidates(messageSessionIdentity), + queryCandidates(messageRequest.sessionId), + ]); + } + + const bySessionId = new Map(); + for (const row of [...canonicalResults, ...physicalResults]) { + if (!row.sessionId || !row.firstSeen) continue; + const current = bySessionId.get(row.sessionId); + if (!current || row.firstSeen > current) bySessionId.set(row.sessionId, row.firstSeen); + } + return [...bySessionId.entries()] + .sort((a, b) => b[1].getTime() - a[1].getTime()) + .slice(0, limit) + .map(([sessionId]) => sessionId); } /** @@ -1779,7 +2053,7 @@ export async function findUsageLogsStats( const trimmedSessionId = filters.sessionId?.trim(); if (trimmedSessionId) { - conditions.push(eq(ledgerSessionIdentity, trimmedSessionId)); + conditions.push(buildLedgerSessionIdCondition(trimmedSessionId)); } if (filters.startTime !== undefined) { diff --git a/tests/api/v1/usage-logs/usage-logs.test.ts b/tests/api/v1/usage-logs/usage-logs.test.ts index 76054def2..e13cfae98 100644 --- a/tests/api/v1/usage-logs/usage-logs.test.ts +++ b/tests/api/v1/usage-logs/usage-logs.test.ts @@ -173,6 +173,56 @@ describe("v1 usage log endpoints", () => { expect(getUsageLogsBatchMock).not.toHaveBeenCalled(); }); + test("preserves source session identity mappings in list responses", async () => { + getUsageLogsMock.mockResolvedValueOnce({ + ok: true, + data: { + logs: [{ id: 1, model: "claude" }], + total: 1, + page: 1, + pageSize: 20, + sourceSessionIdsByIdentity: { + "offset:identity": ["offset-source-a", "offset-source-b"], + }, + }, + }); + getUsageLogsBatchMock.mockResolvedValueOnce({ + ok: true, + data: { + logs: [{ id: 2, model: "codex" }], + nextCursor: null, + hasMore: false, + sourceSessionIdsByIdentity: { + "cursor:identity": ["cursor-source-a", "cursor-source-b"], + }, + }, + }); + + const offset = await callV1Route({ + method: "GET", + pathname: "/api/v1/usage-logs?page=1&pageSize=20", + headers, + }); + expect(offset.response.status).toBe(200); + expect(offset.json).toMatchObject({ + sourceSessionIdsByIdentity: { + "offset:identity": ["offset-source-a", "offset-source-b"], + }, + }); + + const cursor = await callV1Route({ + method: "GET", + pathname: "/api/v1/usage-logs?limit=15", + headers, + }); + expect(cursor.response.status).toBe(200); + expect(cursor.json).toMatchObject({ + sourceSessionIdsByIdentity: { + "cursor:identity": ["cursor-source-a", "cursor-source-b"], + }, + }); + }); + test("reads stats filters lists and session suggestions", async () => { const stats = await callV1Route({ method: "GET", diff --git a/tests/unit/actions/active-sessions-monitoring.test.ts b/tests/unit/actions/active-sessions-monitoring.test.ts new file mode 100644 index 000000000..8ea721715 --- /dev/null +++ b/tests/unit/actions/active-sessions-monitoring.test.ts @@ -0,0 +1,160 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +const getSessionMock = vi.fn(); +const getActiveSessionsCacheMock = vi.fn(); +const setActiveSessionsCacheMock = vi.fn(); +const getObservedActiveSessionsMock = vi.fn(); +const getObservedConcurrentCountBatchMock = vi.fn(); +const getAllSessionIdsMock = vi.fn(); +const aggregateMultipleSessionStatsMock = vi.fn(); + +vi.mock("@/lib/auth", () => ({ getSession: getSessionMock })); +vi.mock("@/lib/cache/session-cache", () => ({ + getActiveSessionsCache: getActiveSessionsCacheMock, + setActiveSessionsCache: setActiveSessionsCacheMock, + getSessionDetailsCache: vi.fn(() => null), + setSessionDetailsCache: vi.fn(), +})); +vi.mock("@/lib/logger", () => ({ + logger: { + trace: vi.fn(), + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); +vi.mock("@/lib/session-manager", () => ({ + SessionManager: { + getAllSessionIds: getAllSessionIdsMock, + }, +})); +vi.mock("@/lib/session-tracker", () => ({ + SessionTracker: { + getObservedActiveSessions: getObservedActiveSessionsMock, + getObservedConcurrentCountBatch: getObservedConcurrentCountBatchMock, + }, +})); +vi.mock("@/repository/message", () => ({ + aggregateMultipleSessionStats: aggregateMultipleSessionStatsMock, +})); + +const SESSION_STATS = { + sessionId: "pfx:scope:fingerprint", + sessionIdentityKind: "prefix_affinity" as const, + sessionFingerprint: "fingerprint", + requestCount: 2, + totalCostUsd: "0.01", + totalInputTokens: 10, + totalOutputTokens: 5, + totalCacheCreationTokens: 0, + totalCacheReadTokens: 0, + totalDurationMs: 100, + firstRequestAt: new Date("2026-08-02T00:00:00.000Z"), + lastRequestAt: new Date("2026-08-02T00:01:00.000Z"), + providers: [], + models: ["model"], + userName: "user", + userId: 1, + keyName: "key", + keyId: 1, + userAgent: "client", + apiType: "chat", + cacheTtlApplied: null, +}; + +const NOW = new Date("2026-08-02T00:02:00.000Z"); + +describe("getAllSessions monitoring status", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(NOW); + vi.clearAllMocks(); + getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } }); + getActiveSessionsCacheMock.mockReturnValue(null); + getObservedActiveSessionsMock.mockResolvedValue([SESSION_STATS.sessionId]); + getAllSessionIdsMock.mockResolvedValue([]); + aggregateMultipleSessionStatsMock.mockResolvedValue([SESSION_STATS]); + getObservedConcurrentCountBatchMock.mockResolvedValue(new Map([[SESSION_STATS.sessionId, 1]])); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test("marks an observed prefix session as in progress", async () => { + const { getAllSessions } = await import("@/actions/active-sessions"); + + const result = await getAllSessions(1, 1, 20); + + expect(result).toEqual( + expect.objectContaining({ + ok: true, + data: expect.objectContaining({ + active: [ + expect.objectContaining({ + sessionId: SESSION_STATS.sessionId, + status: "in_progress", + concurrentCount: 1, + }), + ], + totalActive: 1, + }), + }) + ); + expect(getObservedConcurrentCountBatchMock).toHaveBeenCalledWith([SESSION_STATS.sessionId]); + }); + + test("keeps an in-progress session active when its last request is older than five minutes", async () => { + aggregateMultipleSessionStatsMock.mockResolvedValue([ + { + ...SESSION_STATS, + lastRequestAt: new Date("2026-08-01T23:00:00.000Z"), + }, + ]); + + const { getAllSessions } = await import("@/actions/active-sessions"); + const result = await getAllSessions(1, 1, 20); + + expect(result).toEqual( + expect.objectContaining({ + ok: true, + data: expect.objectContaining({ + active: [ + expect.objectContaining({ + sessionId: SESSION_STATS.sessionId, + status: "in_progress", + concurrentCount: 1, + }), + ], + inactive: [], + totalActive: 1, + totalInactive: 0, + }), + }) + ); + }); + + test("keeps a cached in-progress session in the active page", async () => { + getActiveSessionsCacheMock.mockReturnValue([ + { + ...SESSION_STATS, + lastRequestAt: new Date("2026-08-01T23:00:00.000Z"), + }, + ]); + + const { getAllSessions } = await import("@/actions/active-sessions"); + const result = await getAllSessions(1, 1, 20); + + expect(result).toEqual( + expect.objectContaining({ + ok: true, + data: expect.objectContaining({ + active: [expect.objectContaining({ status: "in_progress", concurrentCount: 1 })], + inactive: [], + }), + }) + ); + expect(aggregateMultipleSessionStatsMock).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/unit/actions/usage-logs-export-retry-count.test.ts b/tests/unit/actions/usage-logs-export-retry-count.test.ts index 0c97c2587..5aa479632 100644 --- a/tests/unit/actions/usage-logs-export-retry-count.test.ts +++ b/tests/unit/actions/usage-logs-export-retry-count.test.ts @@ -265,6 +265,10 @@ describe("Usage logs CSV export retryCount", () => { const result = await exportUsageLogs({}); expect(result.ok).toBe(true); + expect(findUsageLogsWithDetailsMock).toHaveBeenCalledWith( + expect.objectContaining({ page: 1, pageSize: 1 }), + { includeSourceSessionIds: false } + ); const csv = result.data; const csvNoBom = csv.replace(/^\uFEFF/, ""); const lines = csvNoBom @@ -311,6 +315,14 @@ describe("Usage logs CSV export retryCount", () => { expect(result.ok).toBe(true); expect(findUsageLogsBatchMock).toHaveBeenCalledTimes(2); + expect(findUsageLogsBatchMock).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ includeSourceSessionIds: false }) + ); + expect(findUsageLogsBatchMock).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ includeSourceSessionIds: false }) + ); const csvNoBom = result.data.replace(/^\uFEFF/, ""); const lines = csvNoBom diff --git a/tests/unit/deploy-dockerfile-contract.test.ts b/tests/unit/deploy-dockerfile-contract.test.ts index f63bd405c..8309d85a5 100644 --- a/tests/unit/deploy-dockerfile-contract.test.ts +++ b/tests/unit/deploy-dockerfile-contract.test.ts @@ -3,6 +3,21 @@ import { resolve } from "node:path"; import { describe, expect, it } from "vitest"; describe("deploy/Dockerfile runtime contract", () => { + it("builds with real Node and the repository Bun version", () => { + const dockerfile = readFileSync(resolve(process.cwd(), "deploy/Dockerfile"), "utf8"); + const packageJson = JSON.parse( + readFileSync(resolve(process.cwd(), "package.json"), "utf8") + ) as { engines: { node: string } }; + const bunVersion = readFileSync(resolve(process.cwd(), ".bun-version"), "utf8").trim(); + + expect(packageJson.engines.node).toBe(">=22.19.0"); + expect(dockerfile).toContain( + `FROM --platform=$BUILDPLATFORM oven/bun:${bunVersion}-debian AS bun-runtime` + ); + expect(dockerfile).toContain("FROM --platform=$BUILDPLATFORM node:trixie-slim AS build-base"); + expect(dockerfile).toContain("COPY --from=bun-runtime /usr/local/bin/bun /usr/local/bin/bun"); + }); + it("runs as node with writable, environment-redacted diagnostic reports", () => { const dockerfile = readFileSync(resolve(process.cwd(), "deploy/Dockerfile"), "utf8"); const reportsDirectory = "RUN mkdir -p /app/reports && chown node:node /app/reports"; diff --git a/tests/unit/drizzle/session-identity-indexes.test.ts b/tests/unit/drizzle/session-identity-indexes.test.ts index 481687c00..5d5841191 100644 --- a/tests/unit/drizzle/session-identity-indexes.test.ts +++ b/tests/unit/drizzle/session-identity-indexes.test.ts @@ -33,4 +33,18 @@ describe("Session identity query indexes", () => { expect(index?.config.where).toBeDefined(); expect(compileSql(index?.config.where as SQL)).toContain(predicate); }); + + test("usage_ledger has an unfiltered identity index for grouped source-ID hydration", () => { + const index = getTableConfig(usageLedger).indexes.find( + (entry) => entry.config.name === "idx_usage_ledger_session_identity" + ); + expect(index).toBeDefined(); + + const expression = index?.config.columns[0]; + expect(expression).toBeDefined(); + expect(compileSql(expression as SQL)).toContain("coalesce"); + expect(compileSql(expression as SQL)).toContain("session_identity"); + expect(compileSql(expression as SQL)).toContain("session_id"); + expect(index?.config.where).toBeUndefined(); + }); }); diff --git a/tests/unit/lib/session-replay-index-preflight.test.ts b/tests/unit/lib/session-replay-index-preflight.test.ts index aaae2f5d8..9e24e02be 100644 --- a/tests/unit/lib/session-replay-index-preflight.test.ts +++ b/tests/unit/lib/session-replay-index-preflight.test.ts @@ -48,6 +48,23 @@ function createFakeExecutor(initial: Record = {}) { describe("0116 concurrent index preflight", () => { const spec = SESSION_REPLAY_INDEX_SPECS[0]; + const hydrationSpec = SESSION_REPLAY_INDEX_SPECS.find( + (candidate) => candidate.canonicalName === "idx_usage_ledger_session_identity" + ); + + test("builds the unfiltered ledger identity index concurrently", async () => { + if (!hydrationSpec) throw new Error("missing 0117 identity index spec"); + const { executor, execute } = createFakeExecutor(); + + await runSessionReplayIndexPreflight(executor, [hydrationSpec]); + + const createStatement = execute.mock.calls + .map(([statement]) => statement) + .find((statement) => statement.startsWith("CREATE INDEX CONCURRENTLY")); + expect(createStatement).toContain(`"${hydrationSpec.temporaryName}"`); + expect(createStatement).toContain(hydrationSpec.definition); + expect(createStatement).not.toContain("WHERE"); + }); test("builds and validates a temporary index before replacing the canonical index", async () => { const { executor, execute, states } = createFakeExecutor({ @@ -158,6 +175,17 @@ describe("0116 concurrent index preflight", () => { }); describe("0116 migration orchestration", () => { + test("preflights the unfiltered ledger identity index before migration 0117", async () => { + const events: string[] = []; + await runSessionReplayMigrationPlan({ + baseTablesReady: true, + latestMigrationCreatedAt: 1785563419224, + migrate: async () => events.push("migrate"), + runIndexPreflight: async () => events.push("preflight"), + }); + + expect(events).toEqual(["preflight", "migrate", "preflight"]); + }); test("migrates a fresh database before installing concurrent indexes", async () => { const calls: string[] = []; @@ -192,7 +220,7 @@ describe("0116 migration orchestration", () => { expect(calls).toEqual(["indexes", "migrate", "indexes"]); }); - test("repairs indexes in postflight after migration 0116 was recorded", async () => { + test("preflights the new identity index after migration 0116 was recorded", async () => { const calls: string[] = []; await runSessionReplayMigrationPlan({ @@ -206,7 +234,7 @@ describe("0116 migration orchestration", () => { }, }); - expect(calls).toEqual(["migrate", "indexes"]); + expect(calls).toEqual(["indexes", "migrate", "indexes"]); }); test("fails the migration flow when concurrent index postflight fails", async () => { diff --git a/tests/unit/repository/activity-stream-replay.test.ts b/tests/unit/repository/activity-stream-replay.test.ts index 6b88f0de4..e783f1f73 100644 --- a/tests/unit/repository/activity-stream-replay.test.ts +++ b/tests/unit/repository/activity-stream-replay.test.ts @@ -5,13 +5,19 @@ const activeSessionIdsMock = vi.fn<() => Promise>(); vi.mock("@/lib/session-tracker", () => ({ SessionTracker: { - getActiveSessions: activeSessionIdsMock, + getObservedActiveSessions: activeSessionIdsMock, }, })); -function installDbBoundary(rows: readonly unknown[]) { +function installDbBoundary(rows: readonly unknown[] | readonly (readonly unknown[])[]) { const whereConditions: unknown[] = []; - const select = vi.fn(() => { + const limits: unknown[] = []; + let selectIndex = 0; + let distinctQuery: any = null; + const makeQuery = () => { + const selectedRows = Array.isArray(rows[0]) + ? ((rows as readonly (readonly unknown[])[])[selectIndex++] ?? []) + : (rows as readonly unknown[]); const query = { from: vi.fn(() => query), leftJoin: vi.fn(() => query), @@ -20,13 +26,32 @@ function installDbBoundary(rows: readonly unknown[]) { return query; }), orderBy: vi.fn(() => query), - limit: vi.fn(async () => rows), + limit: vi.fn(async (value: unknown) => { + limits.push(value); + return selectedRows; + }), + // biome-ignore lint/suspicious/noThenProperty: 模拟 Drizzle 可直接 await 的 query builder。 + then: (resolve: (value: readonly unknown[]) => unknown) => + Promise.resolve(selectedRows).then(resolve), + as: vi.fn(() => query), }; return query; + }; + const select = vi.fn(() => { + if (distinctQuery) { + const query = distinctQuery; + distinctQuery = null; + return query; + } + return makeQuery(); + }); + const selectDistinctOn = vi.fn(() => { + distinctQuery = makeQuery(); + return distinctQuery; }); - vi.doMock("@/drizzle/db", () => ({ db: { select } })); - return { whereConditions }; + vi.doMock("@/drizzle/db", () => ({ db: { select, selectDistinctOn } })); + return { whereConditions, limits, selectDistinctOn }; } function expectReplayExcluded(condition: unknown) { @@ -81,4 +106,55 @@ describe("activity stream Replay exclusion", () => { expectReplayExcluded(boundary.whereConditions[0]); }); + + it("matches observed Sessions only through the canonical identity", async () => { + activeSessionIdsMock.mockResolvedValueOnce(["pfx:scope:fingerprint"]); + const boundary = installDbBoundary([ + { ...REQUEST_ROW, sessionId: "pfx:scope:fingerprint", rowNum: 1 }, + ]); + const { findRecentActivityStream } = await import("@/repository/activity-stream"); + + await findRecentActivityStream(1); + + const condition = new PgDialect().sqlToQuery(boundary.whereConditions[0] as never); + const normalizedSql = condition.sql.toLowerCase(); + expect(normalizedSql).toContain( + 'coalesce("message_request"."session_identity", "message_request"."session_id") in' + ); + expect(normalizedSql).not.toContain('or "message_request"."session_id" in'); + expect(activeSessionIdsMock).toHaveBeenCalledOnce(); + }); + + it("does not exclude fallback rows through the physical session ID", async () => { + activeSessionIdsMock.mockResolvedValueOnce(["pfx:scope:fingerprint"]); + const boundary = installDbBoundary([ + [{ ...REQUEST_ROW, sessionId: "pfx:scope:fingerprint", rowNum: 1 }], + [], + ]); + const { findRecentActivityStream } = await import("@/repository/activity-stream"); + + await findRecentActivityStream(2); + + const condition = new PgDialect().sqlToQuery(boundary.whereConditions[1] as never); + expect(condition.sql.toLowerCase()).toContain("coalesce"); + expect(condition.sql).not.toContain('and "message_request"."session_id" not in'); + }); + + it("deduplicates canonical sessions in SQL before applying the result limit", async () => { + activeSessionIdsMock.mockResolvedValueOnce(["session-a", "session-b"]); + const boundary = installDbBoundary([ + [ + { ...REQUEST_ROW, id: 3, sessionId: "session-a", rowNum: 1 }, + { ...REQUEST_ROW, id: 1, sessionId: "session-b", rowNum: 1 }, + ], + ]); + const { findRecentActivityStream } = await import("@/repository/activity-stream"); + + const result = await findRecentActivityStream(1); + + expect(result).toHaveLength(1); + expect(result[0]?.id).toBe(3); + expect(boundary.selectDistinctOn).toHaveBeenCalledOnce(); + expect(boundary.limits).toEqual([1]); + }); }); diff --git a/tests/unit/repository/usage-logs-replay-projection.test.ts b/tests/unit/repository/usage-logs-replay-projection.test.ts index 88b72d4ad..d4d8161c8 100644 --- a/tests/unit/repository/usage-logs-replay-projection.test.ts +++ b/tests/unit/repository/usage-logs-replay-projection.test.ts @@ -8,6 +8,7 @@ function createThenableQuery(result: T, whereArgs?: unknown[]) { query.innerJoin = vi.fn(() => query); query.leftJoin = vi.fn(() => query); query.orderBy = vi.fn(() => query); + query.groupBy = vi.fn(() => query); query.limit = vi.fn(() => query); query.offset = vi.fn(() => query); query.where = vi.fn((condition: unknown) => { @@ -77,7 +78,10 @@ function makeReplayRow(overrides: Record = {}) { describe("findUsageLogsBatch Replay projection", () => { test("projects Replay provenance from message_request", async () => { vi.resetModules(); - const selectMock = vi.fn(() => createThenableQuery([makeReplayRow()])); + const selectMock = vi + .fn() + .mockImplementationOnce(() => createThenableQuery([makeReplayRow()])) + .mockImplementationOnce(() => createThenableQuery([])); vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); vi.doMock("@/lib/ledger-fallback", () => ({ @@ -104,7 +108,8 @@ describe("findUsageLogsBatch Replay projection", () => { const selectMock = vi .fn() .mockImplementationOnce(() => createThenableQuery([])) - .mockImplementationOnce(() => createThenableQuery([ledgerRow])); + .mockImplementationOnce(() => createThenableQuery([ledgerRow])) + .mockImplementationOnce(() => createThenableQuery([])); vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); vi.doMock("@/lib/ledger-fallback", () => ({ diff --git a/tests/unit/repository/usage-logs-sessionid-filter.test.ts b/tests/unit/repository/usage-logs-sessionid-filter.test.ts index 8ced8b065..ca267d53c 100644 --- a/tests/unit/repository/usage-logs-sessionid-filter.test.ts +++ b/tests/unit/repository/usage-logs-sessionid-filter.test.ts @@ -1,4 +1,8 @@ import { describe, expect, test, vi } from "vitest"; +import { PgDialect } from "drizzle-orm/pg-core"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { buildUsageLogConditions } from "@/repository/_shared/usage-log-filters"; function sqlToString(sqlObj: unknown): string { const visited = new Set(); @@ -56,6 +60,11 @@ function createThenableQuery(result: T, whereArgs?: unknown[]) { } describe("Usage logs sessionId filter", () => { + test("readonly key hydration queries both backing stores even when one page is empty", () => { + const source = readFileSync(resolve(process.cwd(), "src/repository/usage-logs.ts"), "utf8"); + expect(source).toContain("{ message: true, ledger: true }"); + }); + test("findUsageLogsBatch: sessionId 为空/空白不应追加条件", async () => { vi.resetModules(); @@ -115,6 +124,43 @@ describe("Usage logs sessionId filter", () => { expect(ledgerWhereSql).not.toContain(" abc "); }); + test("findUsageLogsBatch: sessionId should match canonical and physical identities", async () => { + vi.resetModules(); + + const whereArgs: unknown[] = []; + const selectMock = vi.fn(() => createThenableQuery([], whereArgs)); + + vi.doMock("@/drizzle/db", () => ({ + db: { + select: selectMock, + execute: vi.fn(async () => ({ count: 0 })), + }, + })); + vi.doMock("@/lib/ledger-fallback", () => ({ + isLedgerOnlyMode: vi.fn(async () => false), + })); + + const { findUsageLogsBatch } = await import("@/repository/usage-logs"); + await findUsageLogsBatch({ sessionId: "client-session" }); + + const primaryWhereSql = new PgDialect().sqlToQuery(whereArgs[0] as never).sql.toLowerCase(); + expect(primaryWhereSql).toContain( + 'coalesce("message_request"."session_identity", "message_request"."session_id") =' + ); + expect(primaryWhereSql).toContain('or "message_request"."session_id" ='); + }); + + test.each(["pfx:scope:fingerprint", "sid:physical-id"])( + "reserved session identities do not alias raw physical session IDs: %s", + (sessionId) => { + const [condition] = buildUsageLogConditions({ sessionId }); + const sql = new PgDialect().sqlToQuery(condition).sql.toLowerCase(); + + expect(sql).toContain("coalesce"); + expect(sql).not.toContain('"message_request"."session_id" ='); + } + ); + test("findUsageLogsBatch: hasMore 为 true 时缺失 createdAtRaw 应直接报错,避免静默截断", async () => { vi.resetModules(); @@ -125,6 +171,7 @@ describe("Usage logs sessionId filter", () => { createdAt: new Date("2026-03-21T00:00:00Z"), createdAtRaw: null, sessionId: null, + sourceSessionIds: [], requestSequence: null, userName: "u", keyName: "k", @@ -232,6 +279,148 @@ describe("Usage logs sessionId filter", () => { expect(query?.limit).toHaveBeenCalledWith(101); }); + test("findUsageLogsBatch: returns distinct source IDs once per page identity", async () => { + vi.resetModules(); + + const projections: unknown[] = []; + const selectMock = vi.fn((projection: unknown) => { + projections.push(projection); + const call = projections.length; + if (call === 1) { + return createThenableQuery([ + { + id: 101, + createdAt: new Date("2026-03-21T00:00:00Z"), + createdAtRaw: "2026-03-21T00:00:00.000000Z", + sessionId: "pfx:scope:fingerprint", + sourceSessionId: "client-old", + requestSequence: 1, + userName: "u", + keyName: "k", + providerName: "p", + model: "m", + originalModel: "m", + actualResponseModel: null, + endpoint: "/v1/messages", + statusCode: 200, + inputTokens: 1, + outputTokens: 1, + cacheCreationInputTokens: 0, + cacheReadInputTokens: 0, + cacheCreation5mInputTokens: 0, + cacheCreation1hInputTokens: 0, + cacheTtlApplied: null, + costUsd: "0.01", + costMultiplier: null, + groupCostMultiplier: null, + costBreakdown: null, + hedgeLosers: null, + durationMs: 10, + tfftMs: 5, + firstByteMs: 5, + errorMessage: null, + providerChain: null, + routingTrace: null, + blockedBy: null, + blockedReason: null, + isReplay: false, + replaySourceRequestId: null, + userAgent: null, + clientIp: null, + messagesCount: null, + context1mApplied: null, + swapCacheTtlApplied: null, + specialSettings: null, + }, + ]); + } + if (call === 2) { + return createThenableQuery([ + { + sessionId: "pfx:scope:fingerprint", + sourceSessionIds: ["client-new", "client-old"], + }, + ]); + } + return createThenableQuery([]); + }); + + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + vi.doMock("@/lib/ledger-fallback", () => ({ + isLedgerOnlyMode: vi.fn(async () => false), + })); + + const { findUsageLogsBatch } = await import("@/repository/usage-logs"); + const result = await findUsageLogsBatch({ cursor: { createdAt: "2026-03-22", id: 102 } }); + + expect(result.logs[0]?.sourceSessionIds).toBeUndefined(); + expect(result.sourceSessionIdsByIdentity).toEqual({ + "pfx:scope:fingerprint": ["client-new", "client-old"], + }); + expect(sqlToString(projections[0]).toLowerCase()).not.toContain("array_agg"); + expect(selectMock).toHaveBeenCalledTimes(2); + }); + + test("findUsageLogsBatch: skips source ID hydration for export callers", async () => { + vi.resetModules(); + + const selectMock = vi.fn(() => + createThenableQuery([ + { + id: 101, + createdAt: new Date("2026-03-21T00:00:00Z"), + createdAtRaw: "2026-03-21T00:00:00.000000Z", + sessionId: "pfx:scope:fingerprint", + sourceSessionId: "client-old", + requestSequence: 1, + userName: "u", + keyName: "k", + providerName: "p", + model: "m", + originalModel: "m", + actualResponseModel: null, + endpoint: "/v1/messages", + statusCode: 200, + inputTokens: 1, + outputTokens: 1, + cacheCreationInputTokens: 0, + cacheReadInputTokens: 0, + cacheCreation5mInputTokens: 0, + cacheCreation1hInputTokens: 0, + cacheTtlApplied: null, + costUsd: "0.01", + costMultiplier: null, + groupCostMultiplier: null, + costBreakdown: null, + hedgeLosers: null, + durationMs: 10, + tfftMs: 5, + firstByteMs: 5, + errorMessage: null, + providerChain: null, + routingTrace: null, + blockedBy: null, + blockedReason: null, + isReplay: false, + replaySourceRequestId: null, + userAgent: null, + clientIp: null, + messagesCount: null, + context1mApplied: null, + swapCacheTtlApplied: null, + specialSettings: null, + }, + ]) + ); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogsBatch } = await import("@/repository/usage-logs"); + const result = await findUsageLogsBatch({ includeSourceSessionIds: false }); + + expect(result.logs[0]?.sourceSessionIds).toBeUndefined(); + expect(selectMock).toHaveBeenCalledTimes(1); + }); + test("findUsageLogsForKeyBatch: hasMore 为 true 时缺失 createdAtRaw 应直接报错,避免静默截断", async () => { vi.resetModules(); @@ -503,4 +692,60 @@ describe("Usage logs sessionId filter", () => { expect(whereSql).toContain("abc"); expect(whereSql).not.toContain(" abc "); }); + + test("ledger filters match canonical and physical session identities", async () => { + vi.resetModules(); + + const whereArgs: unknown[] = []; + const selectMock = vi.fn(() => createThenableQuery([], whereArgs)); + + vi.doMock("@/drizzle/db", () => ({ + db: { + select: selectMock, + execute: vi.fn(async () => ({ count: 0 })), + }, + })); + vi.doMock("@/lib/ledger-fallback", () => ({ + isLedgerOnlyMode: vi.fn(async () => true), + })); + + const { findUsageLogsForKeyBatch, findUsageLogsStats } = await import( + "@/repository/usage-logs" + ); + await findUsageLogsForKeyBatch({ keyString: "key", sessionId: "client-session" }); + await findUsageLogsStats({ sessionId: "client-session" }); + + const ledgerSql = new PgDialect().sqlToQuery(whereArgs[1] as never).sql.toLowerCase(); + expect(ledgerSql).toContain( + '(coalesce("usage_ledger"."session_identity", "usage_ledger"."session_id") =' + ); + expect(ledgerSql).toContain('or "usage_ledger"."session_id" ='); + }); + + test.each(["pfx:scope:fingerprint", "sid:physical-id"])( + "ledger reserved session identities do not alias raw physical session IDs: %s", + async (sessionId) => { + vi.resetModules(); + + const whereArgs: unknown[] = []; + const selectMock = vi.fn(() => createThenableQuery([], whereArgs)); + + vi.doMock("@/drizzle/db", () => ({ + db: { + select: selectMock, + execute: vi.fn(async () => ({ count: 0 })), + }, + })); + vi.doMock("@/lib/ledger-fallback", () => ({ + isLedgerOnlyMode: vi.fn(async () => true), + })); + + const { findUsageLogsBatch } = await import("@/repository/usage-logs"); + await findUsageLogsBatch({ sessionId }); + + const ledgerWhereSql = new PgDialect().sqlToQuery(whereArgs[1] as never).sql.toLowerCase(); + expect(ledgerWhereSql).toContain("coalesce"); + expect(ledgerWhereSql).not.toContain('"usage_ledger"."session_id" ='); + } + ); }); diff --git a/tests/unit/repository/usage-logs-sessionid-suggestions.test.ts b/tests/unit/repository/usage-logs-sessionid-suggestions.test.ts index 0114be6cc..c4ccfd2b4 100644 --- a/tests/unit/repository/usage-logs-sessionid-suggestions.test.ts +++ b/tests/unit/repository/usage-logs-sessionid-suggestions.test.ts @@ -1,4 +1,14 @@ -import { describe, expect, test, vi } from "vitest"; +import { getTableName } from "drizzle-orm"; +import { PgDialect } from "drizzle-orm/pg-core"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { beforeEach, describe, expect, test, vi } from "vitest"; + +const isLedgerOnlyModeMock = vi.hoisted(() => vi.fn<() => Promise>()); + +vi.mock("@/lib/ledger-fallback", () => ({ + isLedgerOnlyMode: isLedgerOnlyModeMock, +})); function sqlToString(sqlObj: unknown): string { const visited = new Set(); @@ -36,6 +46,7 @@ function sqlToString(sqlObj: unknown): string { function createThenableQuery( result: T, opts?: { + fromArgs?: unknown[]; whereArgs?: unknown[]; groupByArgs?: unknown[]; orderByArgs?: unknown[]; @@ -44,7 +55,10 @@ function createThenableQuery( ) { const query: any = Promise.resolve(result); - query.from = vi.fn(() => query); + query.from = vi.fn((arg: unknown) => { + opts?.fromArgs?.push(arg); + return query; + }); query.innerJoin = vi.fn(() => query); query.leftJoin = vi.fn(() => query); query.where = vi.fn((arg: unknown) => { @@ -68,6 +82,34 @@ function createThenableQuery( } describe("Usage logs sessionId suggestions", () => { + test("reserved physical IDs are excluded from suggestions", () => { + const source = readFileSync(resolve(process.cwd(), "src/repository/usage-logs.ts"), "utf8"); + expect(source).toContain("NOT LIKE 'pfx:%'"); + expect(source).toContain("NOT LIKE 'sid:%'"); + }); + + test.each([false, true])("filters reserved physical IDs in %s storage", async (ledgerOnly) => { + vi.resetModules(); + isLedgerOnlyModeMock.mockResolvedValue(ledgerOnly); + const whereArgs: unknown[] = []; + const selectMock = vi.fn(() => createThenableQuery([], { whereArgs })); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); + await findUsageLogSessionIdSuggestions({ term: "s" }); + + const whereSql = whereArgs.map((condition) => + new PgDialect().sqlToQuery(condition as never).sql.toLowerCase() + ); + const physicalSql = whereSql.find((sql) => sql.includes('"session_id" not like')); + expect(physicalSql).toContain("\"session_id\" not like 'pfx:%'"); + expect(physicalSql).toContain("\"session_id\" not like 'sid:%'"); + }); + beforeEach(() => { + isLedgerOnlyModeMock.mockReset(); + isLedgerOnlyModeMock.mockResolvedValue(false); + }); + test("term 为空/空白:应直接返回空数组且不查询 DB", async () => { vi.resetModules(); @@ -83,7 +125,7 @@ describe("Usage logs sessionId suggestions", () => { expect(selectMock).not.toHaveBeenCalled(); }); - test("term 应 trim 并按 MIN(created_at) 倒序,limit 生效", async () => { + test("term 应 trim 并按最近 created_at 倒序,limit 生效", async () => { vi.resetModules(); const whereArgs: unknown[] = []; @@ -128,9 +170,120 @@ describe("Usage logs sessionId suggestions", () => { expect(orderByArgs.length).toBeGreaterThan(0); const orderSql = sqlToString(orderByArgs[0]).toLowerCase(); - expect(orderSql).toContain("min"); + expect(orderSql).toContain("max"); + + expect(limitArgs).toEqual([20, 20]); + }); + + test("returns only candidate identities that match the searched prefix", async () => { + vi.resetModules(); + + const selectMock = vi + .fn() + .mockImplementationOnce(() => createThenableQuery([])) + .mockImplementationOnce(() => + createThenableQuery([ + { + sessionId: "client-session", + firstSeen: new Date("2026-01-01T00:00:00Z"), + }, + ]) + ); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); + await expect(findUsageLogSessionIdSuggestions({ term: "client", limit: 20 })).resolves.toEqual([ + "client-session", + ]); + }); + + test("deduplicates canonical and physical candidates before applying the final limit", async () => { + vi.resetModules(); + + const selectMock = vi + .fn() + .mockImplementationOnce(() => + createThenableQuery([ + { sessionId: "session-shared", firstSeen: new Date("2026-01-03T00:00:00Z") }, + { sessionId: "session-canonical", firstSeen: new Date("2026-01-01T00:00:00Z") }, + ]) + ) + .mockImplementationOnce(() => + createThenableQuery([ + { sessionId: "session-shared", firstSeen: new Date("2026-01-02T00:00:00Z") }, + { sessionId: "session-physical", firstSeen: new Date("2026-01-02T12:00:00Z") }, + ]) + ); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); + await expect(findUsageLogSessionIdSuggestions({ term: "session-", limit: 2 })).resolves.toEqual( + ["session-shared", "session-physical"] + ); + }); - expect(limitArgs).toEqual([20]); + test("returns canonical and physical candidates from ledger-only storage", async () => { + vi.resetModules(); + isLedgerOnlyModeMock.mockResolvedValueOnce(true); + + const fromArgs: unknown[] = []; + const whereArgs: unknown[] = []; + const selectMock = vi + .fn() + .mockImplementationOnce(() => + createThenableQuery( + [{ sessionId: "pfx:scope:fingerprint", firstSeen: new Date("2026-01-03T00:00:00Z") }], + { fromArgs, whereArgs } + ) + ) + .mockImplementationOnce(() => + createThenableQuery( + [{ sessionId: "physical-client", firstSeen: new Date("2026-01-02T00:00:00Z") }], + { fromArgs, whereArgs } + ) + ); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); + await expect( + findUsageLogSessionIdSuggestions({ + term: "p", + userId: 1, + keyId: 2, + providerId: 3, + limit: 20, + }) + ).resolves.toEqual(["pfx:scope:fingerprint", "physical-client"]); + + expect(isLedgerOnlyModeMock).toHaveBeenCalledOnce(); + expect( + fromArgs.map((table) => getTableName(table as Parameters[0])) + ).toEqual(["usage_ledger", "usage_ledger"]); + expect(whereArgs).toHaveLength(2); + for (const condition of whereArgs) { + const whereSql = sqlToString(condition).toLowerCase(); + expect(whereSql).not.toContain("message_request"); + } + }); + + test("ignores candidates whose latest createdAt is NULL", async () => { + vi.resetModules(); + + const selectMock = vi + .fn() + .mockImplementationOnce(() => + createThenableQuery([ + { sessionId: "session-null", firstSeen: null }, + { sessionId: "session-valid", firstSeen: new Date("2026-01-02T00:00:00Z") }, + ]) + ) + .mockImplementationOnce(() => createThenableQuery([])); + vi.doMock("@/drizzle/db", () => ({ db: { select: selectMock } })); + + const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); + await expect( + findUsageLogSessionIdSuggestions({ term: "session-", limit: 20 }) + ).resolves.toEqual(["session-valid"]); }); test("term 含 %/_/\\\\:应按字面量前缀匹配(需转义)", async () => { @@ -169,7 +322,7 @@ describe("Usage logs sessionId suggestions", () => { const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); await findUsageLogSessionIdSuggestions({ term: "abc", limit: 500 }); - expect(limitArgs).toEqual([50]); + expect(limitArgs).toEqual([50, 50]); }); test("keyId 未提供时不应 innerJoin(keysTable)", async () => { @@ -184,6 +337,7 @@ describe("Usage logs sessionId suggestions", () => { const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); await findUsageLogSessionIdSuggestions({ term: "abc", limit: 20 }); + expect(selectMock).toHaveBeenCalledTimes(2); expect(query.innerJoin).not.toHaveBeenCalled(); }); @@ -199,6 +353,6 @@ describe("Usage logs sessionId suggestions", () => { const { findUsageLogSessionIdSuggestions } = await import("@/repository/usage-logs"); await findUsageLogSessionIdSuggestions({ term: "abc", keyId: 2, limit: 20 }); - expect(query.innerJoin).toHaveBeenCalledTimes(1); + expect(query.innerJoin).toHaveBeenCalledTimes(2); }); });