/** * Mock API Handler * 演示模式的 API 请求拦截和模拟响应 */ import { AxiosHeaders, type AxiosRequestConfig, type AxiosResponse } from 'axios' import { isDemoMode, DEMO_ACCOUNTS } from '@/config/demo' import { log } from '@/utils/logger' import { MOCK_ADMIN_USER, MOCK_NORMAL_USER, MOCK_LOGIN_RESPONSE_ADMIN, MOCK_LOGIN_RESPONSE_USER, MOCK_ADMIN_PROFILE, MOCK_USER_PROFILE, MOCK_DASHBOARD_STATS, MOCK_RECENT_REQUESTS, MOCK_PROVIDER_STATUS, MOCK_DAILY_STATS, MOCK_ALL_USERS, MOCK_USER_API_KEYS, MOCK_ADMIN_API_KEYS, MOCK_PROVIDERS, MOCK_GLOBAL_MODELS, MOCK_SYSTEM_CONFIGS, MOCK_MODULE_STATUSES, MOCK_API_FORMATS } from './data' // 当前登录用户的 token(用于判断角色) let currentUserToken: string | null = null // 模拟网络延迟 function delay(ms: number = 150): Promise { return new Promise(resolve => setTimeout(resolve, ms + Math.random() * 200)) } // 创建模拟响应 function createMockResponse(data: T, status: number = 200): AxiosResponse { return { data, status, statusText: status === 200 ? 'OK' : 'Error', headers: {}, config: { headers: new AxiosHeaders() } } } // 判断当前是否为管理员 function isCurrentUserAdmin(): boolean { return currentUserToken === 'demo-access-token-admin' } // 获取当前用户 function getCurrentUser() { return isCurrentUserAdmin() ? MOCK_ADMIN_USER : MOCK_NORMAL_USER } // 获取当前用户 Profile function getCurrentProfile() { return isCurrentUserAdmin() ? MOCK_ADMIN_PROFILE : MOCK_USER_PROFILE } // 检查管理员权限 function requireAdmin() { if (!isCurrentUserAdmin()) { throw { response: createMockResponse({ detail: '需要管理员权限' }, 403) } } } // Mock 公告数据 const MOCK_ANNOUNCEMENTS = [ { id: 'ann-001', title: '系统升级通知', content: '系统将于本周六凌晨 2:00-4:00 进行维护升级,届时服务将暂停访问。', type: 'maintenance', priority: 100, is_pinned: true, is_active: true, author: { id: 'demo-admin-uuid-0001', username: 'Demo Admin' }, created_at: '2024-12-01T00:00:00Z', updated_at: '2024-12-01T00:00:00Z', is_read: false }, { id: 'ann-002', title: '新模型上线:Claude Sonnet 4', content: 'Anthropic 最新模型 Claude Sonnet 4 已上线,支持更长上下文和更强推理能力。', type: 'info', priority: 50, is_pinned: false, is_active: true, author: { id: 'demo-admin-uuid-0001', username: 'Demo Admin' }, created_at: '2024-11-28T00:00:00Z', updated_at: '2024-11-28T00:00:00Z', is_read: true } ] // 生成模拟健康事件 // status: success(绿), failed(红), skipped(黄) // 无事件的时间段会显示为灰色 function generateHealthEvents( count: number, successRate: number, failRate: number, _skipRate: number, baseLatency: number, latencyVariance: number ) { const events = [] const now = Date.now() // 6小时内随机分布事件,留一些空白时段(灰色) const timeSpan = 6 * 60 * 60 * 1000 // skipRate 由 1 - successRate - failRate 隐含计算 for (let i = 0; i < count; i++) { const rand = Math.random() let status: string let statusCode: number if (rand < successRate) { status = 'success' statusCode = 200 } else if (rand < successRate + failRate) { status = 'failed' statusCode = [500, 502, 503, 429, 400][Math.floor(Math.random() * 5)] } else { status = 'skipped' statusCode = 0 } events.push({ timestamp: new Date(now - Math.random() * timeSpan).toISOString(), status, status_code: statusCode, latency_ms: Math.round(baseLatency + Math.random() * latencyVariance), error_type: status === 'failed' ? ['RateLimitError', 'TimeoutError', 'ServerError'][Math.floor(Math.random() * 3)] : undefined }) } // 按时间排序 return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()) } function generateHealthTimeline( healthyRate: number, warningRate: number, segments = 60 ) { return Array.from({ length: segments }, () => { const rand = Math.random() if (rand < healthyRate) return 'healthy' if (rand < healthyRate + warningRate) return 'warning' if (rand < 0.96) return 'unknown' return 'unhealthy' }) } function generateHealthTimelineDetails( timeline: string[], avgLatencyMs: number | null, avgFirstByteMs: number | null, avgTps: number | null, rangeStart = Date.now() - 6 * 60 * 60 * 1000, rangeEnd = Date.now() ) { const safeRange = Math.max(rangeEnd - rangeStart, 1) const interval = safeRange / Math.max(timeline.length, 1) return timeline.map((status, index) => { const totalAttempts = status === 'unknown' ? 0 : 3 + (index % 6) const successRate = status === 'healthy' ? 0.98 : status === 'warning' ? 0.84 : status === 'unhealthy' ? 0.42 : null const successCount = successRate == null ? 0 : Math.round(totalAttempts * successRate) const failedCount = successRate == null ? 0 : Math.max(totalAttempts - successCount, 0) const latencyFactor = status === 'warning' ? 1.25 : status === 'unhealthy' ? 1.7 : 1 return { segment_index: index, status, time_range_start: new Date(rangeStart + index * interval).toISOString(), time_range_end: new Date(rangeStart + (index + 1) * interval).toISOString(), total_attempts: totalAttempts, success_count: successCount, failed_count: failedCount, success_rate: successRate, avg_latency_ms: avgLatencyMs == null || totalAttempts === 0 ? null : Math.round(avgLatencyMs * latencyFactor), avg_first_byte_ms: avgFirstByteMs == null || totalAttempts === 0 ? null : Math.round(avgFirstByteMs * latencyFactor), avg_tps: avgTps == null || totalAttempts === 0 ? null : Number((avgTps / latencyFactor).toFixed(1)) } }) } function withHealthTimelineDetails(item: T) { const rangeStart = item.time_range_start ? new Date(item.time_range_start).getTime() : Date.now() - 6 * 60 * 60 * 1000 const rangeEnd = item.time_range_end ? new Date(item.time_range_end).getTime() : Date.now() return { ...item, timeline_details: generateHealthTimelineDetails( item.timeline || [], item.avg_latency_ms ?? null, item.avg_first_byte_ms ?? null, item.avg_tps ?? null, rangeStart, rangeEnd ) } } // Mock 端点健康数据 // 注意:success_rate 使用 0-1 之间的小数,前端会乘以 100 显示为百分比 // 事件的成功/失败/跳过比例必须与 success_rate 保持一致 // 覆盖所有 API 格式:claude, claude_cli, openai, openai_responses, gemini, gemini_cli const MOCK_ENDPOINT_STATUS = { generated_at: new Date().toISOString(), formats: [ { api_format: 'claude:messages', api_path: '/v1/messages', total_attempts: 2580, success_count: 2540, failed_count: 30, skipped_count: 10, success_rate: 0.984, avg_latency_ms: 920, avg_first_byte_ms: 148, avg_tps: 92.4, provider_count: 2, key_count: 4, last_event_at: new Date().toISOString(), // 98.4% 成功率:successRate=0.984, failRate=0.012, skipRate=0.004 events: generateHealthEvents(80, 0.984, 0.012, 0.004, 900, 500), timeline: generateHealthTimeline(0.9, 0.06, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'claude:messages', api_path: '/v1/messages', total_attempts: 1890, success_count: 1780, failed_count: 85, skipped_count: 25, success_rate: 0.942, avg_latency_ms: 1280, avg_first_byte_ms: 232, avg_tps: 71.5, provider_count: 5, key_count: 9, last_event_at: new Date().toISOString(), // 94.2% 成功率:successRate=0.942, failRate=0.045, skipRate=0.013 events: generateHealthEvents(120, 0.942, 0.045, 0.013, 1200, 800), timeline: generateHealthTimeline(0.78, 0.14, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'gemini:generate_content', api_path: '/v1beta/models', total_attempts: 890, success_count: 890, failed_count: 0, skipped_count: 0, success_rate: 1.0, avg_latency_ms: 410, avg_first_byte_ms: 92, avg_tps: 118.2, provider_count: 3, key_count: 3, last_event_at: new Date().toISOString(), // 100% 成功率:全部成功 events: generateHealthEvents(45, 1.0, 0, 0, 400, 200), timeline: generateHealthTimeline(0.96, 0.02, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'gemini:generate_content', api_path: '/v1beta/models', total_attempts: 456, success_count: 450, failed_count: 4, skipped_count: 2, success_rate: 0.987, avg_latency_ms: 520, avg_first_byte_ms: 110, avg_tps: 102.7, provider_count: 3, key_count: 3, last_event_at: new Date().toISOString(), // 98.7% 成功率:successRate=0.987, failRate=0.009, skipRate=0.004 events: generateHealthEvents(25, 0.987, 0.009, 0.004, 500, 300), timeline: generateHealthTimeline(0.9, 0.06, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'openai:chat', api_path: '/v1/chat/completions', total_attempts: 1560, success_count: 1520, failed_count: 35, skipped_count: 5, success_rate: 0.974, avg_latency_ms: 760, avg_first_byte_ms: 138, avg_tps: 88.9, provider_count: 1, key_count: 2, last_event_at: new Date().toISOString(), // 97.4% 成功率:successRate=0.974, failRate=0.022, skipRate=0.004 events: generateHealthEvents(60, 0.974, 0.022, 0.004, 700, 400), timeline: generateHealthTimeline(0.86, 0.09, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'openai:responses', api_path: '/v1/responses', total_attempts: 2340, success_count: 2200, failed_count: 100, skipped_count: 40, success_rate: 0.940, avg_latency_ms: 980, avg_first_byte_ms: 185, avg_tps: 64.3, provider_count: 4, key_count: 5, last_event_at: new Date().toISOString(), // 94.0% 成功率:successRate=0.940, failRate=0.043, skipRate=0.017 events: generateHealthEvents(100, 0.940, 0.043, 0.017, 800, 600), timeline: generateHealthTimeline(0.76, 0.14, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { api_format: 'openai:embedding', api_path: '/v1/embeddings', total_attempts: 620, success_count: 612, failed_count: 6, skipped_count: 2, success_rate: 0.987, avg_latency_ms: 330, avg_first_byte_ms: 72, avg_tps: 0, provider_count: 1, key_count: 1, last_event_at: new Date().toISOString(), events: generateHealthEvents(40, 0.987, 0.01, 0.003, 320, 140), timeline: generateHealthTimeline(0.92, 0.05, 60), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() } ] } const MOCK_MODEL_STATUS = { generated_at: new Date().toISOString(), models: [ { model: 'gpt-5.5', display_name: 'gpt-5.5', total_attempts: 2021, success_count: 2000, failed_count: 21, success_rate: 0.9896, avg_latency_ms: 1736, avg_first_byte_ms: 176, avg_tps: 84.6, provider_count: 3, last_event_at: new Date().toISOString(), events: generateHealthEvents(60, 0.989, 0.008, 0.003, 1600, 460), timeline: generateHealthTimeline(0.9, 0.05), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { model: 'claude-sonnet-4-5-20250929', display_name: 'Claude Sonnet 4.5', total_attempts: 1684, success_count: 1642, failed_count: 42, success_rate: 0.9751, avg_latency_ms: 1280, avg_first_byte_ms: 221, avg_tps: 76.2, provider_count: 2, last_event_at: new Date().toISOString(), events: generateHealthEvents(60, 0.975, 0.02, 0.005, 1200, 520), timeline: generateHealthTimeline(0.84, 0.09), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { model: 'gemini-3-pro-preview', display_name: 'Gemini 3 Pro Preview', total_attempts: 932, success_count: 887, failed_count: 45, success_rate: 0.9517, avg_latency_ms: 940, avg_first_byte_ms: 184, avg_tps: 101.8, provider_count: 2, last_event_at: new Date().toISOString(), events: generateHealthEvents(55, 0.952, 0.04, 0.008, 860, 300), timeline: generateHealthTimeline(0.78, 0.14), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() }, { model: 'gpt-5.1-codex-mini', display_name: 'gpt-5.1-codex-mini', total_attempts: 418, success_count: 349, failed_count: 69, success_rate: 0.835, avg_latency_ms: 2310, avg_first_byte_ms: 420, avg_tps: 38.4, provider_count: 1, last_event_at: new Date().toISOString(), events: generateHealthEvents(45, 0.835, 0.145, 0.02, 2200, 780), timeline: generateHealthTimeline(0.58, 0.24), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString() } ] } const MOCK_PROVIDER_HEALTH_STATUS = { generated_at: new Date().toISOString(), providers: [ { provider_id: 'provider-001', provider_name: 'OpenAI Official', provider_type: 'codex', is_active: true, total_attempts: 2021, success_count: 2000, failed_count: 21, success_rate: 0.9896, avg_latency_ms: 1736, avg_first_byte_ms: 176, avg_tps: 84.6, model_count: 2, last_event_at: new Date().toISOString(), timeline: generateHealthTimeline(0.9, 0.05), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString(), models: [MOCK_MODEL_STATUS.models[0], MOCK_MODEL_STATUS.models[3]] }, { provider_id: 'provider-002', provider_name: 'Anthropic Official', provider_type: 'claude_code', is_active: true, total_attempts: 1684, success_count: 1642, failed_count: 42, success_rate: 0.9751, avg_latency_ms: 1280, avg_first_byte_ms: 221, avg_tps: 76.2, model_count: 1, last_event_at: new Date().toISOString(), timeline: generateHealthTimeline(0.84, 0.09), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString(), models: [MOCK_MODEL_STATUS.models[1]] }, { provider_id: 'provider-003', provider_name: 'Google AI', provider_type: 'gemini_cli', is_active: true, total_attempts: 932, success_count: 887, failed_count: 45, success_rate: 0.9517, avg_latency_ms: 940, avg_first_byte_ms: 184, avg_tps: 101.8, model_count: 1, last_event_at: new Date().toISOString(), timeline: generateHealthTimeline(0.78, 0.14), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString(), models: [MOCK_MODEL_STATUS.models[2]] }, { provider_id: 'provider-004', provider_name: 'AWS Bedrock', provider_type: 'custom', is_active: true, total_attempts: 0, success_count: 0, failed_count: 0, success_rate: 1, avg_latency_ms: null, avg_first_byte_ms: null, avg_tps: null, model_count: 0, last_event_at: null, timeline: Array.from({ length: 60 }, () => 'unknown'), time_range_start: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), time_range_end: new Date().toISOString(), models: [] } ] } function mockApiFormatDisplayName(apiFormat: string) { const labels: Record = { 'claude:messages': 'Claude Messages', 'gemini:generate_content': 'Gemini Generate Content', 'gemini:interactions': 'Gemini Interactions', 'openai:chat': 'OpenAI Chat', 'openai:embedding': 'OpenAI Embedding' } return labels[apiFormat] || apiFormat } function relatedEndpointMonitor(format: typeof MOCK_ENDPOINT_STATUS.formats[number]) { const detailed = withHealthTimelineDetails(format) return { kind: 'endpoint', key: format.api_format, display_name: mockApiFormatDisplayName(format.api_format), meta_text: format.api_path, total_attempts: format.total_attempts, success_count: format.success_count, failed_count: format.failed_count, success_rate: format.success_rate, avg_latency_ms: format.avg_latency_ms, avg_first_byte_ms: format.avg_first_byte_ms, avg_tps: format.avg_tps, last_event_at: format.last_event_at, timeline: format.timeline, timeline_details: detailed.timeline_details, time_range_start: format.time_range_start, time_range_end: format.time_range_end } } function relatedModelMonitor(model: typeof MOCK_MODEL_STATUS.models[number]) { const detailed = withHealthTimelineDetails(model) return { kind: 'model', key: model.model, display_name: model.display_name || model.model, meta_text: model.provider_count ? `${model.provider_count} 个提供商` : null, total_attempts: model.total_attempts, success_count: model.success_count, failed_count: model.failed_count, success_rate: model.success_rate, avg_latency_ms: model.avg_latency_ms, avg_first_byte_ms: model.avg_first_byte_ms, avg_tps: model.avg_tps, last_event_at: model.last_event_at, timeline: model.timeline, timeline_details: detailed.timeline_details, time_range_start: model.time_range_start, time_range_end: model.time_range_end } } function relatedProviderMonitor(provider: typeof MOCK_PROVIDER_HEALTH_STATUS.providers[number]) { const detailed = withHealthTimelineDetails(provider) return { kind: 'provider', key: provider.provider_name, display_name: provider.provider_name, meta_text: provider.provider_type || 'custom', total_attempts: provider.total_attempts, success_count: provider.success_count, failed_count: provider.failed_count, success_rate: provider.success_rate, avg_latency_ms: provider.avg_latency_ms, avg_first_byte_ms: provider.avg_first_byte_ms, avg_tps: provider.avg_tps, last_event_at: provider.last_event_at, timeline: provider.timeline, timeline_details: detailed.timeline_details, time_range_start: provider.time_range_start, time_range_end: provider.time_range_end } } function uniqueMockEndpointFormats() { const seen = new Set() return MOCK_ENDPOINT_STATUS.formats.filter(format => { if (seen.has(format.api_format)) return false seen.add(format.api_format) return true }) } function buildMockRelatedHealthResponse(config: AxiosRequestConfig, includeProviders: boolean) { const dimension = String(config.params?.dimension || 'endpoint') const value = String(config.params?.value || '') const endpoints = uniqueMockEndpointFormats().slice(0, 3).map(relatedEndpointMonitor) const models = MOCK_MODEL_STATUS.models.slice(0, 3).map(relatedModelMonitor) const providers = includeProviders ? MOCK_PROVIDER_HEALTH_STATUS.providers.slice(0, 3).map(relatedProviderMonitor) : [] return { generated_at: new Date().toISOString(), dimension, value, related_endpoints: dimension === 'endpoint' ? [] : endpoints, related_models: dimension === 'model' ? [] : models, related_providers: dimension === 'provider' ? [] : providers } } // 生成活跃热力图数据(最近365天) function generateActivityHeatmap() { const days: Array<{ date: string requests: number total_tokens: number total_cost: number actual_total_cost?: number }> = [] const now = new Date() const startDate = new Date(now) startDate.setDate(startDate.getDate() - 364) // 365天数据(一年) let maxRequests = 0 // 生成每天的数据 for (let i = 0; i < 365; i++) { const date = new Date(startDate) date.setDate(startDate.getDate() + i) const dateStr = date.toISOString().split('T')[0] // 工作日请求量更高 const dayOfWeek = date.getDay() const isWeekend = dayOfWeek === 0 || dayOfWeek === 6 // 基础请求量 + 随机波动 + 周末减少 // 加入一些趋势:越近的日期请求量可能越高 const trendFactor = 0.7 + (i / 365) * 0.5 // 从0.7到1.2的增长趋势 const baseRequests = isWeekend ? 40 : 120 const variance = Math.floor(Math.random() * 80) // 有些天可能没有请求(约5%的天数) const noActivity = Math.random() < 0.05 const requests = noActivity ? 0 : Math.round((baseRequests + variance) * trendFactor) if (requests > maxRequests) maxRequests = requests // 根据请求量计算 tokens 和 cost const avgTokensPerRequest = 3000 + Math.floor(Math.random() * 2000) const totalTokens = requests * avgTokensPerRequest const avgCostPerRequest = 0.02 + Math.random() * 0.03 const totalCost = Number((requests * avgCostPerRequest).toFixed(2)) const actualTotalCost = Number((totalCost * 0.8).toFixed(2)) // 实际成本约为 80% days.push({ date: dateStr, requests, total_tokens: totalTokens, total_cost: totalCost, actual_total_cost: actualTotalCost }) } return { start_date: days[0].date, end_date: days[days.length - 1].date, total_days: days.length, max_requests: maxRequests, days } } // 缓存热力图数据(避免每次请求都重新生成) let cachedHeatmap: ReturnType | null = null function getActivityHeatmap() { if (!cachedHeatmap) { cachedHeatmap = generateActivityHeatmap() } return cachedHeatmap } const MOCK_CYBER_POLICY_USAGE_ID = 'usage-cyber-risk-demo' const MOCK_CYBER_POLICY_ERROR_MESSAGE = 'This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. To get authorized for security work, join the Trusted Access for Cyber program: https://chatgpt.com/cyber' const MOCK_CYBER_POLICY_ERROR_BODY = { error: { type: 'invalid_request', message: MOCK_CYBER_POLICY_ERROR_MESSAGE, code: 400 } } interface MockManagedUserApiKey { id: string fullKey: string key_display: string name: string created_at: string last_used_at?: string is_active: boolean is_locked: boolean is_standalone: false feature_settings?: Record | null rate_limit?: number | null concurrent_limit?: number | null ip_rules?: string[] | null total_requests: number total_cost_usd: number force_capabilities?: Record | null } const mockManagedUserApiKeysByUserId = new Map([ [MOCK_NORMAL_USER.id ?? '', MOCK_USER_API_KEYS.map((key, index) => ({ ...key, fullKey: `sk-ae-demo-user-${index + 1}`, is_locked: false, is_standalone: false as const, }))], ]) let mockManagedUserApiKeySequence = 0 function mockManagedUserApiKeys(userId: string): MockManagedUserApiKey[] { if (!MOCK_ALL_USERS.some(user => user.id === userId)) { throw { response: createMockResponse({ detail: '用户不存在' }, 404) } } let keys = mockManagedUserApiKeysByUserId.get(userId) if (!keys) { keys = [] mockManagedUserApiKeysByUserId.set(userId, keys) } return keys } function publicMockManagedUserApiKey(key: MockManagedUserApiKey) { const { fullKey: _fullKey, ...publicKey } = key void _fullKey return publicKey } function mockRequestObject(config: AxiosRequestConfig): Record { if (typeof config.data === 'string') { try { const parsed = JSON.parse(config.data) return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} } catch { return {} } } return config.data && typeof config.data === 'object' && !Array.isArray(config.data) ? config.data as Record : {} } // 生成更真实的使用记录 function generateMockUsageRecords(count: number = 100) { const records = [] const now = Date.now() const models = [ { name: 'claude-sonnet-4-5-20250929', provider: 'anthropic', inputPrice: 3, outputPrice: 15 }, { name: 'claude-haiku-4-5-20251001', provider: 'anthropic', inputPrice: 1, outputPrice: 5 }, { name: 'claude-opus-4-5-20251101', provider: 'anthropic', inputPrice: 15, outputPrice: 75 }, { name: 'gpt-5.1', provider: 'openai', inputPrice: 2.5, outputPrice: 10 }, { name: 'gpt-5.1-codex', provider: 'openai', inputPrice: 2.5, outputPrice: 10 }, { name: 'gemini-3-pro-preview', provider: 'google', inputPrice: 2, outputPrice: 12 } ] const users = [ { id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.ai' }, { id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.ai' }, { id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.ai' }, { id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.ai' } ] const apiFormats = ['claude:messages', 'openai:chat', 'openai:responses', 'gemini:generate_content'] const statusOptions: Array<'completed' | 'failed' | 'streaming'> = ['completed', 'completed', 'completed', 'completed', 'failed', 'streaming'] for (let i = 0; i < count; i++) { const model = models[Math.floor(Math.random() * models.length)] const user = users[Math.floor(Math.random() * users.length)] const status = statusOptions[Math.floor(Math.random() * statusOptions.length)] // 根据模型类型选择 API 格式 let apiFormat = apiFormats[0] if (model.provider === 'anthropic') { apiFormat = 'claude:messages' } else if (model.provider === 'openai') { apiFormat = Math.random() > 0.3 ? 'openai:responses' : 'openai:chat' } else { apiFormat = 'gemini:generate_content' } const inputTokens = 500 + Math.floor(Math.random() * 10000) const outputTokens = 200 + Math.floor(Math.random() * 4000) const cacheCreation = Math.random() > 0.7 ? Math.floor(Math.random() * 2000) : 0 const cacheRead = Math.random() > 0.5 ? Math.floor(Math.random() * 5000) : 0 const totalTokens = inputTokens + outputTokens // 计算成本(每百万 token) const inputCost = (inputTokens / 1000000) * model.inputPrice const outputCost = (outputTokens / 1000000) * model.outputPrice const cost = Number((inputCost + outputCost).toFixed(6)) const actualCost = Number((cost * (0.7 + Math.random() * 0.3)).toFixed(6)) // 时间分布:最近的记录更密集 const timeOffset = Math.pow(i / count, 1.5) * 7 * 24 * 60 * 60 * 1000 // 7天内 const createdAt = new Date(now - timeOffset) // 响应时间:根据模型和 token 数量 const baseResponseTime = model.name.includes('opus') ? 2000 : model.name.includes('haiku') ? 500 : 1000 const responseTime = status === 'failed' ? null : baseResponseTime + Math.floor(Math.random() * outputTokens * 0.5) records.push({ id: `usage-${String(i + 1).padStart(4, '0')}`, user_id: user.id, username: user.username, user_email: user.email, api_key: { id: `key-${user.id}-${Math.ceil(Math.random() * 2)}`, name: `${user.username} Key ${Math.ceil(Math.random() * 3)}`, display: `sk-ae...${String(1000 + Math.floor(Math.random() * 9000))}` }, provider: model.provider, api_key_name: `${model.provider}-key-${Math.ceil(Math.random() * 3)}`, rate_multiplier: 1.0, model: model.name, target_model: model.name, api_format: apiFormat, input_tokens: inputTokens, output_tokens: outputTokens, cache_creation_input_tokens: cacheCreation, cache_read_input_tokens: cacheRead, total_tokens: totalTokens, cost, actual_cost: actualCost, response_time_ms: responseTime, is_stream: apiFormat.includes(':cli'), status_code: status === 'failed' ? [500, 502, 429, 400][Math.floor(Math.random() * 4)] : 200, error_message: status === 'failed' ? ['Rate limit exceeded', 'Internal server error', 'Model overloaded'][Math.floor(Math.random() * 3)] : undefined, status, created_at: createdAt.toISOString(), has_fallback: Math.random() > 0.9, model_version: model.provider === 'google' ? 'gemini-3-pro-preview-2025-01' : undefined }) } // 固定在首屏的失败记录,用于预览候选链路中的实际上游错误响应。 records.unshift({ id: MOCK_CYBER_POLICY_USAGE_ID, user_id: 'demo-admin-uuid-0001', username: 'Demo Admin', user_email: 'admin@demo.aether.ai', api_key: { id: 'key-demo-cyber-risk', name: 'OpenAI Cyber Risk Demo', display: 'sk-ae...demo' }, provider: 'openai', api_key_name: 'openai-cyber-risk-demo', rate_multiplier: 1.0, model: 'gpt-5', target_model: 'gpt-5.1', requested_reasoning_effort: 'xhigh', reasoning_effort: 'max', service_tier: 'priority', // Deliberately conflicts with the final provider request. UI and billing // must use the request-side `service_tier`, never this response fact. actual_service_tier: 'default', api_format: 'openai:responses', input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, total_tokens: 0, cost: 0, actual_cost: 0, response_time_ms: 428, is_stream: true, status_code: 400, error_message: MOCK_CYBER_POLICY_ERROR_MESSAGE, status: 'failed', created_at: new Date(now).toISOString(), updated_at: new Date(now).toISOString(), has_fallback: false, model_version: undefined }) return records } // 缓存使用记录 let cachedUsageRecords: ReturnType | null = null function getUsageRecords() { if (!cachedUsageRecords) { cachedUsageRecords = generateMockUsageRecords(100) } return cachedUsageRecords } // Mock 映射数据 const MOCK_ALIASES = [ { id: 'alias-001', source_model: 'claude-4-sonnet', target_global_model_id: 'gm-003', target_global_model_name: 'claude-sonnet-4-5-20250929', target_global_model_display_name: 'Claude Sonnet 4.5', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }, { id: 'alias-002', source_model: 'claude-4-opus', target_global_model_id: 'gm-002', target_global_model_name: 'claude-opus-4-5-20251101', target_global_model_display_name: 'Claude Opus 4.5', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }, { id: 'alias-003', source_model: 'gpt5', target_global_model_id: 'gm-006', target_global_model_name: 'gpt-5.1', target_global_model_display_name: 'GPT-5.1', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }, { id: 'alias-004', source_model: 'gemini-pro', target_global_model_id: 'gm-005', target_global_model_name: 'gemini-3-pro-preview', target_global_model_display_name: 'Gemini 3 Pro Preview', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' } ] interface MockRoutingGroup { id: string name: string description: string | null enabled: boolean is_system_default: boolean config_json: Record version: number created_at: number updated_at: number published_at: number | null } interface MockRoutingGroupVersion { id: string group_id: string version: number config_json: Record created_at: number created_by: string | null } interface MockRoutingGroupBinding { id: string group_id: string subject_type: 'user' | 'api_key' | 'user_group' subject_id: string is_default: boolean allow_explicit_select: boolean created_at: number updated_at: number } const mockRoutingNow = Math.floor(Date.now() / 1000) const MOCK_ROUTING_GROUPS: MockRoutingGroup[] = [ { id: 'routing-default', name: '默认调度策略', description: '演示模式默认分组,保持 Provider 优先和缓存亲和', enabled: true, is_system_default: true, config_json: { allowed_models: [], default_policy: { priority_mode: 'provider', scheduling_mode: 'cache_affinity', keep_priority_on_conversion: false, sticky_key_attempts: 2, }, model_policies: [ { model: 'gpt-5.1', allowed_providers: ['provider-002'], allowed_keys: [], provider_priority_overrides: { 'provider-002': 0 }, key_priority_overrides: {}, key_priority_overrides_by_format: {}, pool_priority_overrides: {}, pool_policy_overrides: {}, }, ], rules: [], }, version: 1, created_at: mockRoutingNow - 86400, updated_at: mockRoutingNow - 3600, published_at: mockRoutingNow - 3600, }, ] const MOCK_ROUTING_GROUP_VERSIONS: MockRoutingGroupVersion[] = [ { id: 'routing-default-v1', group_id: 'routing-default', version: 1, config_json: MOCK_ROUTING_GROUPS[0].config_json, created_at: MOCK_ROUTING_GROUPS[0].published_at ?? mockRoutingNow, created_by: null, }, ] const MOCK_ROUTING_GROUP_BINDINGS: MockRoutingGroupBinding[] = [] function cloneMockRoutingGroup(group: MockRoutingGroup): MockRoutingGroup { return JSON.parse(JSON.stringify(group)) as MockRoutingGroup } function cloneMockRoutingVersion(version: MockRoutingGroupVersion): MockRoutingGroupVersion { return JSON.parse(JSON.stringify(version)) as MockRoutingGroupVersion } function unsetOtherMockRoutingDefaults(groupId: string): void { for (const group of MOCK_ROUTING_GROUPS) { if (group.id !== groupId) { group.is_system_default = false } } } function normalizeApiFormat(apiFormat: string): string { return apiFormat.toLowerCase().replace(/_/g, ':') } function getMockEndpointExtras(apiFormat: string) { const normalizedFormat = normalizeApiFormat(apiFormat) const extras: Record = {} if (normalizedFormat === 'claude:messages') { extras.header_rules = [ { action: 'set', key: 'x-app-id', value: 'demo-app' }, { action: 'rename', from: 'x-client-id', to: 'x-client' }, { action: 'drop', key: 'x-debug' } ] extras.body_rules = [ { action: 'set', path: 'metadata.user_id', value: 'demo-user' }, { action: 'insert', path: 'messages', index: 0, value: { role: 'system', content: 'You are a helpful assistant.' } }, { action: 'regex_replace', path: 'messages[0].content', pattern: '\\s+', replacement: ' ', flags: 'm', condition: { path: 'metadata.source', op: 'eq', value: 'internal' } } ] } else if (normalizedFormat === 'openai:chat') { extras.header_rules = [ { action: 'set', key: 'x-client', value: 'demo' } ] extras.format_acceptance_config = { enabled: true, accept_formats: ['openai:chat', 'claude:messages'] } extras.config = { upstream_stream_policy: 'force_stream' } } else if (normalizedFormat === 'openai:responses') { extras.config = { upstream_stream_policy: 'force_non_stream' } } else if (normalizedFormat === 'openai:embedding') { extras.config = { route_kind: 'embedding' } } else if (normalizedFormat === 'openai:rerank' || normalizedFormat === 'jina:rerank') { extras.config = { route_kind: 'rerank' } } else if (normalizedFormat === 'gemini:generate_content') { extras.custom_path = '/models/gemini-3-pro-preview:generateContent' extras.body_rules = [ { action: 'drop', path: 'metadata.debug' } ] } return extras } // Mock Endpoint Keys const MOCK_ENDPOINT_KEYS = [ { id: 'ekey-001', provider_id: 'provider-001', api_formats: ['claude:messages'], api_key_masked: 'sk-ant...abc1', auth_type: 'api_key', name: 'Primary Key', rate_multiplier: 1.0, internal_priority: 1, health_score: 0.98, consecutive_failures: 0, request_count: 5000, success_count: 4950, error_count: 50, success_rate: 0.99, avg_response_time_ms: 1200, cache_ttl_minutes: 5, max_probe_interval_minutes: 32, is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString() }, { id: 'ekey-002', provider_id: 'provider-001', api_formats: ['claude:messages'], api_key_masked: 'sk-ant...def2', auth_type: 'api_key', name: 'Backup Key', rate_multiplier: 1.0, internal_priority: 2, health_score: 0.95, consecutive_failures: 1, request_count: 2000, success_count: 1950, error_count: 50, success_rate: 0.975, avg_response_time_ms: 1350, cache_ttl_minutes: 5, max_probe_interval_minutes: 32, is_active: true, created_at: '2024-02-01T00:00:00Z', updated_at: new Date().toISOString() }, { id: 'ekey-003', provider_id: 'provider-002', api_formats: ['openai:chat'], api_key_masked: 'sk-oai...ghi3', auth_type: 'oauth', name: 'OpenAI OAuth', oauth_email: 'oauth-demo@aether.dev', oauth_expires_at: Math.floor(Date.now() / 1000) + 6 * 3600, oauth_plan_type: 'pro', oauth_account_id: 'acct-demo-002', rate_multiplier: 1.0, internal_priority: 1, health_score: 0.97, consecutive_failures: 0, request_count: 3500, success_count: 3450, error_count: 50, success_rate: 0.986, avg_response_time_ms: 900, cache_ttl_minutes: 5, max_probe_interval_minutes: 32, is_active: true, created_at: '2024-01-15T00:00:00Z', updated_at: new Date().toISOString() } ] // Mock Endpoints const MOCK_ENDPOINTS = [ { id: 'ep-001', provider_id: 'provider-001', provider_name: 'anthropic', api_format: 'claude:messages', base_url: 'https://api.anthropic.com/v1', max_retries: 2, is_active: true, total_keys: 2, active_keys: 2, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString(), ...getMockEndpointExtras('claude:messages') }, { id: 'ep-002', provider_id: 'provider-002', provider_name: 'openai', api_format: 'openai:chat', base_url: 'https://api.openai.com/v1', max_retries: 2, is_active: true, total_keys: 1, active_keys: 1, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString(), ...getMockEndpointExtras('openai:chat') }, { id: 'ep-003', provider_id: 'provider-003', provider_name: 'google', api_format: 'gemini:generate_content', base_url: 'https://generativelanguage.googleapis.com/v1beta', max_retries: 2, is_active: true, total_keys: 1, active_keys: 1, created_at: '2024-01-15T00:00:00Z', updated_at: new Date().toISOString(), ...getMockEndpointExtras('gemini:generate_content') } ] // Mock 能力定义 const MOCK_CAPABILITIES = [ { name: 'cache_1h', display_name: '1小时缓存', description: '支持1小时prompt缓存', match_mode: 'exclusive', short_name: '1h' }, { name: 'context_1m', display_name: '1M上下文', description: '支持1M上下文窗口', match_mode: 'compatible', short_name: '1M' } ] const MOCK_CODEX_POOL_PROVIDER_ID = 'provider-codex-pool-demo' const MOCK_CODEX_POOL_PROVIDER = { id: MOCK_CODEX_POOL_PROVIDER_ID, name: 'Codex 周期额度演示', provider_type: 'codex', description: '展示 5H、周、月及组合额度窗口', website: 'https://openai.com/codex', provider_priority: 0, billing_type: 'free_tier', monthly_used_usd: 0, is_active: true, total_endpoints: 1, active_endpoints: 1, total_keys: 4, active_keys: 4, total_models: 3, active_models: 3, avg_health_score: 0.97, unhealthy_endpoints: 0, api_formats: ['openai:responses'], endpoint_health_details: [ { api_format: 'openai:responses', health_score: 0.97, is_active: true, active_keys: 4 } ], pool_advanced: { enabled: true, probing_enabled: true, }, claude_code_advanced: null, proxy: null, created_at: '2026-07-01T00:00:00Z', updated_at: new Date().toISOString(), } function createMockCodexQuotaWindow( code: string, label: string, windowMinutes: number, remainingRatio: number, resetSeconds: number, observedAt: number, requestCount: number, ) { return { code, label, scope: 'account', unit: 'percent', used_ratio: 1 - remainingRatio, remaining_ratio: remainingRatio, reset_at: resetSeconds > 0 ? observedAt + resetSeconds : null, reset_seconds: resetSeconds, window_minutes: windowMinutes, usage: { request_count: requestCount, total_tokens: requestCount * 1250, total_cost_usd: (requestCount * 0.0025).toFixed(8), }, } } function createMockCodexPoolKeys() { const nowSeconds = Math.floor(Date.now() / 1000) const common = { provider_type: 'codex', is_active: true, auth_type: 'oauth', credential_kind: 'oauth_session', runtime_auth_kind: 'bearer', oauth_managed: true, oauth_header_auth: true, can_refresh_oauth: true, can_export_oauth: true, can_edit_oauth: true, oauth_expires_at: nowSeconds + 14 * 24 * 3600, api_formats: ['openai:responses'], rate_multipliers: null, internal_priority: 50, rpm_limit: null, cache_ttl_minutes: 5, max_probe_interval_minutes: 32, health_score: 0.97, circuit_breaker_open: false, proxy: null, cooldown_reason: null, cooldown_ttl_seconds: null, cost_window_usage: 0, cost_limit: null, sticky_sessions: 0, lru_score: null, created_at: '2026-07-01T00:00:00Z', imported_at: '2026-07-01T00:00:00Z', last_used_at: new Date(nowSeconds * 1000 - 10 * 60 * 1000).toISOString(), scheduling_status: 'available', scheduling_reason: 'available', scheduling_label: '可调度', scheduling_reasons: [], } const buildKey = ( keyId: string, keyName: string, planType: string, accountQuota: string, windows: ReturnType[], requestCount: number, ) => ({ ...common, key_id: keyId, key_name: keyName, oauth_plan_type: planType, oauth_account_id: `acct-${keyId}`, oauth_account_name: keyName, quota_updated_at: nowSeconds - 10 * 60, account_quota: accountQuota, request_count: requestCount, total_tokens: requestCount * 2400, total_cost_usd: (requestCount * 0.004).toFixed(8), status_snapshot: { oauth: { code: 'valid', label: '有效', expires_at: nowSeconds + 14 * 24 * 3600, requires_reauth: false, expiring_soon: false, }, account: { code: 'ok', label: null, reason: null, blocked: false, source: null, recoverable: false, }, quota: { version: 2, provider_type: 'codex', code: 'ok', label: null, reason: null, freshness: 'fresh', source: 'response_headers', observed_at: nowSeconds, updated_at: nowSeconds, exhausted: false, usage_ratio: windows.reduce((max, window) => Math.max(max, window.used_ratio), 0), plan_type: planType, credits: { has_credits: false, unlimited: false }, windows, }, }, }) return [ buildKey( 'codex-pool-plus-dual', 'Plus · 5H + 周', 'plus', '5H剩余 62.0% | 周剩余 84.0%', [ createMockCodexQuotaWindow('5h', '5H', 300, 0.62, 3 * 3600, nowSeconds, 18), createMockCodexQuotaWindow('weekly', '周', 10_080, 0.84, 5 * 24 * 3600, nowSeconds, 42), ], 128, ), buildKey( 'codex-pool-team-weekly', 'Team · 仅周', 'team', '周剩余 71.0%', [ createMockCodexQuotaWindow('weekly', '周', 10_080, 0.71, 4 * 24 * 3600, nowSeconds, 31), ], 96, ), buildKey( 'codex-pool-business-monthly', 'Codex · 仅月(含空占位)', 'self_serve_business_usage_based', '月剩余 86.0%', [ createMockCodexQuotaWindow('monthly', '月', 43_800, 0.86, 2_627_672, nowSeconds, 54), createMockCodexQuotaWindow('weekly', '周', 0, 1, 0, nowSeconds, 0), ], 214, ), buildKey( 'codex-pool-free-five-hour', 'Free · 仅5H', 'free', '5H剩余 93.0%', [ createMockCodexQuotaWindow('5h', '5H', 300, 0.93, 4 * 3600, nowSeconds, 7), ], 37, ), ] } /** * Mock API 路由处理器 */ const mockHandlers: Record Promise>> = { // ========== 认证相关 ========== 'POST /api/auth/login': async (config) => { await delay() const body = JSON.parse(config.data || '{}') const { email, password } = body if (email === DEMO_ACCOUNTS.admin.email && password === DEMO_ACCOUNTS.admin.password) { currentUserToken = 'demo-access-token-admin' return createMockResponse(MOCK_LOGIN_RESPONSE_ADMIN) } if (email === DEMO_ACCOUNTS.user.email && password === DEMO_ACCOUNTS.user.password) { currentUserToken = 'demo-access-token-user' return createMockResponse(MOCK_LOGIN_RESPONSE_USER) } throw { response: createMockResponse({ detail: '邮箱或密码错误' }, 401) } }, 'POST /api/auth/logout': async () => { await delay(100) currentUserToken = null return createMockResponse({ message: '已登出' }) }, 'POST /api/auth/refresh': async () => { await delay(100) if (isCurrentUserAdmin()) { return createMockResponse(MOCK_LOGIN_RESPONSE_ADMIN) } return createMockResponse(MOCK_LOGIN_RESPONSE_USER) }, // ========== 用户信息 ========== 'GET /api/users/me': async () => { await delay() return createMockResponse(getCurrentProfile()) }, 'PUT /api/users/me': async () => { await delay() return createMockResponse({ message: '更新成功(演示模式)' }) }, 'PATCH /api/users/me/password': async () => { await delay() return createMockResponse({ message: '密码修改成功(演示模式)' }) }, 'GET /api/users/me/sessions': async () => { await delay() return createMockResponse([ { id: 'session-current', device_label: 'Chrome / macOS', device_type: 'desktop', browser_name: 'Chrome', browser_version: '134.0', os_name: 'macOS', os_version: '15.3', device_model: null, ip_address: '192.168.1.100', last_seen_at: new Date().toISOString(), created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(), is_current: true, revoked_at: null, revoke_reason: null }, { id: 'session-other', device_label: 'Safari / iPhone', device_type: 'mobile', browser_name: 'Safari', browser_version: '18.0', os_name: 'iOS', os_version: '18.3', device_model: 'iPhone', ip_address: '10.0.0.12', last_seen_at: new Date(Date.now() - 3 * 3600 * 1000).toISOString(), created_at: new Date(Date.now() - 5 * 24 * 3600 * 1000).toISOString(), is_current: false, revoked_at: null, revoke_reason: null } ]) }, 'DELETE /api/users/me/sessions/others': async () => { await delay() return createMockResponse({ message: '其他设备已退出登录(演示模式)', revoked_count: 1 }) }, 'PATCH /api/users/me/sessions/:sessionId': async (config) => { await delay() const sessionId = config.url?.split('/').pop() || 'session' const body = JSON.parse(config.data || '{}') return createMockResponse({ id: sessionId, device_label: body.device_label || '已重命名设备', device_type: 'desktop', browser_name: 'Chrome', browser_version: '134.0', os_name: 'macOS', os_version: '15.3', device_model: null, ip_address: '192.168.1.100', last_seen_at: new Date().toISOString(), created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(), is_current: sessionId === 'session-current', revoked_at: null, revoke_reason: null }) }, 'DELETE /api/users/me/sessions/:sessionId': async () => { await delay() return createMockResponse({ message: '设备已退出登录(演示模式)' }) }, 'GET /api/users/me/api-keys': async () => { await delay() return createMockResponse(MOCK_USER_API_KEYS) }, 'GET /api/users/me/client-config': async () => { await delay() const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://demo.aether.local' return createMockResponse({ base_url: baseUrl, site_name: 'Aether Demo', }) }, 'POST /api/users/me/api-keys': async (config) => { await delay() const body = JSON.parse(config.data || '{}') const newKey = { id: `key-demo-${Date.now()}`, key: `sk-aether-demo-${Math.random().toString(36).substring(2, 15)}`, key_display: 'sk-ae...demo', name: body.name || '新密钥(演示)', created_at: new Date().toISOString(), is_active: true, is_standalone: false, total_requests: 0, total_cost_usd: 0 } return createMockResponse(newKey) }, 'GET /api/users/me/usage': async () => { await delay() const heatmap = getActivityHeatmap() const records = getUsageRecords() // 只返回当前用户的数据 const userRecords = records.filter(r => r.user_id === getCurrentUser().id) const totalRequests = userRecords.length const totalTokens = userRecords.reduce((sum, r) => sum + r.total_tokens, 0) const totalInputTokens = userRecords.reduce((sum, r) => sum + r.input_tokens, 0) const totalOutputTokens = userRecords.reduce((sum, r) => sum + r.output_tokens, 0) const totalCost = userRecords.reduce((sum, r) => sum + r.cost, 0) const totalActualCost = userRecords.reduce((sum, r) => sum + (r.actual_cost || 0), 0) const avgResponseTime = userRecords.filter(r => r.response_time_ms).reduce((sum, r) => sum + (r.response_time_ms || 0), 0) / userRecords.filter(r => r.response_time_ms).length / 1000 // 按模型聚合 const modelStats = new Map() for (const r of userRecords) { const existing = modelStats.get(r.model) || { requests: 0, input_tokens: 0, output_tokens: 0, total_tokens: 0, total_cost_usd: 0, actual_total_cost_usd: 0 } existing.requests++ existing.input_tokens += r.input_tokens existing.output_tokens += r.output_tokens existing.total_tokens += r.total_tokens existing.total_cost_usd += r.cost existing.actual_total_cost_usd += r.actual_cost || 0 modelStats.set(r.model, existing) } return createMockResponse({ total_requests: totalRequests * 20, total_input_tokens: totalInputTokens * 20, total_output_tokens: totalOutputTokens * 20, total_tokens: totalTokens * 20, total_cost: Number((totalCost * 20).toFixed(2)), total_actual_cost: Number((totalActualCost * 20).toFixed(2)), avg_response_time: Number(avgResponseTime.toFixed(2)) || 1.23, billing: { id: 'wallet-demo-user', balance: Number((100 - totalCost * 20).toFixed(2)), recharge_balance: Number((100 - totalCost * 20).toFixed(2)), gift_balance: 0, refundable_balance: Number((100 - totalCost * 20).toFixed(2)), currency: 'USD', status: 'active', limit_mode: 'finite', unlimited: false, total_recharged: 100, total_consumed: Number((totalCost * 20).toFixed(2)), total_refunded: 0, total_adjusted: 0, updated_at: new Date().toISOString(), }, activity_heatmap: heatmap, summary_by_model: Array.from(modelStats.entries()).map(([model, stats]) => ({ model, requests: stats.requests * 20, input_tokens: stats.input_tokens * 20, output_tokens: stats.output_tokens * 20, total_tokens: stats.total_tokens * 20, total_cost_usd: Number((stats.total_cost_usd * 20).toFixed(2)), actual_total_cost_usd: Number((stats.actual_total_cost_usd * 20).toFixed(2)) })), records: userRecords.slice(0, 10).map(r => ({ id: r.id, provider: r.provider, model: r.model, input_tokens: r.input_tokens, output_tokens: r.output_tokens, total_tokens: r.total_tokens, cost: r.cost, response_time_ms: r.response_time_ms, is_stream: r.is_stream, created_at: r.created_at, status_code: r.status_code, input_price_per_1m: 3, output_price_per_1m: 15 })) }) }, 'GET /api/users/me/providers': async () => { await delay() return createMockResponse(MOCK_PROVIDERS.map(p => ({ id: p.id, name: p.name, is_active: p.is_active }))) }, 'GET /api/users/me/endpoint-status': async () => { await delay() return createMockResponse(MOCK_ENDPOINTS.map(e => ({ api_format: e.api_format, health_score: 1, is_active: e.is_active }))) }, 'GET /api/users/me/available-models': async () => { await delay() const models = MOCK_GLOBAL_MODELS.filter(model => model.is_active).map(model => ({ id: model.id, name: model.name, display_name: model.display_name, is_active: model.is_active, default_price_per_request: model.default_price_per_request ?? null, default_tiered_pricing: model.default_tiered_pricing, supported_capabilities: model.supported_capabilities ?? null, supports_embedding: model.supports_embedding ?? null, config: model.config ?? null, usage_count: model.usage_count ?? 0, })) return createMockResponse({ models, total: models.length }) }, 'GET /api/users/me/preferences': async () => { await delay() return createMockResponse(getCurrentProfile().preferences || { theme: 'auto', language: 'zh-CN' }) }, 'PUT /api/users/me/preferences': async () => { await delay() return createMockResponse({ message: '偏好设置已更新(演示模式)' }) }, 'GET /api/users/me/model-capabilities': async () => { await delay() return createMockResponse({ model_capability_settings: {} }) }, 'PUT /api/users/me/model-capabilities': async () => { await delay() return createMockResponse({ message: '已更新', model_capability_settings: {} }) }, // ========== Dashboard ========== 'GET /api/dashboard/stats': async () => { await delay() return createMockResponse(MOCK_DASHBOARD_STATS) }, 'GET /api/dashboard/recent-requests': async () => { await delay() return createMockResponse({ requests: MOCK_RECENT_REQUESTS }) }, 'GET /api/dashboard/provider-status': async () => { await delay() return createMockResponse({ providers: MOCK_PROVIDER_STATUS }) }, 'GET /api/dashboard/daily-stats': async () => { await delay() return createMockResponse(MOCK_DAILY_STATS) }, // ========== 公告 ========== 'GET /api/announcements': async () => { await delay() return createMockResponse({ items: MOCK_ANNOUNCEMENTS, total: MOCK_ANNOUNCEMENTS.length, unread_count: 1 }) }, 'GET /api/announcements/active': async () => { await delay() return createMockResponse({ items: MOCK_ANNOUNCEMENTS.filter(a => a.is_active), total: MOCK_ANNOUNCEMENTS.filter(a => a.is_active).length, unread_count: 1 }) }, 'GET /api/announcements/users/me/unread-count': async () => { await delay() return createMockResponse({ unread_count: 1 }) }, 'PATCH /api/announcements': async () => { await delay() return createMockResponse({ message: '已标记为已读' }) }, 'POST /api/announcements': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: `ann-demo-${Date.now()}`, title: body.title, message: '公告已创建(演示模式)' }) }, 'POST /api/announcements/read-all': async () => { await delay() return createMockResponse({ message: '已全部标记为已读' }) }, // ========== Admin: 用户管理 ========== 'GET /api/admin/users': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ALL_USERS) }, 'GET /api/admin/user-groups': async () => { await delay() requireAdmin() return createMockResponse({ items: [], default_group_id: null, }) }, 'POST /api/admin/users': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const newUser = { id: `user-demo-${Date.now()}`, username: body.username, email: body.email, role: body.role || 'user', unlimited: Boolean(body.unlimited), is_active: true, allowed_providers: null, allowed_api_formats: null, allowed_models: null, created_at: new Date().toISOString() } return createMockResponse(newUser) }, // ========== Admin: API Keys ========== 'GET /api/admin/api-keys': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ADMIN_API_KEYS) }, 'POST /api/admin/api-keys': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const newKey = { id: `standalone-demo-${Date.now()}`, key: `sk-sa-demo-${Math.random().toString(36).substring(2, 15)}`, user_id: 'demo-user-uuid-0002', name: body.name || '新独立 Key(演示)', key_display: 'sk-sa...demo', is_active: true, is_standalone: true, total_requests: 0, created_at: new Date().toISOString() } return createMockResponse(newKey) }, // ========== Admin: Providers ========== 'GET /api/admin/providers/summary': async () => { await delay() requireAdmin() return createMockResponse({ total: MOCK_PROVIDERS.length, page: 1, page_size: MOCK_PROVIDERS.length, items: MOCK_PROVIDERS, }) }, 'GET /api/admin/providers': async () => { await delay() requireAdmin() return createMockResponse(MOCK_PROVIDERS) }, 'GET /api/admin/pool/overview': async () => { await delay() requireAdmin() return createMockResponse({ items: [{ provider_id: MOCK_CODEX_POOL_PROVIDER_ID, provider_name: MOCK_CODEX_POOL_PROVIDER.name, provider_type: 'codex', total_keys: 4, active_keys: 4, cooldown_count: 0, pool_enabled: true, provider_hot_count: 2, provider_desired_hot: 3, provider_in_flight: 1, provider_ema_in_flight: 0.8, provider_burst_pending: false, }] }) }, 'GET /api/admin/pool/scheduling-presets': async () => { await delay() requireAdmin() return createMockResponse([]) }, 'POST /api/admin/providers': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ ...body, id: `provider-demo-${Date.now()}`, created_at: new Date().toISOString() }) }, // ========== Admin: Endpoints ========== 'GET /api/admin/endpoints/providers': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ENDPOINTS) }, 'GET /api/admin/endpoints': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ENDPOINTS) }, 'GET /api/admin/endpoints/health/summary': async () => { await delay() requireAdmin() return createMockResponse({ endpoints: { total: 6, active: 5, unhealthy: 1 }, keys: { total: 15, active: 12, unhealthy: 3 } }) }, 'GET /api/admin/endpoints/health/api-formats': async () => { await delay() requireAdmin() return createMockResponse({ ...MOCK_ENDPOINT_STATUS, formats: MOCK_ENDPOINT_STATUS.formats.map(withHealthTimelineDetails) }) }, 'GET /api/admin/endpoints/health/models': async () => { await delay() requireAdmin() return createMockResponse({ ...MOCK_MODEL_STATUS, models: MOCK_MODEL_STATUS.models.map(withHealthTimelineDetails) }) }, 'GET /api/admin/endpoints/health/providers': async () => { await delay() requireAdmin() return createMockResponse({ ...MOCK_PROVIDER_HEALTH_STATUS, providers: MOCK_PROVIDER_HEALTH_STATUS.providers.map(provider => ({ ...withHealthTimelineDetails(provider), models: provider.models.map(withHealthTimelineDetails) })) }) }, 'GET /api/admin/endpoints/health/related': async (config) => { await delay() requireAdmin() return createMockResponse(buildMockRelatedHealthResponse(config, true)) }, 'GET /api/admin/endpoints/keys': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ENDPOINT_KEYS) }, // ========== Admin: Global Models ========== 'GET /api/admin/models/global': async () => { await delay() requireAdmin() return createMockResponse({ models: MOCK_GLOBAL_MODELS, total: MOCK_GLOBAL_MODELS.length }) }, 'POST /api/admin/models/global': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ ...body, id: `gm-demo-${Date.now()}`, created_at: new Date().toISOString() }) }, // ========== Admin: Routing Profiles ========== 'GET /api/admin/routing/groups': async () => { await delay() requireAdmin() return createMockResponse({ items: MOCK_ROUTING_GROUPS.map(cloneMockRoutingGroup), total: MOCK_ROUTING_GROUPS.length, }) }, 'POST /api/admin/routing/groups': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') as Partial const now = Math.floor(Date.now() / 1000) const group: MockRoutingGroup = { id: body.id || `routing-demo-${Date.now()}`, name: body.name || '未命名调度策略', description: body.description ?? null, enabled: body.enabled ?? true, is_system_default: body.is_system_default ?? false, config_json: body.config_json ?? {}, version: 1, created_at: now, updated_at: now, published_at: null, } if (group.is_system_default) { unsetOtherMockRoutingDefaults(group.id) } MOCK_ROUTING_GROUPS.unshift(group) return createMockResponse(cloneMockRoutingGroup(group)) }, 'GET /api/admin/routing/bindings': async () => { await delay() requireAdmin() return createMockResponse({ items: MOCK_ROUTING_GROUP_BINDINGS.map(binding => ({ ...binding })), total: MOCK_ROUTING_GROUP_BINDINGS.length, }) }, 'POST /api/admin/routing/bindings': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') as Partial const now = Math.floor(Date.now() / 1000) const binding: MockRoutingGroupBinding = { id: body.id || `routing-binding-demo-${Date.now()}`, group_id: body.group_id || 'routing-default', subject_type: body.subject_type || 'api_key', subject_id: body.subject_id || 'demo', is_default: body.is_default ?? false, allow_explicit_select: body.allow_explicit_select ?? false, created_at: now, updated_at: now, } MOCK_ROUTING_GROUP_BINDINGS.unshift(binding) return createMockResponse({ ...binding }) }, // ========== Admin: Model Mappings / Aliases ========== 'GET /api/admin/models/mappings': async () => { await delay() requireAdmin() return createMockResponse(MOCK_ALIASES) }, 'POST /api/admin/models/mappings': async (config) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ ...body, id: `alias-demo-${Date.now()}`, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }) }, // ========== Admin: Usage ========== 'GET /api/admin/usage/stats': async () => { await delay() requireAdmin() const heatmap = getActivityHeatmap() const records = getUsageRecords() const totalRequests = records.length const totalTokens = records.reduce((sum, r) => sum + r.total_tokens, 0) const totalCost = records.reduce((sum, r) => sum + r.cost, 0) const totalActualCost = records.reduce((sum, r) => sum + (r.actual_cost || 0), 0) const avgResponseTime = records.filter(r => r.response_time_ms).reduce((sum, r) => sum + (r.response_time_ms || 0), 0) / records.filter(r => r.response_time_ms).length / 1000 // 今日数据 const today = new Date().toISOString().split('T')[0] const todayRecords = records.filter(r => r.created_at.startsWith(today)) return createMockResponse({ total_requests: totalRequests * 100, // 放大显示 total_tokens: totalTokens * 100, total_cost: Number((totalCost * 100).toFixed(2)), total_actual_cost: Number((totalActualCost * 100).toFixed(2)), avg_response_time: Number(avgResponseTime.toFixed(2)), today: { requests: todayRecords.length * 10, tokens: todayRecords.reduce((sum, r) => sum + r.total_tokens, 0) * 10, cost: Number((todayRecords.reduce((sum, r) => sum + r.cost, 0) * 10).toFixed(2)) }, activity_heatmap: heatmap }) }, 'GET /api/admin/usage/records': async (config) => { await delay() requireAdmin() let records = getUsageRecords() const params = config.params || {} const limit = parseInt(params.limit) || 20 const offset = parseInt(params.offset) || 0 // 通用搜索:用户名、密钥名、模型名、提供商名 // 支持空格分隔的组合搜索,多个关键词之间是 AND 关系 if (typeof params.search === 'string' && params.search.trim()) { const keywords = params.search.trim().toLowerCase().split(/\s+/) records = records.filter(r => { // 每个关键词都要匹配至少一个字段 return keywords.every((keyword: string) => (r.username || '').toLowerCase().includes(keyword) || (r.api_key?.name || '').toLowerCase().includes(keyword) || (r.model || '').toLowerCase().includes(keyword) || (r.provider || '').toLowerCase().includes(keyword) ) }) } return createMockResponse({ records: records.slice(offset, offset + limit), total: records.length, limit, offset }) }, 'GET /api/admin/usage/aggregation/stats': async (config) => { await delay() requireAdmin() const params = config.params || {} const groupBy = params.group_by || 'model' const records = getUsageRecords() if (groupBy === 'model') { // 按模型聚合 const modelStats = new Map() for (const r of records) { const existing = modelStats.get(r.model) || { request_count: 0, total_tokens: 0, total_cost: 0 } existing.request_count++ existing.total_tokens += r.total_tokens existing.total_cost += r.cost modelStats.set(r.model, existing) } return createMockResponse( Array.from(modelStats.entries()).map(([model, stats]) => ({ model, request_count: stats.request_count * 50, total_tokens: stats.total_tokens * 50, total_cost: Number((stats.total_cost * 50).toFixed(2)) })) ) } if (groupBy === 'provider') { const providerStats = new Map() for (const r of records) { const existing = providerStats.get(r.provider) || { request_count: 0, total_tokens: 0, total_cost: 0, actual_cost: 0, response_times: [], errors: 0 } existing.request_count++ existing.total_tokens += r.total_tokens existing.total_cost += r.cost existing.actual_cost += r.actual_cost || 0 if (r.response_time_ms) existing.response_times.push(r.response_time_ms) if (r.status === 'failed') existing.errors++ providerStats.set(r.provider, existing) } return createMockResponse( Array.from(providerStats.entries()).map(([provider, stats]) => ({ provider_id: `provider-${provider}`, provider, request_count: stats.request_count * 50, total_tokens: stats.total_tokens * 50, total_cost: Number((stats.total_cost * 50).toFixed(2)), actual_cost: Number((stats.actual_cost * 50).toFixed(2)), avg_response_time_ms: Math.round(stats.response_times.reduce((a, b) => a + b, 0) / stats.response_times.length || 0), success_rate: (stats.request_count - stats.errors) / stats.request_count, error_count: stats.errors * 50 })) ) } if (groupBy === 'user') { const userStats = new Map() for (const r of records) { const existing = userStats.get(r.user_id) || { user_id: r.user_id, username: r.username, email: r.user_email || '', request_count: 0, total_tokens: 0, total_cost: 0 } existing.request_count++ existing.total_tokens += r.total_tokens existing.total_cost += r.cost userStats.set(r.user_id, existing) } return createMockResponse( Array.from(userStats.values()).map(stats => ({ user_id: stats.user_id, email: stats.email, username: stats.username, request_count: stats.request_count * 50, total_tokens: stats.total_tokens * 50, total_cost: Number((stats.total_cost * 50).toFixed(2)) })) ) } if (groupBy === 'api_format') { const formatStats = new Map() for (const r of records) { const existing = formatStats.get(r.api_format) || { request_count: 0, total_tokens: 0, total_cost: 0, actual_cost: 0, response_times: [] } existing.request_count++ existing.total_tokens += r.total_tokens existing.total_cost += r.cost existing.actual_cost += r.actual_cost || 0 if (r.response_time_ms) existing.response_times.push(r.response_time_ms) formatStats.set(r.api_format, existing) } return createMockResponse( Array.from(formatStats.entries()).map(([api_format, stats]) => ({ api_format, request_count: stats.request_count * 50, total_tokens: stats.total_tokens * 50, total_cost: Number((stats.total_cost * 50).toFixed(2)), actual_cost: Number((stats.actual_cost * 50).toFixed(2)), avg_response_time_ms: Math.round(stats.response_times.reduce((a, b) => a + b, 0) / stats.response_times.length || 0) })) ) } return createMockResponse([]) }, 'GET /api/admin/usage/active': async () => { await delay() requireAdmin() return createMockResponse({ requests: [] }) }, // ========== Admin: Modules ========== 'GET /api/admin/modules/status': async () => { await delay() requireAdmin() return createMockResponse(MOCK_MODULE_STATUSES) }, // ========== Admin: System ========== 'GET /api/admin/system/configs': async () => { await delay() requireAdmin() return createMockResponse(MOCK_SYSTEM_CONFIGS) }, 'GET /api/admin/system/api-formats': async () => { await delay() return createMockResponse(MOCK_API_FORMATS) }, 'GET /api/admin/system/stats': async () => { await delay() requireAdmin() return createMockResponse({ total_requests_today: 1234, total_requests_month: 45678, total_users: 156, active_users_today: 28, total_cost_today: 45.67, total_cost_month: 1234.56, uptime_hours: 720, cache_hit_rate: 0.35 }) }, // ========== 能力接口 ========== 'GET /api/capabilities': async () => { await delay() return createMockResponse({ capabilities: MOCK_CAPABILITIES }) }, 'GET /api/capabilities/user-configurable': async () => { await delay() return createMockResponse({ capabilities: MOCK_CAPABILITIES.filter(c => c.match_mode === 'exclusive') }) }, // ========== 公开接口 ========== 'GET /api/public/global-models': async () => { await delay() return createMockResponse({ models: MOCK_GLOBAL_MODELS.map(m => ({ id: m.id, name: m.name, display_name: m.display_name, is_active: m.is_active, default_tiered_pricing: m.default_tiered_pricing, default_price_per_request: m.default_price_per_request, supported_capabilities: m.supported_capabilities, supports_embedding: m.supports_embedding, config: m.config })), total: MOCK_GLOBAL_MODELS.length }) }, 'GET /api/public/models': async () => { await delay() return createMockResponse({ models: MOCK_GLOBAL_MODELS.map(m => ({ name: m.name, display_name: m.display_name, description: m.config?.description })) }) }, 'GET /api/public/health': async () => { await delay(50) return createMockResponse({ status: 'healthy', demo_mode: true }) }, 'GET /api/public/health/api-formats': async () => { await delay() return createMockResponse({ generated_at: new Date().toISOString(), formats: MOCK_ENDPOINT_STATUS.formats.map(f => ({ api_format: f.api_format, api_path: f.api_path, total_attempts: f.total_attempts, success_count: f.success_count, failed_count: f.failed_count, skipped_count: f.skipped_count, success_rate: f.success_rate, avg_latency_ms: f.avg_latency_ms, avg_first_byte_ms: f.avg_first_byte_ms, avg_tps: f.avg_tps, last_event_at: f.last_event_at, events: f.events.slice(0, 10), timeline: f.timeline, timeline_details: withHealthTimelineDetails(f).timeline_details, time_range_start: f.time_range_start, time_range_end: f.time_range_end })) }) }, 'GET /api/public/health/models': async () => { await delay() return createMockResponse({ generated_at: new Date().toISOString(), models: MOCK_MODEL_STATUS.models.map(model => ({ model: model.model, display_name: model.display_name, total_attempts: model.total_attempts, success_count: model.success_count, failed_count: model.failed_count, success_rate: model.success_rate, avg_latency_ms: model.avg_latency_ms, avg_first_byte_ms: model.avg_first_byte_ms, avg_tps: model.avg_tps, last_event_at: model.last_event_at, events: model.events.slice(0, 10), timeline: model.timeline, timeline_details: withHealthTimelineDetails(model).timeline_details, time_range_start: model.time_range_start, time_range_end: model.time_range_end })) }) }, 'GET /api/public/health/related': async (config) => { await delay() return createMockResponse(buildMockRelatedHealthResponse(config, false)) } } // 动态路由匹配器 - 支持 :id 形式的参数 interface RouteMatch { handler: (config: AxiosRequestConfig, params: Record) => Promise> params: Record } type DynamicHandler = (config: AxiosRequestConfig, params: Record) => Promise> // 动态路由注册表 const dynamicRoutes: Array<{ method: string pattern: RegExp paramNames: string[] handler: DynamicHandler }> = [] /** * 注册动态路由 */ function registerDynamicRoute( method: string, path: string, handler: DynamicHandler ) { // 将 :param 形式转换为正则 const paramNames: string[] = [] const regexStr = path.replace(/:([^/]+)/g, (_, paramName) => { paramNames.push(paramName) return '([^/]+)' }) dynamicRoutes.push({ method: method.toUpperCase(), pattern: new RegExp(`^${regexStr}$`), paramNames, handler }) } /** * 匹配动态路由 */ function matchDynamicRoute(method: string, url: string): RouteMatch | null { const cleanUrl = url.split('?')[0] const upperMethod = method.toUpperCase() for (const route of dynamicRoutes) { if (route.method !== upperMethod) continue const match = cleanUrl.match(route.pattern) if (match) { const params: Record = {} route.paramNames.forEach((name, index) => { params[name] = match[index + 1] }) return { handler: route.handler, params } } } return null } /** * 匹配请求到 handler */ function matchHandler(method: string, url: string): ((config: AxiosRequestConfig) => Promise>) | null { // 移除查询参数 const cleanUrl = url.split('?')[0] const upperMethod = method.toUpperCase() // 精确匹配 const exactKey = `${upperMethod} ${cleanUrl}` if (mockHandlers[exactKey]) { return mockHandlers[exactKey] } // 动态路由匹配 const dynamicMatch = matchDynamicRoute(method, url) if (dynamicMatch) { return (config) => dynamicMatch.handler(config, dynamicMatch.params) } // 路径前缀匹配(按优先级排序) const sortedPatterns = Object.keys(mockHandlers).sort((a, b) => b.length - a.length) for (const pattern of sortedPatterns) { const [patternMethod, patternPath] = pattern.split(' ') if (patternMethod !== upperMethod) continue // 检查是否为前缀匹配(用于处理带 ID 的路由) if (cleanUrl.startsWith(patternPath) || patternPath === cleanUrl) { return mockHandlers[pattern] } } return null } /** * 处理 Mock 请求 */ export async function handleMockRequest(config: AxiosRequestConfig): Promise | null> { if (!isDemoMode()) { return null } const method = config.method?.toUpperCase() || 'GET' const url = config.url || '' // 尝试匹配 handler const handler = matchHandler(method, url) if (handler) { try { return await handler(config) } catch (error: unknown) { if ((error as Record)?.response) { throw error } log.error('Mock request handler failed', error) throw { response: createMockResponse({ detail: '模拟请求处理失败' }, 500) } } } // 未匹配的请求返回默认响应 log.warn('Mock request was not handled', { method, url }) return createMockResponse({ message: '演示模式:该接口暂未模拟', demo_mode: true }) } /** * 设置当前用户 token(供 client 初始化使用) */ export function setMockUserToken(token: string | null): void { currentUserToken = token } /** * 获取当前 mock token */ export function getMockUserToken(): string | null { return currentUserToken } // ========== Mock Provider Endpoints 数据 ========== // 为每个 provider 生成对应的 endpoints function generateMockEndpointsForProvider(providerId: string) { const provider = MOCK_PROVIDERS.find(p => p.id === providerId) if (!provider || provider.api_formats.length === 0) return [] return provider.api_formats.map((format, index) => { const normalizedFormat = normalizeApiFormat(format) const healthDetail = provider.endpoint_health_details.find(h => h.api_format === format) const baseUrl = normalizedFormat.includes('claude') ? 'https://api.anthropic.com/v1' : normalizedFormat.includes('openai') ? 'https://api.openai.com/v1' : normalizedFormat.includes('jina') ? 'https://api.jina.ai/v1' : 'https://generativelanguage.googleapis.com' return { id: `ep-${providerId}-${index + 1}`, provider_id: providerId, provider_name: provider.name, api_format: format, base_url: baseUrl, max_retries: 2, is_active: healthDetail?.is_active ?? true, total_keys: Math.ceil(Math.random() * 3) + 1, active_keys: Math.ceil(Math.random() * 2) + 1, created_at: provider.created_at, updated_at: new Date().toISOString(), ...getMockEndpointExtras(normalizedFormat) } }) } // 为 provider 生成 keys(Key 归属 Provider,通过 api_formats 关联) const PROVIDER_KEYS_CACHE: Record[]> = {} function generateMockKeysForProvider(providerId: string, count: number = 2) { const provider = MOCK_PROVIDERS.find(p => p.id === providerId) const formats = provider?.api_formats || [] const nowSec = Math.floor(Date.now() / 1000) return Array.from({ length: count }, (_, i) => { const isOAuth = i === 1 const markInvalid = isOAuth && providerId.endsWith('3') const oauthFields = isOAuth ? { auth_type: 'oauth', oauth_email: 'oauth-demo@aether.dev', oauth_expires_at: markInvalid ? null : nowSec + 6 * 3600, oauth_invalid_at: markInvalid ? nowSec - 3600 : null, oauth_invalid_reason: markInvalid ? '[ACCOUNT_BLOCK] Demo verification required' : null, status_snapshot: { oauth: { code: 'valid', label: '有效', reason: null, expires_at: nowSec + 6 * 3600, invalid_at: null, requires_reauth: false, expiring_soon: false }, account: markInvalid ? { code: 'account_verification', label: '需要验证', reason: 'Demo verification required', blocked: true, source: 'oauth_invalid', recoverable: false } : { code: 'ok', label: null, reason: null, blocked: false, source: null, recoverable: false }, quota: { code: 'unknown', label: null, reason: null, exhausted: false, usage_ratio: null, updated_at: null, reset_seconds: null, plan_type: null } }, oauth_plan_type: 'pro', oauth_account_id: `acct-${providerId}` } : { auth_type: 'api_key' } return { id: `key-${providerId}-${i + 1}`, provider_id: providerId, api_formats: i === 0 ? formats : formats.slice(0, 1), api_key_masked: `sk-***...${Math.random().toString(36).substring(2, 6)}`, name: i === 0 ? 'Primary Key' : `Backup Key ${i}`, ...oauthFields, rate_multiplier: 1.0, internal_priority: i + 1, health_score: 0.90 + Math.random() * 0.10, consecutive_failures: Math.random() > 0.8 ? 1 : 0, request_count: 1000 + Math.floor(Math.random() * 5000), success_count: 950 + Math.floor(Math.random() * 4800), error_count: Math.floor(Math.random() * 100), success_rate: 0.95 + Math.random() * 0.04, avg_response_time_ms: 800 + Math.floor(Math.random() * 600), cache_ttl_minutes: 5, max_probe_interval_minutes: 32, is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString() } }) } const deletedProviderModelIds = new Set() // 为 provider 生成 models function generateMockModelsForProvider(providerId: string) { const provider = MOCK_PROVIDERS.find(p => p.id === providerId) if (!provider) return [] // 基于 provider 的 api_formats 选择合适的模型 const hasClaude = provider.api_formats.some(f => f.includes('claude')) const hasOpenAI = provider.api_formats.some(f => f.includes('openai')) const hasGemini = provider.api_formats.some(f => f.includes('gemini')) const hasEmbedding = provider.api_formats.some(f => f.endsWith(':embedding') || f === 'aliyun:multimodal_embedding') const hasRerank = provider.api_formats.some(f => f.endsWith(':rerank')) const models: Record[] = [] const now = new Date().toISOString() if (hasClaude) { models.push( { id: `pm-${providerId}-claude-1`, provider_id: providerId, global_model_id: 'gm-003', provider_model_name: 'claude-sonnet-4-5-20250929', global_model_name: 'claude-sonnet-4-5-20250929', global_model_display_name: 'claude-sonnet-4-5', effective_input_price: 3.0, effective_output_price: 15.0, effective_supports_vision: true, effective_supports_function_calling: true, effective_supports_streaming: true, effective_supports_extended_thinking: true, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now }, { id: `pm-${providerId}-claude-2`, provider_id: providerId, global_model_id: 'gm-001', provider_model_name: 'claude-haiku-4-5-20251001', global_model_name: 'claude-haiku-4-5-20251001', global_model_display_name: 'claude-haiku-4-5', effective_input_price: 1.0, effective_output_price: 5.0, effective_supports_vision: true, effective_supports_function_calling: true, effective_supports_streaming: true, effective_supports_extended_thinking: true, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now } ) } if (hasOpenAI) { models.push( { id: `pm-${providerId}-openai-1`, provider_id: providerId, global_model_id: 'gm-006', provider_model_name: 'gpt-5.1', global_model_name: 'gpt-5.1', global_model_display_name: 'gpt-5.1', effective_input_price: 1.25, effective_output_price: 10.0, effective_supports_vision: true, effective_supports_function_calling: true, effective_supports_streaming: true, effective_supports_extended_thinking: true, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now }, { id: `pm-${providerId}-openai-2`, provider_id: providerId, global_model_id: 'gm-007', provider_model_name: 'gpt-5.1-codex', global_model_name: 'gpt-5.1-codex', global_model_display_name: 'gpt-5.1-codex', effective_input_price: 1.25, effective_output_price: 10.0, effective_supports_vision: true, effective_supports_function_calling: true, effective_supports_streaming: true, effective_supports_extended_thinking: true, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now } ) } if (hasEmbedding) { models.push({ id: `pm-${providerId}-embedding-1`, provider_id: providerId, global_model_id: 'gm-010', provider_model_name: 'text-embedding-3-small', global_model_name: 'text-embedding-3-small', global_model_display_name: 'text-embedding-3-small', effective_input_price: 0.02, effective_output_price: 0, supports_embedding: true, effective_supports_embedding: true, supports_streaming: false, effective_supports_streaming: false, config: { embedding: true, model_type: 'embedding', api_formats: ['openai:embedding'], }, effective_config: { embedding: true, model_type: 'embedding', api_formats: ['openai:embedding'], streaming: false, }, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now }) } if (hasRerank) { models.push({ id: `pm-${providerId}-rerank-1`, provider_id: providerId, global_model_id: 'gm-rerank-001', provider_model_name: 'bge-reranker-base', global_model_name: 'bge-reranker-base', global_model_display_name: 'bge-reranker-base', effective_input_price: 0.05, effective_output_price: 0, supports_streaming: false, effective_supports_streaming: false, config: { rerank: true, model_type: 'rerank', api_formats: ['openai:rerank'], }, effective_config: { rerank: true, model_type: 'rerank', api_formats: ['openai:rerank'], streaming: false, }, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now }) } if (hasGemini) { models.push( { id: `pm-${providerId}-gemini-1`, provider_id: providerId, global_model_id: 'gm-005', provider_model_name: 'gemini-3-pro-preview', global_model_name: 'gemini-3-pro-preview', global_model_display_name: 'gemini-3-pro-preview', effective_input_price: 2.0, effective_output_price: 12.0, effective_supports_vision: true, effective_supports_function_calling: true, effective_supports_streaming: true, effective_supports_extended_thinking: true, is_active: true, is_available: true, created_at: provider.created_at, updated_at: now } ) } return models.filter(model => !deletedProviderModelIds.has(String(model.id))) } // ========== 注册动态路由 ========== const WRITE_ONLY_SYSTEM_CONFIG_KEYS = new Set([ 'module.server_chan_push.send_key', 'module.bark_push.device_key', 'backup_s3_secret_access_key', ]) function mockSystemConfigValue(key: string) { return MOCK_SYSTEM_CONFIGS.find(item => item.key === key)?.value } function mockS3BackupConfigValidated() { return [ 'backup_s3_endpoint', 'backup_s3_bucket', 'backup_s3_access_key_id', 'backup_s3_secret_access_key', ].every(key => { const value = mockSystemConfigValue(key) return typeof value === 'string' && value.trim() !== '' }) } function refreshMockS3BackupModuleStatus() { const moduleStatus = MOCK_MODULE_STATUSES.s3_backup if (!moduleStatus) return const enabled = mockSystemConfigValue('backup_s3_enabled') === true const configValidated = mockS3BackupConfigValidated() MOCK_MODULE_STATUSES.s3_backup = { ...moduleStatus, enabled, config_validated: configValidated, config_error: configValidated ? null : '请先完成 S3 备份配置', active: moduleStatus.available && enabled && configValidated, } } function refreshMockModelDirectivesModuleStatus() { const moduleStatus = MOCK_MODULE_STATUSES.model_directives if (!moduleStatus) return const enabled = mockSystemConfigValue('enable_model_directives') === true MOCK_MODULE_STATUSES.model_directives = { ...moduleStatus, enabled, active: moduleStatus.available && enabled && moduleStatus.config_validated, } } // 系统配置详情 registerDynamicRoute('GET', '/api/admin/system/configs/:configKey', async (_config, params) => { await delay() requireAdmin() const key = decodeURIComponent(params.configKey) const entry = MOCK_SYSTEM_CONFIGS.find(item => item.key === key) if (!entry) { throw { response: createMockResponse({ detail: `配置项 '${key}' 不存在` }, 404) } } if (WRITE_ONLY_SYSTEM_CONFIG_KEYS.has(key)) { return createMockResponse({ key: entry.key, value: null, description: entry.description, is_set: typeof entry.value === 'string' && entry.value.trim() !== '', }) } return createMockResponse({ key: entry.key, value: entry.value, description: entry.description }) }) // 系统配置更新 registerDynamicRoute('PUT', '/api/admin/system/configs/:configKey', async (config, params) => { await delay() requireAdmin() const key = decodeURIComponent(params.configKey) const body = JSON.parse(config.data || '{}') as { value?: unknown; description?: string } const index = MOCK_SYSTEM_CONFIGS.findIndex(item => item.key === key) const entry = { key, value: body.value ?? null, description: body.description, } if (index === -1) { MOCK_SYSTEM_CONFIGS.push(entry) } else { MOCK_SYSTEM_CONFIGS[index] = { ...MOCK_SYSTEM_CONFIGS[index], ...entry, } } if (key.startsWith('backup_s3_')) { refreshMockS3BackupModuleStatus() } if (key === 'enable_model_directives') { refreshMockModelDirectivesModuleStatus() } return createMockResponse(entry) }) registerDynamicRoute('POST', '/api/admin/system/backups/s3/run', async () => { await delay() requireAdmin() return createMockResponse({ message: 'S3 备份任务已提交', task: { id: `mock-s3-backup-${Date.now()}`, task_key: 'system.s3.backup', status: 'queued', progress_message: 'S3 备份任务已提交', }, }) }) // 模块状态详情 registerDynamicRoute('GET', '/api/admin/modules/status/:moduleName', async (_config, params) => { await delay() requireAdmin() const moduleStatus = MOCK_MODULE_STATUSES[params.moduleName] if (!moduleStatus) { throw { response: createMockResponse({ detail: '模块不存在' }, 404) } } return createMockResponse(moduleStatus) }) // 模块启用状态更新 registerDynamicRoute('PUT', '/api/admin/modules/status/:moduleName/enabled', async (config, params) => { await delay() requireAdmin() const moduleStatus = MOCK_MODULE_STATUSES[params.moduleName] if (!moduleStatus) { throw { response: createMockResponse({ detail: '模块不存在' }, 404) } } const body = JSON.parse(config.data || '{}') as { enabled?: boolean } const enabled = body.enabled === true if (params.moduleName === 'model_directives') { const index = MOCK_SYSTEM_CONFIGS.findIndex(item => item.key === 'enable_model_directives') const entry = { key: 'enable_model_directives', value: enabled, description: '模型后缀参数模块开关', } if (index === -1) { MOCK_SYSTEM_CONFIGS.push(entry) } else { MOCK_SYSTEM_CONFIGS[index] = { ...MOCK_SYSTEM_CONFIGS[index], ...entry } } refreshMockModelDirectivesModuleStatus() return createMockResponse(MOCK_MODULE_STATUSES.model_directives) } if (params.moduleName === 's3_backup') { const index = MOCK_SYSTEM_CONFIGS.findIndex(item => item.key === 'backup_s3_enabled') const entry = { key: 'backup_s3_enabled', value: enabled, description: 'S3 自动备份开关', } if (index === -1) { MOCK_SYSTEM_CONFIGS.push(entry) } else { MOCK_SYSTEM_CONFIGS[index] = { ...MOCK_SYSTEM_CONFIGS[index], ...entry } } refreshMockS3BackupModuleStatus() return createMockResponse(MOCK_MODULE_STATUSES.s3_backup) } const updated = { ...moduleStatus, enabled, active: moduleStatus.available && enabled && moduleStatus.config_validated, } MOCK_MODULE_STATUSES[params.moduleName] = updated return createMockResponse(updated) }) // Provider 详情 registerDynamicRoute('GET', '/api/admin/providers/:providerId/summary', async (_config, params) => { await delay() requireAdmin() if (params.providerId === MOCK_CODEX_POOL_PROVIDER_ID) { return createMockResponse(MOCK_CODEX_POOL_PROVIDER) } const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId) if (!provider) { throw { response: createMockResponse({ detail: '提供商不存在' }, 404) } } return createMockResponse(provider) }) registerDynamicRoute('GET', '/api/admin/pool/:providerId/keys', async (config, params) => { await delay() requireAdmin() if (params.providerId !== MOCK_CODEX_POOL_PROVIDER_ID) { return createMockResponse({ total: 0, page: 1, page_size: 50, keys: [] }) } const query = (config.params || {}) as Record const search = String(query.search || '').trim().toLowerCase() const status = String(query.status || 'all').trim().toLowerCase() const sortBy = String(query.sort_by || 'imported_at').trim() const sortOrder = String(query.sort_order || 'desc').trim().toLowerCase() let keys = createMockCodexPoolKeys() if (search) { keys = keys.filter(key => [ key.key_name, key.oauth_plan_type, key.oauth_account_id, key.account_quota, ].some(value => String(value || '').toLowerCase().includes(search))) } if (status === 'enabled') { keys = keys.filter(key => key.is_active) } else if (status === 'disabled') { keys = keys.filter(key => !key.is_active) } else if (status !== 'all') { keys = keys.filter(key => key.scheduling_status === status || key.scheduling_reason === status) } keys.sort((left, right) => { const leftValue = String((left as Record)[sortBy] ?? left.imported_at ?? '') const rightValue = String((right as Record)[sortBy] ?? right.imported_at ?? '') const comparison = leftValue.localeCompare(rightValue) return sortOrder === 'asc' ? comparison : -comparison }) const rawPage = Number(query.page) const rawPageSize = Number(query.page_size) const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : 1 const pageSize = Number.isFinite(rawPageSize) && rawPageSize >= 1 ? Math.floor(rawPageSize) : 50 const start = (page - 1) * pageSize return createMockResponse({ total: keys.length, page, page_size: pageSize, keys: keys.slice(start, start + pageSize), }) }) // Provider 模型映射预览 registerDynamicRoute('GET', '/api/admin/providers/:providerId/mapping-preview', async (_config, params) => { await delay() requireAdmin() const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId) if (!provider) { throw { response: createMockResponse({ detail: '提供商不存在' }, 404) } } return createMockResponse({ provider_id: provider.id, provider_name: provider.name, keys: [], total_keys: 0, total_matches: 0, truncated: false, truncated_keys: 0, truncated_models: 0, }) }) // Provider 更新 registerDynamicRoute('PATCH', '/api/admin/providers/:providerId', async (config, params) => { await delay() requireAdmin() const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId) if (!provider) { throw { response: createMockResponse({ detail: '提供商不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...provider, ...body, updated_at: new Date().toISOString() }) }) // Provider 删除 registerDynamicRoute('DELETE', '/api/admin/providers/:providerId', async (_config, params) => { await delay() requireAdmin() const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId) if (!provider) { throw { response: createMockResponse({ detail: '提供商不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // Provider Endpoints 列表 registerDynamicRoute('GET', '/api/admin/endpoints/providers/:providerId/endpoints', async (_config, params) => { await delay() requireAdmin() const endpoints = generateMockEndpointsForProvider(params.providerId) return createMockResponse(endpoints) }) // 创建 Endpoint registerDynamicRoute('POST', '/api/admin/endpoints/providers/:providerId/endpoints', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: `ep-demo-${Date.now()}`, provider_id: params.providerId, ...body, created_at: new Date().toISOString() }) }) // Endpoint 详情 registerDynamicRoute('GET', '/api/admin/endpoints/:endpointId', async (_config, params) => { await delay() requireAdmin() // 从所有 providers 的 endpoints 中查找 for (const provider of MOCK_PROVIDERS) { const endpoints = generateMockEndpointsForProvider(provider.id) const endpoint = endpoints.find(e => e.id === params.endpointId) if (endpoint) { return createMockResponse(endpoint) } } throw { response: createMockResponse({ detail: '端点不存在' }, 404) } }) // Endpoint 更新 registerDynamicRoute('PUT', '/api/admin/endpoints/:endpointId', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: params.endpointId, ...body, updated_at: new Date().toISOString() }) }) // Endpoint 删除 registerDynamicRoute('DELETE', '/api/admin/endpoints/:endpointId', async (_config, _params) => { await delay() requireAdmin() return createMockResponse({ message: '删除成功(演示模式)', affected_keys_count: 0 }) }) // Provider Keys 列表 registerDynamicRoute('GET', '/api/admin/endpoints/providers/:providerId/keys', async (config, params) => { await delay() requireAdmin() if (!PROVIDER_KEYS_CACHE[params.providerId]) { PROVIDER_KEYS_CACHE[params.providerId] = generateMockKeysForProvider(params.providerId, 2) } const keys = PROVIDER_KEYS_CACHE[params.providerId] const query = config.params || {} // 当前详情抽屉使用 page/page_size 分页;其他调用仍使用 skip/limit 并期望裸数组。 if (query.page !== undefined || query.page_size !== undefined) { const rawPage = Number(query.page) const rawPageSize = Number(query.page_size) const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : 1 const pageSize = Number.isFinite(rawPageSize) && rawPageSize >= 1 ? Math.floor(rawPageSize) : 20 const start = (page - 1) * pageSize return createMockResponse({ total: keys.length, page, page_size: pageSize, keys: keys.slice(start, start + pageSize), }) } const rawSkip = Number(query.skip) const rawLimit = Number(query.limit) const skip = Number.isFinite(rawSkip) && rawSkip >= 0 ? Math.floor(rawSkip) : 0 const limit = Number.isFinite(rawLimit) && rawLimit >= 1 ? Math.floor(rawLimit) : keys.length return createMockResponse(keys.slice(skip, skip + limit)) }) // 为 Provider 创建 Key registerDynamicRoute('POST', '/api/admin/endpoints/providers/:providerId/keys', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const apiKeyPlain = body.api_key || 'sk-demo' const masked = apiKeyPlain.length >= 12 ? `${apiKeyPlain.slice(0, 8)}***${apiKeyPlain.slice(-4)}` : 'sk-***...demo' const newKey = { id: `key-demo-${Date.now()}`, provider_id: params.providerId, api_formats: body.api_formats || [], api_key_masked: masked, api_key_plain: null, auth_type: body.auth_type || 'api_key', name: body.name || 'New Key', note: body.note, rate_multiplier: body.rate_multiplier ?? 1.0, rate_multipliers: body.rate_multipliers ?? null, internal_priority: body.internal_priority ?? 50, global_priority: body.global_priority ?? null, rpm_limit: body.rpm_limit ?? null, allowed_models: body.allowed_models ?? null, capabilities: body.capabilities ?? null, cache_ttl_minutes: body.cache_ttl_minutes ?? 5, max_probe_interval_minutes: body.max_probe_interval_minutes ?? 32, health_score: 1.0, consecutive_failures: 0, request_count: 0, success_count: 0, error_count: 0, success_rate: 0.0, avg_response_time_ms: 0.0, is_active: true, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), } if (!PROVIDER_KEYS_CACHE[params.providerId]) { PROVIDER_KEYS_CACHE[params.providerId] = [] } PROVIDER_KEYS_CACHE[params.providerId].push(newKey) return createMockResponse(newKey) }) registerDynamicRoute('POST', '/api/admin/endpoints/providers/:providerId/refresh-quota', async (config, params) => { await delay() requireAdmin() if (params.providerId === MOCK_CODEX_POOL_PROVIDER_ID) { const body = JSON.parse(config.data || '{}') const requestedKeyIds = Array.isArray(body.key_ids) ? body.key_ids.map((id: unknown) => String(id).trim()).filter(Boolean) : createMockCodexPoolKeys().map(key => key.key_id) const keyNames = new Map(createMockCodexPoolKeys().map(key => [key.key_id, key.key_name])) const results = requestedKeyIds.map((keyId: string) => ({ key_id: keyId, key_name: keyNames.get(keyId) || keyId, status: 'success', metadata: { updated_at: new Date().toISOString() }, })) return createMockResponse({ success: results.length, failed: 0, total: results.length, results, }) } if (!PROVIDER_KEYS_CACHE[params.providerId]) { PROVIDER_KEYS_CACHE[params.providerId] = generateMockKeysForProvider(params.providerId, 2) } const body = JSON.parse(config.data || '{}') const requestedKeyIds = Array.isArray(body.key_ids) ? new Set(body.key_ids.map((id: unknown) => String(id).trim()).filter(Boolean)) : null const keys = (PROVIDER_KEYS_CACHE[params.providerId] || []) .filter(key => !requestedKeyIds || requestedKeyIds.has(key.id)) const results = keys.map(key => ({ key_id: key.id, key_name: key.name || String(key.id).slice(0, 8), status: 'success', metadata: { updated_at: new Date().toISOString() } })) return createMockResponse({ success: results.length, failed: 0, total: results.length, results }) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/keys/:keyId/refresh', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ provider_type: 'codex', expires_at: Math.floor(Date.now() / 1000) + 6 * 3600, has_refresh_token: true, email: 'oauth-demo@aether.dev', key_id: params.keyId }) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/start', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ authorization_url: `https://example.com/oauth/authorize?provider=${params.providerId}`, redirect_uri: 'https://aether.local/oauth/callback', provider_type: 'codex', instructions: 'Open the authorization URL and paste the callback URL here.' }) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/complete', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ key_id: `key-oauth-${Date.now()}`, provider_type: 'codex', expires_at: Math.floor(Date.now() / 1000) + 24 * 3600, has_refresh_token: true, email: body.name ? `${body.name}@demo.dev` : 'oauth-demo@aether.dev' }) }) const mockClaudeCookieAuthorizeTasks = new Map>() let mockClaudeCookieAuthorizeTaskSequence = 0 registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/cookie-authorize/tasks', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const cookies = Array.isArray(body.cookies) ? body.cookies.filter((cookie: unknown): cookie is string => typeof cookie === 'string' && cookie.trim().length > 0) : [] const errorSamples = cookies.flatMap((cookie: string, index: number) => ( cookie.includes('mock-fail') ? [{ index, status: 'error', error: '演示模式:Cookie 授权失败', replaced: false }] : [] )) const failed = errorSamples.length const success = cookies.length - failed const now = Math.floor(Date.now() / 1000) const taskId = `claude-cookie-${Date.now()}-${++mockClaudeCookieAuthorizeTaskSequence}` mockClaudeCookieAuthorizeTasks.set(taskId, { task_id: taskId, provider_id: params.providerId, provider_type: 'claude_code', import_kind: 'cookie_authorize', status: 'completed', total: cookies.length, processed: cookies.length, success, failed, created_count: success, replaced_count: 0, progress_percent: 100, message: null, error: null, error_samples: errorSamples, created_at: now, started_at: now, finished_at: now, updated_at: now, }) return createMockResponse({ task_id: taskId, import_kind: 'cookie_authorize', status: 'submitted', total: cookies.length, processed: 0, success: 0, failed: 0, created_count: 0, replaced_count: 0, progress_percent: 0, message: '任务已提交', }) }) registerDynamicRoute('GET', '/api/admin/provider-oauth/providers/:providerId/cookie-authorize/tasks/:taskId', async (_config, params) => { await delay() requireAdmin() const task = mockClaudeCookieAuthorizeTasks.get(params.taskId) if (!task || task.provider_id !== params.providerId) { throw { response: createMockResponse({ detail: 'Cookie 授权任务不存在' }, 404) } } return createMockResponse(task) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/cookie-authorize', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ key_id: `key-claude-cookie-${Date.now()}`, provider_type: 'claude_code', expires_at: Math.floor(Date.now() / 1000) + 24 * 3600, has_refresh_token: true, email: body.name ? `${body.name}@demo.dev` : 'claude-oauth-demo@aether.dev' }) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/import-refresh-token', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ key_id: `key-oauth-${Date.now()}`, provider_type: 'codex', expires_at: Math.floor(Date.now() / 1000) + 24 * 3600, has_refresh_token: true, email: body.name ? `${body.name}@demo.dev` : 'oauth-demo@aether.dev' }) }) registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/batch-import', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const raw: string = typeof body.credentials === 'string' ? body.credentials.trim() : '' const lines = raw ? raw.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')) : [] const total = Math.max(Math.min(lines.length, 5), 2) const results = [] for (let index = 0; index < total; index++) { results.push({ index, status: 'success', key_id: `key-oauth-${Date.now()}-${index}`, key_name: `Imported OAuth ${index + 1}`, auth_method: 'oauth' }) } return createMockResponse({ total, success: results.length, failed: 0, results }) }) // Key 更新 registerDynamicRoute('PUT', '/api/admin/endpoints/keys/:keyId', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: params.keyId, ...body, updated_at: new Date().toISOString() }) }) // Key 删除 registerDynamicRoute('DELETE', '/api/admin/endpoints/keys/:keyId', async (_config, _params) => { await delay() requireAdmin() return createMockResponse({ message: '删除成功(演示模式)' }) }) // Key Reveal registerDynamicRoute('GET', '/api/admin/endpoints/keys/:keyId/reveal', async (_config, _params) => { await delay() requireAdmin() return createMockResponse({ api_key: 'sk-demo-reveal' }) }) registerDynamicRoute('GET', '/api/admin/endpoints/keys/:keyId/export', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ key_id: params.keyId, provider_type: 'codex', auth_method: 'oauth', refresh_token: 'rt-demo', email: 'oauth-demo@aether.dev', exported_at: new Date().toISOString() }) }) registerDynamicRoute('POST', '/api/admin/endpoints/keys/:keyId/clear-oauth-invalid', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ message: 'OAuth invalid cleared (demo)', key_id: params.keyId }) }) registerDynamicRoute('POST', '/api/admin/endpoints/keys/:keyId/reset-cycle-stats', async (_config, params) => { await delay() requireAdmin() const key = createMockCodexPoolKeys().find(item => item.key_id === params.keyId) const windows = key?.status_snapshot.quota.windows.filter(window => ( window.window_minutes > 0 && !window.code.startsWith('spark_') )).length ?? 0 return createMockResponse({ message: '已重置周期统计(演示模式)', reset_at: Math.floor(Date.now() / 1000), windows, }) }) // Keys grouped by format mockHandlers['GET /api/admin/endpoints/keys/grouped-by-format'] = async () => { await delay() requireAdmin() // 确保每个 provider 都有 key 数据 for (const provider of MOCK_PROVIDERS) { if (!PROVIDER_KEYS_CACHE[provider.id]) { PROVIDER_KEYS_CACHE[provider.id] = generateMockKeysForProvider(provider.id, 2) } } const grouped: Record[]> = {} for (const provider of MOCK_PROVIDERS) { const endpoints = generateMockEndpointsForProvider(provider.id) const baseUrlByFormat = Object.fromEntries(endpoints.map(e => [e.api_format, e.base_url])) const keys = PROVIDER_KEYS_CACHE[provider.id] || [] for (const key of keys) { const formats = Array.isArray(key.api_formats) ? key.api_formats.filter((format): format is string => typeof format === 'string') : [] for (const fmt of formats) { if (!grouped[fmt]) grouped[fmt] = [] grouped[fmt].push({ ...key, api_format: fmt, provider_name: provider.name, endpoint_base_url: baseUrlByFormat[fmt], global_priority: key.global_priority ?? null, circuit_breaker_open: false, capabilities: [], }) } } } return createMockResponse(grouped) } // Provider Models 列表 registerDynamicRoute('GET', '/api/admin/providers/:providerId/models', async (_config, params) => { await delay() requireAdmin() const models = generateMockModelsForProvider(params.providerId) return createMockResponse(models) }) // Provider Model 详情 registerDynamicRoute('GET', '/api/admin/providers/:providerId/models/:modelId', async (_config, params) => { await delay() requireAdmin() const models = generateMockModelsForProvider(params.providerId) const model = models.find(m => m.id === params.modelId) if (!model) { throw { response: createMockResponse({ detail: '模型不存在' }, 404) } } return createMockResponse(model) }) // 创建 Provider Model registerDynamicRoute('POST', '/api/admin/providers/:providerId/models', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: `pm-demo-${Date.now()}`, provider_id: params.providerId, ...body, created_at: new Date().toISOString() }) }) // 更新 Provider Model registerDynamicRoute('PATCH', '/api/admin/providers/:providerId/models/:modelId', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') return createMockResponse({ id: params.modelId, provider_id: params.providerId, ...body, updated_at: new Date().toISOString() }) }) // 删除 Provider Model registerDynamicRoute('DELETE', '/api/admin/providers/:providerId/models/:modelId', async (_config, params) => { await delay() requireAdmin() deletedProviderModelIds.add(params.modelId) return createMockResponse({ message: '删除成功(演示模式)' }) }) // 批量创建 Provider Models registerDynamicRoute('POST', '/api/admin/providers/:providerId/models/batch', async (config, params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const models = ((body.models || []) as Record[]).map((m: Record, i: number) => ({ id: `pm-demo-${Date.now()}-${i}`, provider_id: params.providerId, ...m, created_at: new Date().toISOString() })) return createMockResponse({ models, created_count: models.length }) }) // Provider 可用源模型 registerDynamicRoute('GET', '/api/admin/providers/:providerId/available-source-models', async (_config, params) => { await delay() requireAdmin() const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId) if (!provider) { throw { response: createMockResponse({ detail: '提供商不存在' }, 404) } } // 返回一些可用的源模型 const availableModels = [ 'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001', 'claude-opus-4-5-20251101', 'gpt-5.1', 'gpt-5.1-codex', 'gemini-3-pro-preview' ] return createMockResponse({ models: availableModels }) }) // 分配 GlobalModels 到 Provider registerDynamicRoute('POST', '/api/admin/providers/:providerId/assign-global-models', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const result = { success: (body.global_model_ids || []).map((id: string) => ({ global_model_id: id, provider_model_id: `pm-demo-${Date.now()}-${id}` })), errors: [] } return createMockResponse(result) }) // GlobalModel 详情 registerDynamicRoute('GET', '/api/admin/models/global/:modelId', async (_config, params) => { await delay() requireAdmin() const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId) if (!model) { throw { response: createMockResponse({ detail: '模型不存在' }, 404) } } return createMockResponse(model) }) // GlobalModel 更新 registerDynamicRoute('PATCH', '/api/admin/models/global/:modelId', async (config, params) => { await delay() requireAdmin() const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId) if (!model) { throw { response: createMockResponse({ detail: '模型不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...model, ...body, updated_at: new Date().toISOString() }) }) // GlobalModel 删除 registerDynamicRoute('DELETE', '/api/admin/models/global/:modelId', async (_config, params) => { await delay() requireAdmin() const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId) if (!model) { throw { response: createMockResponse({ detail: '模型不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // GlobalModel 批量分配到 Providers registerDynamicRoute('POST', '/api/admin/models/global/:modelId/assign-to-providers', async (config, _params) => { await delay() requireAdmin() const body = JSON.parse(config.data || '{}') const result = { success: (body.provider_ids || []).map((providerId: string) => { const provider = MOCK_PROVIDERS.find(p => p.id === providerId) return { provider_id: providerId, provider_name: provider?.name || 'unknown', model_id: `pm-demo-${Date.now()}-${providerId}` } }), errors: [] } return createMockResponse(result) }) registerDynamicRoute('GET', '/api/admin/routing/groups/:groupId', async (_config, params) => { await delay() requireAdmin() const group = MOCK_ROUTING_GROUPS.find(item => item.id === params.groupId) if (!group) { throw { response: createMockResponse({ detail: '调度策略不存在' }, 404) } } return createMockResponse(cloneMockRoutingGroup(group)) }) registerDynamicRoute('PATCH', '/api/admin/routing/groups/:groupId', async (config, params) => { await delay() requireAdmin() const index = MOCK_ROUTING_GROUPS.findIndex(item => item.id === params.groupId) if (index < 0) { throw { response: createMockResponse({ detail: '调度策略不存在' }, 404) } } const body = JSON.parse(config.data || '{}') as Partial const current = MOCK_ROUTING_GROUPS[index] const now = Math.floor(Date.now() / 1000) const updated: MockRoutingGroup = { ...current, ...body, id: current.id, config_json: body.config_json ?? current.config_json, version: body.config_json ? current.version + 1 : (body.version ?? current.version), updated_at: now, } if (updated.is_system_default) { unsetOtherMockRoutingDefaults(updated.id) } MOCK_ROUTING_GROUPS[index] = updated return createMockResponse(cloneMockRoutingGroup(updated)) }) registerDynamicRoute('DELETE', '/api/admin/routing/groups/:groupId', async (_config, params) => { await delay() requireAdmin() const index = MOCK_ROUTING_GROUPS.findIndex(item => item.id === params.groupId) if (index < 0) { throw { response: createMockResponse({ detail: '调度策略不存在' }, 404) } } MOCK_ROUTING_GROUPS.splice(index, 1) return createMockResponse({ message: '删除成功(演示模式)' }) }) registerDynamicRoute('POST', '/api/admin/routing/groups/:groupId/publish', async (_config, params) => { await delay() requireAdmin() const group = MOCK_ROUTING_GROUPS.find(item => item.id === params.groupId) if (!group) { throw { response: createMockResponse({ detail: '调度策略不存在' }, 404) } } const now = Math.floor(Date.now() / 1000) group.published_at = now group.updated_at = now MOCK_ROUTING_GROUP_VERSIONS.unshift({ id: `${group.id}-v${group.version}-${now}`, group_id: group.id, version: group.version, config_json: group.config_json, created_at: now, created_by: null, }) return createMockResponse(cloneMockRoutingGroup(group)) }) registerDynamicRoute('GET', '/api/admin/routing/groups/:groupId/versions', async (_config, params) => { await delay() requireAdmin() const versions = MOCK_ROUTING_GROUP_VERSIONS .filter(version => version.group_id === params.groupId) .map(cloneMockRoutingVersion) return createMockResponse({ items: versions, total: versions.length }) }) registerDynamicRoute('POST', '/api/admin/routing/groups/:groupId/dry-run', async (config, params) => { await delay() requireAdmin() const group = MOCK_ROUTING_GROUPS.find(item => item.id === params.groupId) if (!group) { throw { response: createMockResponse({ detail: '调度策略不存在' }, 404) } } const body = JSON.parse(config.data || '{}') as { model?: string resolved_model?: string api_format?: string headers?: Record body?: unknown } const model = body.model || 'gpt-5.1' const resolvedModel = body.resolved_model || model const rules = Array.isArray(group.config_json.rules) ? group.config_json.rules as Array<{ id?: unknown; enabled?: unknown }> : [] const selectedRules = rules .filter(rule => rule.enabled !== false && typeof rule.id === 'string') .map(rule => String(rule.id)) const traceSeed = { group_id: group.id, group_version: group.version, selection_source: 'admin_dry_run', selected_rules: selectedRules, original_model: model, resolved_model: resolvedModel, client_api_format: body.api_format || 'openai:chat', global_candidates: [ { candidate_kind: 'provider', provider_id: 'provider-002', endpoint_id: 'ep-002', model_id: resolvedModel, key_id: 'ekey-003', ranking_vector: { provider_priority_before: 0, provider_priority_after: 0, key_priority_before: 0, key_priority_after: 0, }, skip_reason: null, selected_order: 0, }, ], pool_expansion: [], runtime_facts: { scheduler_mode: 'cache_affinity', priority_mode: 'provider', }, } return createMockResponse({ group: cloneMockRoutingGroup(group), policy: { selected_rules: selectedRules, ranking_overlay: {}, }, trace_seed: traceSeed, patch_summary: { body_paths: [], header_names: [], failed_action: null }, mutated_body: body.body ?? { model }, mutated_headers: body.headers ?? {}, candidate_preview: { status: 'policy_only', ranking_overlay: {}, note: '演示模式候选预览', }, }) }) registerDynamicRoute('PATCH', '/api/admin/routing/bindings/:bindingId', async (config, params) => { await delay() requireAdmin() const index = MOCK_ROUTING_GROUP_BINDINGS.findIndex(item => item.id === params.bindingId) if (index < 0) { throw { response: createMockResponse({ detail: '调度绑定不存在' }, 404) } } const body = JSON.parse(config.data || '{}') as Partial MOCK_ROUTING_GROUP_BINDINGS[index] = { ...MOCK_ROUTING_GROUP_BINDINGS[index], ...body, id: MOCK_ROUTING_GROUP_BINDINGS[index].id, updated_at: Math.floor(Date.now() / 1000), } return createMockResponse({ ...MOCK_ROUTING_GROUP_BINDINGS[index] }) }) registerDynamicRoute('DELETE', '/api/admin/routing/bindings/:bindingId', async (_config, params) => { await delay() requireAdmin() const index = MOCK_ROUTING_GROUP_BINDINGS.findIndex(item => item.id === params.bindingId) if (index < 0) { throw { response: createMockResponse({ detail: '调度绑定不存在' }, 404) } } MOCK_ROUTING_GROUP_BINDINGS.splice(index, 1) return createMockResponse({ message: '删除成功(演示模式)' }) }) // Endpoint Health 详情 registerDynamicRoute('GET', '/api/admin/endpoints/health/endpoint/:endpointId', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ endpoint_id: params.endpointId, health_score: 0.95, total_requests: 5000, success_count: 4750, failed_count: 250, success_rate: 0.95, avg_response_time_ms: 1200, last_success_at: new Date().toISOString(), last_failure_at: new Date(Date.now() - 3600000).toISOString() }) }) // Key Health 详情 registerDynamicRoute('GET', '/api/admin/endpoints/health/key/:keyId', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ key_id: params.keyId, health_score: 0.92, total_requests: 2000, success_count: 1840, failed_count: 160, success_rate: 0.92, avg_response_time_ms: 1100, last_success_at: new Date().toISOString(), last_failure_at: new Date(Date.now() - 7200000).toISOString() }) }) registerDynamicRoute('PATCH', '/api/admin/endpoints/health/keys', async () => { await delay() requireAdmin() return createMockResponse({ message: 'All key health recovered (demo)', recovered_count: 2, recovered_keys: [ { key_id: 'key-demo-1', key_name: 'Primary Key', endpoint_id: 'ep-demo-1' }, { key_id: 'key-demo-2', key_name: 'Backup Key', endpoint_id: 'ep-demo-2' } ] }) }) // 重置 Key Health registerDynamicRoute('PATCH', '/api/admin/endpoints/health/keys/:keyId', async (_config, params) => { await delay() requireAdmin() return createMockResponse({ key_id: params.keyId, message: '健康状态已重置(演示模式)' }) }) // Alias/Mapping 详情 registerDynamicRoute('GET', '/api/admin/models/mappings/:mappingId', async (_config, params) => { await delay() requireAdmin() const alias = MOCK_ALIASES.find(a => a.id === params.mappingId) if (!alias) { throw { response: createMockResponse({ detail: '映射不存在' }, 404) } } return createMockResponse(alias) }) // Alias/Mapping 更新 registerDynamicRoute('PATCH', '/api/admin/models/mappings/:mappingId', async (config, params) => { await delay() requireAdmin() const alias = MOCK_ALIASES.find(a => a.id === params.mappingId) if (!alias) { throw { response: createMockResponse({ detail: '映射不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...alias, ...body, updated_at: new Date().toISOString() }) }) // Alias/Mapping 删除 registerDynamicRoute('DELETE', '/api/admin/models/mappings/:mappingId', async (_config, params) => { await delay() requireAdmin() const alias = MOCK_ALIASES.find(a => a.id === params.mappingId) if (!alias) { throw { response: createMockResponse({ detail: '映射不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // 公告详情 registerDynamicRoute('GET', '/api/announcements/:announcementId', async (_config, params) => { await delay() const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId) if (!announcement) { throw { response: createMockResponse({ detail: '公告不存在' }, 404) } } return createMockResponse(announcement) }) // 公告更新 registerDynamicRoute('PATCH', '/api/announcements/:announcementId', async (config, params) => { await delay() requireAdmin() const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId) if (!announcement) { throw { response: createMockResponse({ detail: '公告不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...announcement, ...body, updated_at: new Date().toISOString() }) }) // 公告删除 registerDynamicRoute('DELETE', '/api/announcements/:announcementId', async (_config, params) => { await delay() requireAdmin() const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId) if (!announcement) { throw { response: createMockResponse({ detail: '公告不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // 用户详情 registerDynamicRoute('GET', '/api/admin/users/:userId', async (_config, params) => { await delay() requireAdmin() const user = MOCK_ALL_USERS.find(u => u.id === params.userId) if (!user) { throw { response: createMockResponse({ detail: '用户不存在' }, 404) } } return createMockResponse(user) }) // 用户更新 registerDynamicRoute('PATCH', '/api/admin/users/:userId', async (config, params) => { await delay() requireAdmin() const user = MOCK_ALL_USERS.find(u => u.id === params.userId) if (!user) { throw { response: createMockResponse({ detail: '用户不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...user, ...body }) }) // 用户删除 registerDynamicRoute('DELETE', '/api/admin/users/:userId', async (_config, params) => { await delay() requireAdmin() const user = MOCK_ALL_USERS.find(u => u.id === params.userId) if (!user) { throw { response: createMockResponse({ detail: '用户不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // 用户 API Keys registerDynamicRoute('GET', '/api/admin/users/:userId/api-keys', async (_config, params) => { await delay() requireAdmin() const apiKeys = mockManagedUserApiKeys(params.userId).map(publicMockManagedUserApiKey) return createMockResponse({ api_keys: apiKeys, total: apiKeys.length, }) }) registerDynamicRoute('POST', '/api/admin/users/:userId/api-keys', async (config, params) => { await delay() requireAdmin() const keys = mockManagedUserApiKeys(params.userId) const body = mockRequestObject(config) const sequence = ++mockManagedUserApiKeySequence const fullKey = `sk-ae-demo-${params.userId.slice(0, 8)}-${sequence}` const key: MockManagedUserApiKey = { id: `managed-key-${params.userId}-${sequence}`, fullKey, key_display: `${fullKey.slice(0, 10)}...${fullKey.slice(-4)}`, name: typeof body.name === 'string' && body.name.trim() ? body.name.trim() : `Key-${sequence}`, created_at: new Date().toISOString(), is_active: true, is_locked: false, is_standalone: false, feature_settings: body.feature_settings && typeof body.feature_settings === 'object' ? body.feature_settings as Record : null, rate_limit: typeof body.rate_limit === 'number' ? body.rate_limit : 0, concurrent_limit: typeof body.concurrent_limit === 'number' ? body.concurrent_limit : null, ip_rules: Array.isArray(body.ip_rules) ? body.ip_rules.filter((value): value is string => typeof value === 'string') : null, total_requests: 0, total_cost_usd: 0, force_capabilities: null, } keys.unshift(key) return createMockResponse({ ...publicMockManagedUserApiKey(key), key: fullKey, message: 'API Key创建成功,请妥善保存完整密钥', }) }) registerDynamicRoute('PUT', '/api/admin/users/:userId/api-keys/:keyId', async (config, params) => { await delay() requireAdmin() const keys = mockManagedUserApiKeys(params.userId) const index = keys.findIndex(key => key.id === params.keyId) if (index < 0) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } const body = mockRequestObject(config) const existing = keys[index] const updated: MockManagedUserApiKey = { ...existing, ...(typeof body.name === 'string' ? { name: body.name.trim() } : {}), ...(typeof body.rate_limit === 'number' ? { rate_limit: body.rate_limit } : {}), ...(typeof body.concurrent_limit === 'number' || body.concurrent_limit === null ? { concurrent_limit: body.concurrent_limit } : {}), ...(Array.isArray(body.ip_rules) || body.ip_rules === null ? { ip_rules: body.ip_rules as string[] | null } : {}), ...('feature_settings' in body ? { feature_settings: body.feature_settings as Record | null } : {}), } keys[index] = updated return createMockResponse({ ...publicMockManagedUserApiKey(updated), message: 'API Key更新成功', }) }) registerDynamicRoute('DELETE', '/api/admin/users/:userId/api-keys/:keyId', async (_config, params) => { await delay() requireAdmin() const keys = mockManagedUserApiKeys(params.userId) const index = keys.findIndex(key => key.id === params.keyId) if (index < 0) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } keys.splice(index, 1) return createMockResponse({ message: 'API Key删除成功' }) }) registerDynamicRoute('PATCH', '/api/admin/users/:userId/api-keys/:keyId/lock', async (_config, params) => { await delay() requireAdmin() const key = mockManagedUserApiKeys(params.userId).find(key => key.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } key.is_locked = !key.is_locked return createMockResponse({ id: key.id, is_locked: key.is_locked, message: key.is_locked ? 'API Key已锁定' : 'API Key已解锁', }) }) registerDynamicRoute('GET', '/api/admin/users/:userId/api-keys/:keyId/full-key', async (_config, params) => { await delay() requireAdmin() const key = mockManagedUserApiKeys(params.userId).find(key => key.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } return createMockResponse({ key: key.fullKey }) }) // 管理员 - 用户会话列表 registerDynamicRoute('GET', '/api/admin/users/:userId/sessions', async (_config, _params) => { await delay() requireAdmin() return createMockResponse([ { id: 'admin-session-1', device_label: 'Chrome / macOS', device_type: 'desktop', browser_name: 'Chrome', browser_version: '134.0', os_name: 'macOS', os_version: '15.3', device_model: null, ip_address: '192.168.1.100', last_seen_at: new Date().toISOString(), created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(), is_current: false, revoked_at: null, revoke_reason: null } ]) }) // 管理员 - 撤销用户单个会话 registerDynamicRoute('DELETE', '/api/admin/users/:userId/sessions/:sessionId', async (_config, _params) => { await delay() requireAdmin() return createMockResponse({ message: '会话已撤销(演示模式)' }) }) // 管理员 - 撤销用户全部会话 registerDynamicRoute('DELETE', '/api/admin/users/:userId/sessions', async (_config, _params) => { await delay() requireAdmin() return createMockResponse({ message: '全部会话已撤销(演示模式)', revoked_count: 1 }) }) // API Key 详情 registerDynamicRoute('GET', '/api/admin/api-keys/:keyId', async (_config, params) => { await delay() requireAdmin() const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } return createMockResponse(key) }) // API Key 更新 registerDynamicRoute('PUT', '/api/admin/api-keys/:keyId', async (config, params) => { await delay() requireAdmin() const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } const body = JSON.parse(config.data || '{}') return createMockResponse({ ...key, ...body }) }) // API Key 删除 registerDynamicRoute('DELETE', '/api/admin/api-keys/:keyId', async (_config, params) => { await delay() requireAdmin() const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // 用户 API Key 删除 registerDynamicRoute('DELETE', '/api/users/me/api-keys/:keyId', async (_config, params) => { await delay() const key = MOCK_USER_API_KEYS.find(k => k.id === params.keyId) if (!key) { throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) } } return createMockResponse({ message: '删除成功(演示模式)' }) }) // 使用记录详情 - /api/admin/usage/:requestId registerDynamicRoute('GET', '/api/admin/usage/:requestId', async (config, params) => { await delay() requireAdmin() const includeBodies = config.params?.include_bodies !== false && config.params?.include_bodies !== 'false' const records = getUsageRecords() const record = records.find(r => r.id === params.requestId) if (!record) { throw { response: createMockResponse({ detail: '请求记录不存在' }, 404) } } // 生成详细的请求信息 const users = [ { id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.ai' }, { id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.ai' }, { id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.ai' }, { id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.ai' } ] const user = users.find(u => u.id === record.user_id) || users[0] // 生成模拟的请求/响应数据 const mockRequestBody = { model: record.model, ...(record.requested_reasoning_effort ? { reasoning: { effort: record.requested_reasoning_effort } } : {}), max_tokens: 4096, messages: [ { role: 'user', content: 'Hello! Can you help me understand how AI gateways work?' } ], stream: record.is_stream } const mockProviderRequestBody = record.id === MOCK_CYBER_POLICY_USAGE_ID ? { model: record.target_model || record.model, reasoning: { effort: record.reasoning_effort }, service_tier: record.service_tier, input: [ { role: 'user', content: 'Help me with an authorized cybersecurity research task.' } ], stream: record.is_stream } : { ...mockRequestBody, model: record.target_model || record.model } const mockResponseBody = record.id === MOCK_CYBER_POLICY_USAGE_ID ? MOCK_CYBER_POLICY_ERROR_BODY : record.status === 'failed' ? { error: { type: 'api_error', message: record.error_message || 'An error occurred' } } : { id: `msg_${record.id}`, type: 'message', role: 'assistant', content: [ { type: 'text', text: 'AI gateways are middleware services that sit between clients and backend services. They handle routing, authentication, rate limiting, and more...' } ], model: record.model, stop_reason: 'end_turn', usage: { input_tokens: record.input_tokens, output_tokens: record.output_tokens } } // 计算费用明细 const inputPricePer1M = record.model.includes('opus') ? 15 : record.model.includes('haiku') ? 1 : 3 const outputPricePer1M = record.model.includes('opus') ? 75 : record.model.includes('haiku') ? 5 : 15 const inputCost = (record.input_tokens / 1000000) * inputPricePer1M const outputCost = (record.output_tokens / 1000000) * outputPricePer1M const cacheCreationCost = (record.cache_creation_input_tokens / 1000000) * (inputPricePer1M * 1.25) const cacheReadCost = (record.cache_read_input_tokens / 1000000) * (inputPricePer1M * 0.1) const detail = { id: record.id, request_id: `req_${record.id}`, user: { id: user.id, username: user.username, email: user.email }, api_key: { id: `key-${record.api_key_name}`, name: record.api_key_name, display: `sk-***${record.api_key_name.slice(-4)}` }, provider: record.provider, api_format: record.api_format, model: record.model, target_model: record.target_model, requested_reasoning_effort: record.requested_reasoning_effort, reasoning_effort: record.reasoning_effort, service_tier: record.service_tier, actual_service_tier: record.actual_service_tier, tokens: { input: record.input_tokens, output: record.output_tokens, total: record.total_tokens }, cost: { input: inputCost, output: outputCost, total: record.cost }, input_tokens: record.input_tokens, output_tokens: record.output_tokens, total_tokens: record.total_tokens, cache_creation_input_tokens: record.cache_creation_input_tokens, cache_read_input_tokens: record.cache_read_input_tokens, input_cost: inputCost, output_cost: outputCost, total_cost: record.cost, cache_creation_cost: cacheCreationCost, cache_read_cost: cacheReadCost, input_price_per_1m: inputPricePer1M, output_price_per_1m: outputPricePer1M, cache_creation_price_per_1m: inputPricePer1M * 1.25, cache_read_price_per_1m: inputPricePer1M * 0.1, request_type: record.is_stream ? 'stream' : 'standard', is_stream: record.is_stream, status_code: record.status_code, error_message: record.error_message, response_time_ms: record.response_time_ms, created_at: record.created_at, updated_at: record.updated_at ?? record.created_at, request_headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer sk-aether-***', 'X-Api-Key': 'sk-***', 'User-Agent': 'Aether-Client/1.0', 'Accept': 'application/json', 'X-Request-ID': `req_${record.id}` }, has_request_body: true, has_provider_request_body: true, has_response_body: true, has_client_response_body: false, request_body: includeBodies ? mockRequestBody : null, provider_request_body: includeBodies ? mockProviderRequestBody : null, provider_request_headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer sk-${record.provider}-***`, 'anthropic-version': '2024-01-01', 'X-Request-ID': `req_${record.id}` }, response_headers: { 'Content-Type': 'application/json', 'X-Request-ID': `req_${record.id}`, 'X-RateLimit-Limit': '1000', 'X-RateLimit-Remaining': '999', 'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString() }, response_body: includeBodies ? mockResponseBody : null, client_response_body: null, body_load_errors: null, metadata: { client_ip: '192.168.1.100', user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', request_path: `/v1/messages`, provider_endpoint: `https://api.${record.provider}.com/v1/messages`, gateway_version: '1.0.0', processing_time_ms: Math.floor((record.response_time_ms || 1000) * 0.1) }, tiered_pricing: { total_input_context: record.input_tokens + record.cache_creation_input_tokens + record.cache_read_input_tokens, tier_index: 0, source: 'provider', tiers: [ { up_to: 200000, input_price_per_1m: inputPricePer1M, output_price_per_1m: outputPricePer1M, cache_creation_price_per_1m: inputPricePer1M * 1.25, cache_read_price_per_1m: inputPricePer1M * 0.1 }, { up_to: null, input_price_per_1m: inputPricePer1M * 0.5, output_price_per_1m: outputPricePer1M * 0.5, cache_creation_price_per_1m: inputPricePer1M * 0.625, cache_read_price_per_1m: inputPricePer1M * 0.05 } ] } } return createMockResponse(detail) }) // 请求链路追踪 - /api/admin/monitoring/trace/:requestId registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_config, params) => { await delay() requireAdmin() const requestId = params.requestId // 从 usage-xxxx 格式中提取记录 const records = getUsageRecords() const recordId = requestId.startsWith('req_') ? requestId.replace('req_', '') : requestId const record = records.find(r => r.id === recordId) if (!record) { throw { response: createMockResponse({ detail: '请求记录不存在' }, 404) } } // 生成候选记录 const now = new Date(record.created_at) const baseLatency = record.response_time_ms || 1000 // 根据请求状态生成不同的候选链路 const candidates = [] const providerNames = ['AlphaAI', 'BetaClaude', 'GammaCode', 'DeltaAPI'] if (record.status === 'completed') { // 成功请求:可能有1-2个跳过的候选,最后一个成功 const skipCount = Math.random() > 0.5 ? 1 : 0 for (let i = 0; i < skipCount; i++) { const skipStarted = new Date(now.getTime() + i * 50) candidates.push({ id: `candidate-${requestId}-${i}`, request_id: requestId, candidate_index: i, retry_index: 0, provider_id: `provider-${i + 1}`, provider_name: providerNames[i % providerNames.length], provider_website: `https://${providerNames[i % providerNames.length].toLowerCase()}.com`, endpoint_id: `endpoint-${i + 1}`, endpoint_name: record.api_format, key_id: `key-${i + 1}`, key_name: `${record.provider}-key-${i + 1}`, key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`, key_capabilities: { 'cache_1h': true, 'vision': true }, required_capabilities: { 'cache_1h': record.cache_read_input_tokens > 0 }, status: 'skipped', skip_reason: ['并发限制已满', '健康分数过低', '倍率不匹配'][i % 3], is_cached: false, ranking: { mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder', priority_mode: 'Provider', index: i, priority_slot: i + 1, demoted_by: i > 0 ? 'cross_format' : undefined }, extra_data: { ranking_mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder', priority_mode: 'Provider', ranking_index: i, priority_slot: i + 1, demoted_by: i > 0 ? 'cross_format' : undefined }, latency_ms: 10 + Math.floor(Math.random() * 20), created_at: skipStarted.toISOString(), started_at: skipStarted.toISOString(), finished_at: new Date(skipStarted.getTime() + 10).toISOString() }) } // 成功的候选 const successStarted = new Date(now.getTime() + skipCount * 50) candidates.push({ id: `candidate-${requestId}-success`, request_id: requestId, candidate_index: skipCount, retry_index: 0, provider_id: `provider-${record.provider}`, provider_name: record.provider === 'anthropic' ? 'AlphaAI' : record.provider === 'openai' ? 'BetaClaude' : 'GammaCode', provider_website: `https://api.${record.provider}.com`, endpoint_id: `endpoint-${record.provider}`, endpoint_name: record.api_format, key_id: `key-${record.api_key_name}`, key_name: record.api_key_name, key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`, key_capabilities: { 'cache_1h': true, 'vision': true, 'extended_thinking': true }, required_capabilities: { 'cache_1h': record.cache_read_input_tokens > 0, 'vision': false, 'extended_thinking': false }, status: 'success', is_cached: record.cache_read_input_tokens > 0, status_code: 200, ranking: { mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder', priority_mode: 'Provider', index: skipCount, priority_slot: record.cache_read_input_tokens > 0 ? 7 : skipCount + 1, promoted_by: record.cache_read_input_tokens > 0 ? 'cached_affinity' : undefined }, extra_data: { ranking_mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder', priority_mode: 'Provider', ranking_index: skipCount, priority_slot: record.cache_read_input_tokens > 0 ? 7 : skipCount + 1, promoted_by: record.cache_read_input_tokens > 0 ? 'cached_affinity' : undefined }, latency_ms: baseLatency, created_at: successStarted.toISOString(), started_at: successStarted.toISOString(), finished_at: new Date(successStarted.getTime() + baseLatency).toISOString() }) } else if (record.status === 'failed') { // 失败请求:多个候选都失败 const isCyberPolicyDemo = record.id === MOCK_CYBER_POLICY_USAGE_ID const attemptCount = isCyberPolicyDemo ? 1 : 2 + Math.floor(Math.random() * 2) for (let i = 0; i < attemptCount; i++) { const attemptStarted = new Date(now.getTime() + i * 200) const attemptLatency = 100 + Math.floor(Math.random() * 500) candidates.push({ id: `candidate-${requestId}-${i}`, request_id: requestId, candidate_index: i, retry_index: 0, provider_id: `provider-${i + 1}`, provider_name: providerNames[i % providerNames.length], provider_website: `https://${providerNames[i % providerNames.length].toLowerCase()}.com`, endpoint_id: `endpoint-${i + 1}`, endpoint_name: record.api_format, key_id: `key-${i + 1}`, key_name: `${record.provider}-key-${i + 1}`, key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`, key_capabilities: { 'cache_1h': true }, required_capabilities: {}, status: 'failed', is_cached: false, status_code: record.status_code, error_type: ['rate_limit_error', 'api_error', 'timeout_error'][i % 3], error_message: record.error_message || 'Request failed', ranking: { mode: 'FixedOrder', priority_mode: 'Provider', index: i, priority_slot: i + 1 }, extra_data: { ranking_mode: 'FixedOrder', priority_mode: 'Provider', ranking_index: i, priority_slot: i + 1, ...(isCyberPolicyDemo ? { upstream_response: { source: 'upstream_response', status_code: 400, headers: { 'content-type': 'application/json', 'x-request-id': `req_${MOCK_CYBER_POLICY_USAGE_ID}` }, body: MOCK_CYBER_POLICY_ERROR_BODY, body_ref: `usage://request/req_${MOCK_CYBER_POLICY_USAGE_ID}/response_body`, body_state: 'reference' } } : {}) }, latency_ms: attemptLatency, created_at: attemptStarted.toISOString(), started_at: attemptStarted.toISOString(), finished_at: new Date(attemptStarted.getTime() + attemptLatency).toISOString() }) } } else { // 进行中的请求 candidates.push({ id: `candidate-${requestId}-0`, request_id: requestId, candidate_index: 0, retry_index: 0, provider_id: `provider-${record.provider}`, provider_name: record.provider === 'anthropic' ? 'AlphaAI' : record.provider === 'openai' ? 'BetaClaude' : 'GammaCode', provider_website: `https://api.${record.provider}.com`, endpoint_id: `endpoint-${record.provider}`, endpoint_name: record.api_format, key_id: `key-${record.api_key_name}`, key_name: record.api_key_name, key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`, key_capabilities: { 'cache_1h': true, 'vision': true }, required_capabilities: {}, status: 'streaming', is_cached: false, ranking: { mode: 'FixedOrder', priority_mode: 'Provider', index: 0, priority_slot: 1 }, extra_data: { ranking_mode: 'FixedOrder', priority_mode: 'Provider', ranking_index: 0, priority_slot: 1 }, latency_ms: undefined, created_at: now.toISOString(), started_at: now.toISOString(), finished_at: undefined }) } const totalLatency = candidates.reduce((sum, c) => sum + (c.latency_ms || 0), 0) return createMockResponse({ request_id: requestId, total_candidates: candidates.length, final_status: record.status === 'completed' ? 'success' : record.status === 'failed' ? 'failed' : 'streaming', total_latency_ms: totalLatency, candidates }) }) // ========== 请求间隔时间线 Mock 数据 ========== // 生成请求间隔时间线数据(用于散点图) function generateIntervalTimelineData( hours: number = 24, limit: number = 5000, includeUserInfo: boolean = false ) { const now = Date.now() const startTime = now - hours * 60 * 60 * 1000 const points: Array<{ x: string; y: number; user_id?: string; model?: string }> = [] // 用户列表(用于管理员视图) const users = [ { id: 'demo-admin-uuid-0001', username: 'Demo Admin' }, { id: 'demo-user-uuid-0002', username: 'Demo User' }, { id: 'demo-user-uuid-0003', username: 'Alice Chen' }, { id: 'demo-user-uuid-0004', username: 'Bob Zhang' } ] // 模型列表(用于按模型区分颜色) const models = [ 'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001', 'claude-opus-4-5-20251101', 'gpt-5.1' ] // 生成模拟的请求间隔数据 // 间隔时间分布:大部分在 0-10 分钟,少量在 10-60 分钟,极少数在 60-120 分钟 const pointCount = Math.min(limit, Math.floor(hours * 80)) // 每小时约 80 个数据点 let currentTime = startTime + Math.random() * 60 * 1000 // 从起始时间后随机开始 for (let i = 0; i < pointCount && currentTime < now; i++) { // 生成间隔时间(分钟),使用指数分布模拟真实场景 let interval: number const rand = Math.random() if (rand < 0.7) { // 70% 的请求间隔在 0-5 分钟 interval = Math.random() * 5 } else if (rand < 0.9) { // 20% 的请求间隔在 5-30 分钟 interval = 5 + Math.random() * 25 } else if (rand < 0.98) { // 8% 的请求间隔在 30-90 分钟 interval = 30 + Math.random() * 60 } else { // 2% 的请求间隔在 90-120 分钟 interval = 90 + Math.random() * 30 } // 添加一些工作时间的模式(工作时间间隔更短) const hour = new Date(currentTime).getHours() if (hour >= 9 && hour <= 18) { interval *= 0.6 // 工作时间间隔更短 } else if (hour >= 22 || hour <= 6) { interval *= 1.5 // 夜间间隔更长 } // 确保间隔不超过 120 分钟 interval = Math.min(interval, 120) const point: { x: string; y: number; user_id?: string; model?: string } = { x: new Date(currentTime).toISOString(), y: Math.round(interval * 100) / 100, model: models[Math.floor(Math.random() * models.length)] } if (includeUserInfo) { // 管理员视图:添加用户信息 const user = users[Math.floor(Math.random() * users.length)] point.user_id = user.id } points.push(point) // 下一个请求时间 = 当前时间 + 间隔 + 一些随机抖动 currentTime += interval * 60 * 1000 + Math.random() * 30 * 1000 } // 按时间排序 points.sort((a, b) => new Date(a.x).getTime() - new Date(b.x).getTime()) // 收集出现的模型 const usedModels = [...new Set(points.map(p => p.model).filter(Boolean))] as string[] const response: { analysis_period_hours: number total_points: number points: typeof points users?: Record models?: string[] } = { analysis_period_hours: hours, total_points: points.length, points, models: usedModels } if (includeUserInfo) { response.users = Object.fromEntries(users.map(u => [u.id, u.username])) } return response } // 用户 interval-timeline 接口 mockHandlers['GET /api/users/me/usage/interval-timeline'] = async (config) => { await delay() const params = config.params || {} const hours = parseInt(params.hours) || 24 const limit = parseInt(params.limit) || 5000 const data = generateIntervalTimelineData(hours, limit, false) return createMockResponse(data) } // 管理员 interval-timeline 接口 mockHandlers['GET /api/admin/usage/cache-affinity/interval-timeline'] = async (config) => { await delay() requireAdmin() const params = config.params || {} const hours = parseInt(params.hours) || 24 const limit = parseInt(params.limit) || 10000 const userId = params.user_id const includeUserInfo = params.include_user_info === 'true' || params.include_user_info === true // 如果指定了 user_id,则不包含用户信息 const data = generateIntervalTimelineData(hours, limit, includeUserInfo && !userId) return createMockResponse(data) } // ========== TTL 分析 Mock 数据 ========== // 生成 TTL 分析数据 function generateTTLAnalysisData(hours: number = 168) { const users = [ { id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.io' }, { id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.io' }, { id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.io' }, { id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.io' } ] const usersAnalysis = users.map(user => { // 为每个用户生成不同的使用模式 const requestCount = 50 + Math.floor(Math.random() * 500) // 根据用户特性生成不同的间隔分布 let within5min, within15min, within30min, within60min, over60min let p50, p75, p90 let recommendedTtl: number let recommendationReason: string const userType = Math.random() if (userType < 0.3) { // 高频用户 (30%) within5min = Math.floor(requestCount * (0.6 + Math.random() * 0.2)) within15min = Math.floor(requestCount * (0.1 + Math.random() * 0.1)) within30min = Math.floor(requestCount * (0.05 + Math.random() * 0.05)) within60min = Math.floor(requestCount * (0.02 + Math.random() * 0.03)) over60min = requestCount - within5min - within15min - within30min - within60min p50 = 1.5 + Math.random() * 2 p75 = 3 + Math.random() * 3 p90 = 4 + Math.random() * 2 recommendedTtl = 5 recommendationReason = `高频用户:90% 的请求间隔在 ${p90.toFixed(1)} 分钟内` } else if (userType < 0.6) { // 中频用户 (30%) within5min = Math.floor(requestCount * (0.3 + Math.random() * 0.15)) within15min = Math.floor(requestCount * (0.25 + Math.random() * 0.15)) within30min = Math.floor(requestCount * (0.15 + Math.random() * 0.1)) within60min = Math.floor(requestCount * (0.1 + Math.random() * 0.05)) over60min = requestCount - within5min - within15min - within30min - within60min p50 = 5 + Math.random() * 5 p75 = 10 + Math.random() * 8 p90 = 18 + Math.random() * 10 recommendedTtl = 15 recommendationReason = `中高频用户:75% 的请求间隔在 ${p75.toFixed(1)} 分钟内` } else if (userType < 0.85) { // 中低频用户 (25%) within5min = Math.floor(requestCount * (0.15 + Math.random() * 0.1)) within15min = Math.floor(requestCount * (0.2 + Math.random() * 0.1)) within30min = Math.floor(requestCount * (0.25 + Math.random() * 0.1)) within60min = Math.floor(requestCount * (0.15 + Math.random() * 0.1)) over60min = requestCount - within5min - within15min - within30min - within60min p50 = 12 + Math.random() * 8 p75 = 22 + Math.random() * 10 p90 = 35 + Math.random() * 15 recommendedTtl = 30 recommendationReason = `中频用户:75% 的请求间隔在 ${p75.toFixed(1)} 分钟内` } else { // 低频用户 (15%) within5min = Math.floor(requestCount * (0.05 + Math.random() * 0.1)) within15min = Math.floor(requestCount * (0.1 + Math.random() * 0.1)) within30min = Math.floor(requestCount * (0.15 + Math.random() * 0.1)) within60min = Math.floor(requestCount * (0.25 + Math.random() * 0.1)) over60min = requestCount - within5min - within15min - within30min - within60min p50 = 25 + Math.random() * 15 p75 = 45 + Math.random() * 20 p90 = 70 + Math.random() * 30 recommendedTtl = 60 recommendationReason = `低频用户:75% 的请求间隔为 ${p75.toFixed(1)} 分钟,建议使用长 TTL` } // 确保没有负数 over60min = Math.max(0, over60min) const avgInterval = (within5min * 2.5 + within15min * 10 + within30min * 22 + within60min * 45 + over60min * 80) / requestCount return { group_id: user.id, username: user.username, email: user.email, request_count: requestCount, interval_distribution: { within_5min: within5min, within_15min: within15min, within_30min: within30min, within_60min: within60min, over_60min: over60min }, interval_percentages: { within_5min: Math.round(within5min / requestCount * 1000) / 10, within_15min: Math.round(within15min / requestCount * 1000) / 10, within_30min: Math.round(within30min / requestCount * 1000) / 10, within_60min: Math.round(within60min / requestCount * 1000) / 10, over_60min: Math.round(over60min / requestCount * 1000) / 10 }, percentiles: { p50: Math.round(p50 * 100) / 100, p75: Math.round(p75 * 100) / 100, p90: Math.round(p90 * 100) / 100 }, avg_interval_minutes: Math.round(avgInterval * 100) / 100, min_interval_minutes: Math.round((0.1 + Math.random() * 0.5) * 100) / 100, max_interval_minutes: Math.round((80 + Math.random() * 40) * 100) / 100, recommended_ttl_minutes: recommendedTtl, recommendation_reason: recommendationReason } }) // 汇总 TTL 分布 const ttlDistribution = { '5min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 5).length, '15min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 15).length, '30min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 30).length, '60min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 60).length } return { analysis_period_hours: hours, total_users_analyzed: usersAnalysis.length, ttl_distribution: ttlDistribution, users: usersAnalysis } } // 生成缓存命中分析数据 function generateCacheHitAnalysisData(hours: number = 168) { const totalRequests = 5000 + Math.floor(Math.random() * 10000) const requestsWithCacheHit = Math.floor(totalRequests * (0.25 + Math.random() * 0.35)) const totalInputTokens = totalRequests * (2000 + Math.floor(Math.random() * 3000)) const totalCacheReadTokens = Math.floor(totalInputTokens * (0.15 + Math.random() * 0.25)) const totalCacheCreationTokens = Math.floor(totalInputTokens * (0.05 + Math.random() * 0.1)) // 缓存读取成本:按每百万 token $0.30 计算 const cacheReadCostPer1M = 0.30 const cacheCreationCostPer1M = 3.75 const totalCacheReadCost = (totalCacheReadTokens / 1000000) * cacheReadCostPer1M const totalCacheCreationCost = (totalCacheCreationTokens / 1000000) * cacheCreationCostPer1M // 缓存读取节省了 90% 的成本 const estimatedSavings = totalCacheReadCost * 9 const tokenCacheHitRate = totalCacheReadTokens / (totalInputTokens + totalCacheReadTokens) * 100 return { analysis_period_hours: hours, total_requests: totalRequests, requests_with_cache_hit: requestsWithCacheHit, request_cache_hit_rate: Math.round(requestsWithCacheHit / totalRequests * 10000) / 100, total_input_tokens: totalInputTokens, total_cache_read_tokens: totalCacheReadTokens, total_cache_creation_tokens: totalCacheCreationTokens, token_cache_hit_rate: Math.round(tokenCacheHitRate * 100) / 100, total_cache_read_cost_usd: Math.round(totalCacheReadCost * 10000) / 10000, total_cache_creation_cost_usd: Math.round(totalCacheCreationCost * 10000) / 10000, estimated_savings_usd: Math.round(estimatedSavings * 10000) / 10000 } } // TTL 分析接口 mockHandlers['GET /api/admin/usage/cache-affinity/ttl-analysis'] = async (config) => { await delay() requireAdmin() const params = config.params || {} const hours = parseInt(params.hours) || 168 const data = generateTTLAnalysisData(hours) return createMockResponse(data) } // 缓存命中分析接口 mockHandlers['GET /api/admin/usage/cache-affinity/hit-analysis'] = async (config) => { await delay() requireAdmin() const params = config.params || {} const hours = parseInt(params.hours) || 168 const data = generateCacheHitAnalysisData(hours) return createMockResponse(data) } // ========== Admin: Stats / Leaderboard ========== mockHandlers['GET /api/admin/stats/leaderboard/users'] = async () => { await delay() requireAdmin() return createMockResponse({ items: [ { rank: 1, id: 'user-1', name: 'Demo Admin', value: 1200, requests: 1200, tokens: 240000, cost: 123.4 }, { rank: 2, id: 'user-2', name: 'Demo User', value: 980, requests: 980, tokens: 180000, cost: 98.7 } ], total: 2, metric: 'requests', start_date: '2026-02-01', end_date: '2026-02-07' }) } mockHandlers['GET /api/admin/stats/leaderboard/api-keys'] = async () => { await delay() requireAdmin() return createMockResponse({ items: [ { rank: 1, id: 'key-1', name: 'Key A', value: 800, requests: 800, tokens: 160000, cost: 76.2 }, { rank: 2, id: 'key-2', name: 'Key B', value: 620, requests: 620, tokens: 120000, cost: 55.1 } ], total: 2, metric: 'requests', start_date: '2026-02-01', end_date: '2026-02-07' }) } mockHandlers['GET /api/admin/stats/leaderboard/models'] = async () => { await delay() requireAdmin() return createMockResponse({ items: [ { rank: 1, id: 'gpt-4', name: 'gpt-4', value: 500, requests: 500, tokens: 100000, cost: 44.2 }, { rank: 2, id: 'claude-3', name: 'claude-3', value: 420, requests: 420, tokens: 90000, cost: 40.1 } ], total: 2, metric: 'requests', start_date: '2026-02-01', end_date: '2026-02-07' }) } mockHandlers['GET /api/admin/stats/cost/forecast'] = async () => { await delay() requireAdmin() return createMockResponse({ history: [ { date: '2026-02-01', total_cost: 120 }, { date: '2026-02-02', total_cost: 132 }, { date: '2026-02-03', total_cost: 140 } ], forecast: [ { date: '2026-02-04', total_cost: 150 }, { date: '2026-02-05', total_cost: 158 } ], slope: 5.2, intercept: 110.5, start_date: '2026-02-01', end_date: '2026-02-03' }) } mockHandlers['GET /api/admin/stats/cost/savings'] = async () => { await delay() requireAdmin() return createMockResponse({ cache_read_tokens: 120000, cache_read_cost: 8.2, cache_creation_cost: 3.4, estimated_full_cost: 82.0, cache_savings: 73.8 }) } mockHandlers['GET /api/admin/stats/providers/quota-usage'] = async () => { await delay() requireAdmin() return createMockResponse({ providers: [ { id: 'prov-1', name: 'Provider A', quota_usd: 500, used_usd: 320, remaining_usd: 180, usage_percent: 64, quota_expires_at: null, estimated_exhaust_at: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString() } ] }) } mockHandlers['GET /api/admin/stats/performance/percentiles'] = async () => { await delay() requireAdmin() return createMockResponse([ { date: '2026-02-01', p50_response_time_ms: 320, p90_response_time_ms: 560, p99_response_time_ms: 860, p50_first_byte_time_ms: 120, p90_first_byte_time_ms: 210, p99_first_byte_time_ms: 400 }, { date: '2026-02-02', p50_response_time_ms: 300, p90_response_time_ms: 540, p99_response_time_ms: 820, p50_first_byte_time_ms: 110, p90_first_byte_time_ms: 200, p99_first_byte_time_ms: 380 } ]) } mockHandlers['GET /api/admin/stats/performance/providers'] = async () => { await delay() requireAdmin() return createMockResponse({ summary: { request_count: 18420, success_rate: 97.36, avg_output_tps: 78.4, avg_first_byte_time_ms: 318, avg_response_time_ms: 1640, p90_response_time_ms: 2860, p99_response_time_ms: 9120, p90_first_byte_time_ms: 690, p99_first_byte_time_ms: 2210, tps_sample_count: 17120, response_time_sample_count: 18200, first_byte_sample_count: 16840, slow_request_count: 214 }, providers: [ { provider_id: 'provider-openai', provider: 'OpenAI', request_count: 8120, success_count: 7930, error_count: 190, success_rate: 97.66, output_tokens: 1480000, avg_output_tps: 83.2, avg_first_byte_time_ms: 286, avg_response_time_ms: 1420, p90_response_time_ms: 2440, p99_response_time_ms: 7520, p90_first_byte_time_ms: 620, p99_first_byte_time_ms: 1840, tps_sample_count: 7890, response_time_sample_count: 8050, first_byte_sample_count: 7760, slow_request_count: 71 }, { provider_id: 'provider-anthropic', provider: 'Anthropic', request_count: 6230, success_count: 6044, error_count: 186, success_rate: 97.01, output_tokens: 1120000, avg_output_tps: 62.7, avg_first_byte_time_ms: 410, avg_response_time_ms: 1880, p90_response_time_ms: 3180, p99_response_time_ms: 10120, p90_first_byte_time_ms: 820, p99_first_byte_time_ms: 2680, tps_sample_count: 5900, response_time_sample_count: 6150, first_byte_sample_count: 5810, slow_request_count: 109 }, { provider_id: 'provider-gemini', provider: 'Gemini', request_count: 4070, success_count: 3960, error_count: 110, success_rate: 97.30, output_tokens: 780000, avg_output_tps: 91.1, avg_first_byte_time_ms: 240, avg_response_time_ms: 1210, p90_response_time_ms: 2190, p99_response_time_ms: 6420, p90_first_byte_time_ms: 520, p99_first_byte_time_ms: 1510, tps_sample_count: 3930, response_time_sample_count: 4000, first_byte_sample_count: 3820, slow_request_count: 34 } ], timeline: [ { date: '2026-02-01', provider_id: 'provider-openai', provider: 'OpenAI', request_count: 4020, output_tokens: 720000, avg_output_tps: 81.4, avg_first_byte_time_ms: 294, avg_response_time_ms: 1450, success_rate: 97.4, slow_request_count: 38 }, { date: '2026-02-02', provider_id: 'provider-openai', provider: 'OpenAI', request_count: 4100, output_tokens: 760000, avg_output_tps: 85.1, avg_first_byte_time_ms: 278, avg_response_time_ms: 1390, success_rate: 97.9, slow_request_count: 33 }, { date: '2026-02-01', provider_id: 'provider-anthropic', provider: 'Anthropic', request_count: 3020, output_tokens: 540000, avg_output_tps: 60.8, avg_first_byte_time_ms: 430, avg_response_time_ms: 1940, success_rate: 96.7, slow_request_count: 58 }, { date: '2026-02-02', provider_id: 'provider-anthropic', provider: 'Anthropic', request_count: 3210, output_tokens: 580000, avg_output_tps: 64.2, avg_first_byte_time_ms: 392, avg_response_time_ms: 1810, success_rate: 97.3, slow_request_count: 51 }, { date: '2026-02-01', provider_id: 'provider-gemini', provider: 'Gemini', request_count: 2020, output_tokens: 382000, avg_output_tps: 88.6, avg_first_byte_time_ms: 252, avg_response_time_ms: 1260, success_rate: 97.0, slow_request_count: 18 }, { date: '2026-02-02', provider_id: 'provider-gemini', provider: 'Gemini', request_count: 2050, output_tokens: 398000, avg_output_tps: 93.6, avg_first_byte_time_ms: 228, avg_response_time_ms: 1160, success_rate: 97.6, slow_request_count: 16 } ] }) } mockHandlers['GET /api/admin/stats/errors/distribution'] = async () => { await delay() requireAdmin() return createMockResponse({ distribution: [ { category: 'rate_limit', count: 24 }, { category: 'server_error', count: 12 }, { category: 'timeout', count: 6 } ], trend: [ { date: '2026-02-01', total: 8, categories: { rate_limit: 5, server_error: 3 } }, { date: '2026-02-02', total: 6, categories: { rate_limit: 4, timeout: 2 } } ] }) } mockHandlers['GET /api/admin/monitoring/system-status'] = async () => { await delay() requireAdmin() return createMockResponse({ timestamp: new Date().toISOString(), users: { total: 124, active: 111 }, providers: { total: 18, active: 15 }, api_keys: { total: 263, active: 241 }, today_stats: { requests: 12483, tokens: 48751234, cost_usd: '$182.4631' }, tunnel: { proxy_connections: 28, nodes: 6, active_streams: 164 }, internal_gateway: { status: 'disabled', path_prefixes: ['/api/', '/v1/', '/v1beta/', '/_gateway/'] }, recent_errors: 9 }) } mockHandlers['GET /api/admin/monitoring/resilience-status'] = async () => { await delay() requireAdmin() return createMockResponse({ timestamp: new Date().toISOString(), health_score: 86, status: 'healthy', error_statistics: { total_errors: 14, active_keys: 24, degraded_keys: 3, unhealthy_keys: 1, open_circuit_breakers: 1, circuit_breakers: { 'provider-key-1': { state: 'open', provider_id: 'provider-openai', provider_name: 'OpenAI', key_name: 'prod-key-a', health_score: 0.42, consecutive_failures: 4, last_failure_at: new Date(Date.now() - 8 * 60 * 1000).toISOString(), open_formats: ['openai:chat'] } } }, recent_errors: [ { error_id: 'usage-request-1', error_type: 'timeout', operation: 'OpenAI:openai:chat', timestamp: new Date(Date.now() - 3 * 60 * 1000).toISOString(), context: { request_id: 'req-live-001', provider_id: 'provider-openai', provider_name: 'OpenAI', model: 'gpt-5', api_format: 'openai:chat', status_code: 504, error_message: '上游响应超时,等待首字节超过阈值' } }, { error_id: 'usage-request-2', error_type: 'server_error', operation: 'Anthropic:claude:messages', timestamp: new Date(Date.now() - 11 * 60 * 1000).toISOString(), context: { request_id: 'req-live-002', provider_id: 'provider-anthropic', provider_name: 'Anthropic', model: 'claude-sonnet-4-5', api_format: 'claude:messages', status_code: 502, error_message: '上游返回 502 Bad Gateway' } } ], recommendations: [ '以下服务熔断器已打开:OpenAI/prod-key-a', '建议检查最近的 timeout 与 5xx 错误峰值', '当前整体健康度可接受,但需要关注单 Key 退化' ] }) } mockHandlers['GET /api/admin/monitoring/resilience/circuit-history'] = async () => { await delay() requireAdmin() return createMockResponse({ count: 2, items: [ { event: 'opened', key_id: 'provider-key-1', provider_id: 'provider-openai', provider_name: 'OpenAI', key_name: 'prod-key-a', api_format: 'openai:chat', reason: '错误率过高', recovery_seconds: 300, timestamp: new Date(Date.now() - 10 * 60 * 1000).toISOString() }, { event: 'half_open', key_id: 'provider-key-7', provider_id: 'provider-gemini', provider_name: 'Gemini', key_name: 'gemini-burst', api_format: 'gemini:generate_content', reason: '正在探测恢复', recovery_seconds: 120, timestamp: new Date(Date.now() - 22 * 60 * 1000).toISOString() } ] }) } mockHandlers['GET /api/admin/monitoring/cache/stats'] = async () => { await delay() requireAdmin() return createMockResponse({ status: 'ok', data: { scheduler: 'adaptive-cache-affinity', total_affinities: 1428, cache_hit_rate: 0.842, provider_switches: 38, key_switches: 74, cache_hits: 93842, cache_misses: 17610, scheduler_metrics: { cache_hits: 93842, cache_misses: 17610, cache_hit_rate: 0.842, total_batches: 0, last_batch_size: 0, total_candidates: 0, last_candidate_count: 0, concurrency_denied: 0, avg_candidates_per_batch: 0, scheduling_mode: 'adaptive', provider_priority_mode: 'weighted' }, affinity_stats: { storage_type: 'redis', total_affinities: 1428, active_affinities: 1410, cache_hits: 93842, cache_misses: 17610, cache_hit_rate: 0.842, cache_invalidations: 42, provider_switches: 38, key_switches: 74, config: { default_ttl: 300 } } } }) } mockHandlers['GET /api/admin/monitoring/cache/redis-keys'] = async () => { await delay() requireAdmin() return createMockResponse({ status: 'ok', data: { available: true, categories: [ { key: 'cache_affinity', name: '缓存亲和性', pattern: 'cache_affinity:*', description: '请求路由亲和性缓存', count: 1428 }, { key: 'concurrency_lock', name: '并发锁', pattern: 'concurrency:*', description: '请求并发控制锁', count: 186 }, { key: 'apikey', name: 'API Key', pattern: 'apikey:*', description: 'API Key 认证缓存', count: 263 }, { key: 'health', name: '健康检查', pattern: 'health:*', description: '端点健康状态缓存', count: 154 }, { key: 'models_list', name: '模型列表', pattern: 'models:list:*', description: '/v1/models 端点模型列表缓存', count: 38 }, { key: 'provider_balance', name: 'Provider 余额', pattern: 'provider_ops:balance:*', description: 'Provider 余额查询缓存', count: 24 } ], total_keys: 2093 } }) } mockHandlers['GET /api/admin/proxy-nodes'] = async () => { await delay() requireAdmin() const now = new Date().toISOString() return createMockResponse({ items: [ { id: 'proxy-node-1', name: 'edge-shanghai-1', ip: '10.8.0.12', port: 8443, region: 'cn-east', status: 'online', is_manual: false, tunnel_mode: true, tunnel_connected: true, tunnel_connected_at: now, hardware_info: { cpu_cores: 8, total_memory_mb: 16384, os_info: 'linux' }, estimated_max_concurrency: 620, remote_config: null, config_version: 12, registered_by: 'agent', last_heartbeat_at: now, heartbeat_interval: 15, active_connections: 82, total_requests: 43820, avg_latency_ms: 42, failed_requests: 34, dns_failures: 2, stream_errors: 7, proxy_metadata: { resource_usage: { system_cpu_usage_percent: 41.8, process_cpu_usage_percent: 18.4, memory_total_bytes: 17179869184, memory_used_bytes: 9878424780, memory_used_percent: 57.5, process_memory_bytes: 438304768, process_memory_percent: 2.6, process_uptime_secs: 86400 } }, created_at: now, updated_at: now }, { id: 'proxy-node-2', name: 'edge-us-west-1', ip: '10.8.1.18', port: 8443, region: 'us-west', status: 'online', is_manual: false, tunnel_mode: true, tunnel_connected: true, tunnel_connected_at: now, hardware_info: { cpu_cores: 16, total_memory_mb: 32768, os_info: 'linux' }, estimated_max_concurrency: 980, remote_config: null, config_version: 9, registered_by: 'agent', last_heartbeat_at: now, heartbeat_interval: 15, active_connections: 94, total_requests: 58340, avg_latency_ms: 58, failed_requests: 52, dns_failures: 1, stream_errors: 9, proxy_metadata: { resource_usage: { system_cpu_usage_percent: 55.2, process_cpu_usage_percent: 22.6, memory_total_bytes: 34359738368, memory_used_bytes: 22333829939, memory_used_percent: 65.0, process_memory_bytes: 612368384, process_memory_percent: 1.8, process_uptime_secs: 172800 } }, created_at: now, updated_at: now } ], total: 2, skip: 0, limit: 200, rollout: null }) } mockHandlers['GET /api/admin/proxy-nodes/metrics/fleet'] = async () => { await delay() requireAdmin() const now = Math.floor(Date.now() / 1000) return createMockResponse({ step: '1m', from: now - 3600, to: now, items: [ { bucket_start_unix_secs: now - 120, bucket_start: new Date((now - 120) * 1000).toISOString(), samples: 2, uptime_samples: 2, uptime_ratio: 1, active_connections_sum: 168, active_connections_max: 92, active_connections_avg: 84, heartbeat_rtt_ms_sum: 86, heartbeat_rtt_ms_max: 52, heartbeat_rtt_ms_avg: 43, connect_errors_delta: 0, disconnects_delta: 0, error_events_delta: 1, ws_in_bytes_delta: 4849664, ws_out_bytes_delta: 12812288, ws_in_frames_delta: 18320, ws_out_frames_delta: 42110 }, { bucket_start_unix_secs: now - 60, bucket_start: new Date((now - 60) * 1000).toISOString(), samples: 2, uptime_samples: 2, uptime_ratio: 1, active_connections_sum: 176, active_connections_max: 94, active_connections_avg: 88, heartbeat_rtt_ms_sum: 82, heartbeat_rtt_ms_max: 48, heartbeat_rtt_ms_avg: 41, connect_errors_delta: 0, disconnects_delta: 0, error_events_delta: 0, ws_in_bytes_delta: 5154816, ws_out_bytes_delta: 13996032, ws_in_frames_delta: 19120, ws_out_frames_delta: 44710 } ], summary: { samples: 4, uptime_samples: 4, uptime_ratio: 1, active_connections_sum: 344, active_connections_max: 94, active_connections_avg: 86, heartbeat_rtt_ms_sum: 168, heartbeat_rtt_ms_max: 52, heartbeat_rtt_ms_avg: 42, connect_errors_delta: 0, disconnects_delta: 0, error_events_delta: 1, ws_in_bytes_delta: 10004480, ws_out_bytes_delta: 26808320, ws_in_frames_delta: 37440, ws_out_frames_delta: 86820 } }) } mockHandlers['GET /_gateway/metrics'] = async () => { await delay(60) requireAdmin() return createMockResponse(`# HELP aether_gateway_service_up Whether the service process is currently up. # TYPE aether_gateway_service_up gauge aether_gateway_service_up{service="aether-gateway"} 1 # HELP aether_gateway_concurrency_in_flight Current number of in-flight operations guarded by the concurrency gate. # TYPE aether_gateway_concurrency_in_flight gauge aether_gateway_concurrency_in_flight{gate="gateway_requests"} 82 # HELP aether_gateway_concurrency_available_permits Currently available permits for the concurrency gate. # TYPE aether_gateway_concurrency_available_permits gauge aether_gateway_concurrency_available_permits{gate="gateway_requests"} 174 # HELP aether_gateway_concurrency_high_watermark Highest observed in-flight count for the concurrency gate. # TYPE aether_gateway_concurrency_high_watermark gauge aether_gateway_concurrency_high_watermark{gate="gateway_requests"} 121 # HELP aether_gateway_concurrency_rejected_total Number of operations rejected by the concurrency gate. # TYPE aether_gateway_concurrency_rejected_total counter aether_gateway_concurrency_rejected_total{gate="gateway_requests"} 6 # HELP aether_gateway_concurrency_in_flight Current number of in-flight operations guarded by the concurrency gate. # TYPE aether_gateway_concurrency_in_flight gauge aether_gateway_concurrency_in_flight{gate="gateway_requests_distributed"} 94 # HELP aether_gateway_concurrency_available_permits Currently available permits for the concurrency gate. # TYPE aether_gateway_concurrency_available_permits gauge aether_gateway_concurrency_available_permits{gate="gateway_requests_distributed"} 418 # HELP aether_gateway_concurrency_high_watermark Highest observed in-flight count for the concurrency gate. # TYPE aether_gateway_concurrency_high_watermark gauge aether_gateway_concurrency_high_watermark{gate="gateway_requests_distributed"} 137 # HELP aether_gateway_concurrency_rejected_total Number of operations rejected by the concurrency gate. # TYPE aether_gateway_concurrency_rejected_total counter aether_gateway_concurrency_rejected_total{gate="gateway_requests_distributed"} 11 # HELP aether_gateway_tunnel_proxy_connections Current number of connected proxy sockets. # TYPE aether_gateway_tunnel_proxy_connections gauge aether_gateway_tunnel_proxy_connections 28 # HELP aether_gateway_tunnel_nodes Current number of connected logical nodes. # TYPE aether_gateway_tunnel_nodes gauge aether_gateway_tunnel_nodes 6 # HELP aether_gateway_tunnel_active_streams Current number of active local relay streams. # TYPE aether_gateway_tunnel_active_streams gauge aether_gateway_tunnel_active_streams 164 # HELP aether_gateway_tunnel_proxy_connections_available Current number of available proxy sockets. # TYPE aether_gateway_tunnel_proxy_connections_available gauge aether_gateway_tunnel_proxy_connections_available 24 # HELP aether_gateway_tunnel_proxy_connections_closing Current number of closing proxy sockets. # TYPE aether_gateway_tunnel_proxy_connections_closing gauge aether_gateway_tunnel_proxy_connections_closing 1 # HELP aether_gateway_tunnel_proxy_connections_draining Current number of draining proxy sockets. # TYPE aether_gateway_tunnel_proxy_connections_draining gauge aether_gateway_tunnel_proxy_connections_draining 2 # HELP aether_gateway_tunnel_proxy_connections_soft_avoid Current number of soft-avoid proxy sockets. # TYPE aether_gateway_tunnel_proxy_connections_soft_avoid gauge aether_gateway_tunnel_proxy_connections_soft_avoid 3 # HELP aether_gateway_tunnel_proxy_outbound_queue_depth_total Current outbound queue depth total. # TYPE aether_gateway_tunnel_proxy_outbound_queue_depth_total gauge aether_gateway_tunnel_proxy_outbound_queue_depth_total 312 # HELP aether_gateway_tunnel_proxy_outbound_queue_depth_max Current outbound queue max depth. # TYPE aether_gateway_tunnel_proxy_outbound_queue_depth_max gauge aether_gateway_tunnel_proxy_outbound_queue_depth_max 38 # HELP aether_gateway_tunnel_proxy_outbound_queue_capacity_total Current outbound queue capacity total. # TYPE aether_gateway_tunnel_proxy_outbound_queue_capacity_total gauge aether_gateway_tunnel_proxy_outbound_queue_capacity_total 2048 # HELP aether_gateway_tunnel_proxy_outbound_queue_rejected_full_total Outbound queue full rejections. # TYPE aether_gateway_tunnel_proxy_outbound_queue_rejected_full_total counter aether_gateway_tunnel_proxy_outbound_queue_rejected_full_total 7 # HELP aether_gateway_tunnel_proxy_outbound_queue_rejected_closed_total Outbound queue closed rejections. # TYPE aether_gateway_tunnel_proxy_outbound_queue_rejected_closed_total counter aether_gateway_tunnel_proxy_outbound_queue_rejected_closed_total 2 # HELP aether_gateway_tunnel_proxy_connection_congested_total Congested proxy selections. # TYPE aether_gateway_tunnel_proxy_connection_congested_total counter aether_gateway_tunnel_proxy_connection_congested_total 21 # HELP aether_gateway_tunnel_proxy_soft_avoid_selection_total Soft-avoid proxy selections. # TYPE aether_gateway_tunnel_proxy_soft_avoid_selection_total counter aether_gateway_tunnel_proxy_soft_avoid_selection_total 18 # HELP aether_gateway_tunnel_proxy_selection_retry_total Proxy selection retries. # TYPE aether_gateway_tunnel_proxy_selection_retry_total counter aether_gateway_tunnel_proxy_selection_retry_total 11 # HELP aether_gateway_tunnel_proxy_selection_unavailable_total Proxy selection unavailable events. # TYPE aether_gateway_tunnel_proxy_selection_unavailable_total counter aether_gateway_tunnel_proxy_selection_unavailable_total 4 # HELP aether_gateway_decision_remote_total Number of requests that fell back to Python decision endpoints. # TYPE aether_gateway_decision_remote_total counter aether_gateway_decision_remote_total{route_kind="chat",reason="local_decision_miss"} 4 aether_gateway_decision_remote_total{route_kind="responses",reason="remote_decision_miss"} 2 # HELP aether_gateway_plan_fallback_total Number of requests that fell back to Python plan endpoints. # TYPE aether_gateway_plan_fallback_total counter aether_gateway_plan_fallback_total{route_kind="chat",reason="scheduler_decision_unsupported"} 3 # HELP aether_gateway_control_execute_fallback_total Number of requests that fell back to Python control execution. # TYPE aether_gateway_control_execute_fallback_total counter aether_gateway_control_execute_fallback_total{route_kind="chat",reason="control_execute_emergency"} 1 # HELP aether_gateway_remote_execute_emergency_total Number of requests that used remote emergency execution fallback. # TYPE aether_gateway_remote_execute_emergency_total counter aether_gateway_remote_execute_emergency_total{route_kind="chat",reason="control_execute_emergency"} 2 `) } mockHandlers['GET /api/admin/stats/comparison'] = async () => { await delay() requireAdmin() return createMockResponse({ current: { total_requests: 1200, total_tokens: 320000, total_cost: 180, actual_total_cost: 190, avg_response_time_ms: 350, error_requests: 42 }, comparison: { total_requests: 900, total_tokens: 260000, total_cost: 150, actual_total_cost: 160, avg_response_time_ms: 370, error_requests: 38 }, change_percent: { total_requests: 33.3, total_tokens: 23.1, total_cost: 20.0, actual_total_cost: 18.8, avg_response_time_ms: -5.4, error_requests: 10.5 }, current_start: '2026-02-01', current_end: '2026-02-07', comparison_start: '2026-01-25', comparison_end: '2026-01-31' }) } mockHandlers['GET /api/admin/stats/time-series'] = async () => { await delay() requireAdmin() return createMockResponse([ { date: '2026-02-01', total_requests: 120, input_tokens: 20000, output_tokens: 30000, total_tokens: 50000, total_cost: 12.3 }, { date: '2026-02-02', total_requests: 140, input_tokens: 22000, output_tokens: 32000, total_tokens: 54000, total_cost: 13.8 } ]) }