/** * API Fallbacks for ZCLAW Gateway * * Provides sensible default data when OpenFang API endpoints return 404. * This allows the UI to function gracefully even when backend features * are not yet implemented. */ // === Types === export interface QuickConfigFallback { agentName: string; agentRole: string; userName: string; userRole: string; agentNickname?: string; scenarios?: string[]; workspaceDir?: string; gatewayUrl?: string; gatewayToken?: string; skillsExtraDirs?: string[]; mcpServices?: Array<{ id: string; name: string; enabled: boolean }>; theme: 'light' | 'dark'; autoStart?: boolean; showToolCalls: boolean; restrictFiles?: boolean; autoSaveContext?: boolean; fileWatching?: boolean; privacyOptIn?: boolean; } export interface WorkspaceInfoFallback { path: string; resolvedPath: string; exists: boolean; fileCount: number; totalSize: number; } export interface UsageStatsFallback { totalSessions: number; totalMessages: number; totalTokens: number; byModel: Record; } export interface PluginStatusFallback { id: string; name?: string; status: 'active' | 'inactive' | 'error' | 'loading'; version?: string; description?: string; } export interface ScheduledTaskFallback { id: string; name: string; schedule: string; status: 'active' | 'paused' | 'completed' | 'error'; lastRun?: string; nextRun?: string; description?: string; } export interface SecurityLayerFallback { name: string; enabled: boolean; description?: string; } export interface SecurityStatusFallback { layers: SecurityLayerFallback[]; enabledCount: number; totalCount: number; securityLevel: 'critical' | 'high' | 'medium' | 'low'; } // Session type for usage calculation interface SessionForStats { id: string; messageCount?: number; metadata?: { tokens?: { input?: number; output?: number }; model?: string; }; } // Skill type for plugin fallback interface SkillForPlugins { id: string; name: string; source: 'builtin' | 'extra'; enabled?: boolean; description?: string; } // Trigger type for scheduled tasks interface TriggerForTasks { id: string; type: string; enabled: boolean; } // === Fallback Implementations === /** * Default quick config when /api/config/quick returns 404. * Uses sensible defaults for a new user experience. */ export function getQuickConfigFallback(): QuickConfigFallback { return { agentName: '默认助手', agentRole: 'AI 助手', userName: '用户', userRole: '用户', agentNickname: 'ZCLAW', scenarios: ['通用对话', '代码助手', '文档编写'], theme: 'dark', showToolCalls: true, autoSaveContext: true, fileWatching: true, privacyOptIn: false, }; } /** * Default workspace info when /api/workspace returns 404. * Returns a placeholder indicating workspace is not configured. */ export function getWorkspaceInfoFallback(): WorkspaceInfoFallback { // Try to get a reasonable default path const defaultPath = typeof window !== 'undefined' ? `${navigator.userAgent.includes('Windows') ? 'C:\\Users' : '/home'}/workspace` : '/workspace'; return { path: defaultPath, resolvedPath: defaultPath, exists: false, fileCount: 0, totalSize: 0, }; } /** * Calculate usage stats from session data when /api/stats/usage returns 404. */ export function getUsageStatsFallback(sessions: SessionForStats[] = []): UsageStatsFallback { const stats: UsageStatsFallback = { totalSessions: sessions.length, totalMessages: 0, totalTokens: 0, byModel: {}, }; for (const session of sessions) { stats.totalMessages += session.messageCount || 0; if (session.metadata?.tokens) { const input = session.metadata.tokens.input || 0; const output = session.metadata.tokens.output || 0; stats.totalTokens += input + output; if (session.metadata.model) { const model = session.metadata.model; if (!stats.byModel[model]) { stats.byModel[model] = { messages: 0, inputTokens: 0, outputTokens: 0 }; } stats.byModel[model].messages += session.messageCount || 0; stats.byModel[model].inputTokens += input; stats.byModel[model].outputTokens += output; } } } return stats; } /** * Convert skills to plugin status when /api/plugins/status returns 404. * OpenFang uses Skills instead of traditional plugins. */ export function getPluginStatusFallback(skills: SkillForPlugins[] = []): PluginStatusFallback[] { if (skills.length === 0) { // Return default built-in skills if none provided return [ { id: 'builtin-chat', name: 'Chat', status: 'active', description: '基础对话能力' }, { id: 'builtin-code', name: 'Code', status: 'active', description: '代码生成与分析' }, { id: 'builtin-file', name: 'File', status: 'active', description: '文件操作能力' }, ]; } return skills.map((skill) => ({ id: skill.id, name: skill.name, status: skill.enabled !== false ? 'active' : 'inactive', description: skill.description, })); } /** * Convert triggers to scheduled tasks when /api/scheduler/tasks returns 404. */ export function getScheduledTasksFallback(triggers: TriggerForTasks[] = []): ScheduledTaskFallback[] { return triggers .filter((t) => t.enabled) .map((trigger) => ({ id: trigger.id, name: `Trigger: ${trigger.type}`, schedule: 'event-based', status: 'active' as const, description: `Event trigger of type: ${trigger.type}`, })); } /** * Default security status when /api/security/status returns 404. * OpenFang has 16 security layers - show them with conservative defaults. */ export function getSecurityStatusFallback(): SecurityStatusFallback { const layers: SecurityLayerFallback[] = [ { name: 'Input Validation', enabled: true, description: '输入验证' }, { name: 'Output Sanitization', enabled: true, description: '输出净化' }, { name: 'Rate Limiting', enabled: true, description: '速率限制' }, { name: 'Authentication', enabled: true, description: '身份认证' }, { name: 'Authorization', enabled: true, description: '权限控制' }, { name: 'Encryption', enabled: true, description: '数据加密' }, { name: 'Audit Logging', enabled: true, description: '审计日志' }, { name: 'Sandboxing', enabled: false, description: '沙箱隔离' }, { name: 'Network Isolation', enabled: false, description: '网络隔离' }, { name: 'Resource Limits', enabled: true, description: '资源限制' }, { name: 'Secret Management', enabled: true, description: '密钥管理' }, { name: 'Certificate Pinning', enabled: false, description: '证书固定' }, { name: 'Code Signing', enabled: false, description: '代码签名' }, { name: 'Secure Boot', enabled: false, description: '安全启动' }, { name: 'TPM Integration', enabled: false, description: 'TPM 集成' }, { name: 'Zero Trust', enabled: false, description: '零信任' }, ]; const enabledCount = layers.filter((l) => l.enabled).length; const securityLevel = calculateSecurityLevel(enabledCount, layers.length); return { layers, enabledCount, totalCount: layers.length, securityLevel, }; } /** * Calculate security level based on enabled layers ratio. */ function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' { if (totalCount === 0) return 'low'; const ratio = enabledCount / totalCount; if (ratio >= 0.875) return 'critical'; // 14-16 layers if (ratio >= 0.625) return 'high'; // 10-13 layers if (ratio >= 0.375) return 'medium'; // 6-9 layers return 'low'; // 0-5 layers } // === Error Detection Helpers === /** * Check if an error is a 404 Not Found response. */ export function isNotFoundError(error: unknown): boolean { if (error instanceof Error) { const message = error.message.toLowerCase(); return message.includes('404') || message.includes('not found'); } if (typeof error === 'object' && error !== null) { const status = (error as { status?: number }).status; return status === 404; } return false; } /** * Check if an error is a network/connection error. */ export function isNetworkError(error: unknown): boolean { if (error instanceof Error) { const message = error.message.toLowerCase(); return ( message.includes('network') || message.includes('connection') || message.includes('timeout') || message.includes('abort') ); } return false; }