/** * Configurable API Client */ import type { Session, Message, HealthStatus, FileListResponse, FileReadResponse, FileTreeResponse, ServerConfig, CommandInfo, CommandSearchResult, CommandExecuteResult, CommandListResponse, CreateCommandInput, UpdateCommandInput, CommandContent, MCPServerStatus, MCPToolInfo, MCPConfig, HookConfig, FileHookConfig, ShellCommandConfig, HookTestResult, AgentListItem, AgentDetail, AgentInput, AgentDefaults, CheckpointListItem, CheckpointDetail, CheckpointStats, DiffInfo, FileDiffDetail, RestoreOptions, RestoreResult, SafetyCheckResult, UnrevertStatus, UnrevertResult, // Provider types ProviderListItem, ProviderDetail, ModelInfo, CustomProviderDefinition, ProviderConfig, ConnectionTestResult, // Context types ContextUsageInfo, CompressionResult, // File search types FileSearchResponse, // LSP types LSPServer, DiagnosticsResponse, } from './types.js'; // Re-export types export type { Session, Message, HealthStatus, FileInfo, FileListResponse, FileReadResponse, FileTreeNode, FileTreeResponse, ServerConfig, CommandInfo, CommandSearchResult, CommandExecuteResult, CommandListResponse, CreateCommandInput, UpdateCommandInput, CommandContent, MCPServerStatus, MCPServerStatusType, MCPToolInfo, MCPConfig, MCPServerConfigInfo, // Hooks types HookConfig, FileHookConfig, ShellCommandConfig, HookTestResult, // Agent types AgentMode, AgentModelConfig, AgentToolConfig, PermissionRule, AgentBashPermission, AgentFilePermission, AgentGitPermission, AgentPermission, AgentListItem, AgentDetail, AgentInput, AgentDefaults, // Checkpoint types CheckpointTrigger, FileChangeType, CheckpointListItem, CheckpointDetail, FileChange, DiffInfo, FileDiffDetail, RestoreMode, RestoreOptions, RestoreResult, SafetyCheckResult, CheckpointStats, UnrevertStatus, UnrevertResult, // Provider types BuiltinProviderType, ProviderType, ModelCapabilities, ModelInfo, ProviderListItem, ProviderDetail, CustomProviderDefinition, ProviderConfig, ConnectionTestResult, // Context compression types TokenUsage, ContextUsageInfo, CompressionStatus, CompressionType, CompressionResult, // WebSocket error types ConfigErrorPayload, // File search types FileSearchResult, FileSearchResponse, // Agent mode types AgentModeType, // Question types (for ask_user_question tool) QuestionOption, Question, QuestionMessagePart, // LSP types LSPServer, FileDiagnostic, DiagnosticsSummary, SingleFileDiagnosticsResponse, AllFilesDiagnosticsResponse, DiagnosticsResponse, } from './types.js'; // API Configuration interface ApiConfig { baseUrl: string; wsBaseUrl: () => string; healthUrl: () => string; } let apiConfig: ApiConfig = { baseUrl: '/api', wsBaseUrl: () => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; return `${protocol}//${window.location.host}/api`; }, healthUrl: () => '/health', }; /** * Configure API client for different environments */ export function configureApiClient(config: { baseUrl: string; wsBaseUrl: string | (() => string); healthUrl?: string | (() => string); }) { apiConfig = { baseUrl: config.baseUrl, wsBaseUrl: typeof config.wsBaseUrl === 'function' ? config.wsBaseUrl : () => config.wsBaseUrl as string, healthUrl: config.healthUrl ? typeof config.healthUrl === 'function' ? config.healthUrl : () => config.healthUrl as string : () => '/health', }; } async function request(method: string, path: string, body?: unknown): Promise { const response = await fetch(`${apiConfig.baseUrl}${path}`, { method, headers: { 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, }); if (!response.ok) { const error = await response.json().catch(() => ({ error: response.statusText })); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); } // Health export async function getHealth(): Promise { const healthUrl = apiConfig.healthUrl(); const response = await fetch(healthUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); } // Sessions export async function listSessions(): Promise<{ success: boolean; data: Session[] }> { return request('GET', '/sessions'); } export async function createSession(name?: string): Promise<{ success: boolean; data: Session }> { return request('POST', '/sessions', { name }); } export async function getSession(id: string): Promise<{ success: boolean; data: Session }> { return request('GET', `/sessions/${id}`); } export async function deleteSession(id: string): Promise<{ success: boolean }> { return request('DELETE', `/sessions/${id}`); } // Messages export async function getMessages( sessionId: string ): Promise<{ success: boolean; data: Message[] }> { return request('GET', `/sessions/${sessionId}/messages`); } export async function sendMessage( sessionId: string, content: string ): Promise<{ success: boolean; data: Message }> { return request('POST', `/sessions/${sessionId}/messages`, { content }); } // WebSocket export function createWebSocket(sessionId: string): WebSocket { const wsBase = apiConfig.wsBaseUrl(); return new WebSocket(`${wsBase}/ws/${sessionId}`); } // Files export async function getWorkingDirectory(): Promise<{ success: boolean; data: { workingDirectory: string; separator: string }; }> { return request('GET', '/files'); } export async function listFiles( path: string = '.', showHidden: boolean = false ): Promise { const params = new URLSearchParams({ path }); if (showHidden) params.set('hidden', 'true'); return request('GET', `/files/list?${params}`); } export async function readFile(path: string): Promise { return request('GET', `/files/read?path=${encodeURIComponent(path)}`); } export interface FileWriteResponse { success: boolean; data: { path: string; name: string; size: number; modified: string; }; } export async function writeFile(path: string, content: string): Promise { return request('PUT', '/files/write', { path, content }); } export async function getFileTree(path: string = '.', depth: number = 3): Promise { const params = new URLSearchParams({ path, depth: String(depth) }); return request('GET', `/files/tree?${params}`); } // Config export async function getConfig(): Promise<{ success: boolean; data: ServerConfig }> { return request('GET', '/config'); } export async function updateConfig( config: Partial ): Promise<{ success: boolean; data: ServerConfig }> { return request('PATCH', '/config', config); } // Commands export async function listCommands(): Promise<{ success: boolean; data: CommandListResponse }> { return request('GET', '/commands'); } export async function getCommand(name: string): Promise<{ success: boolean; data: CommandInfo }> { return request('GET', `/commands/${encodeURIComponent(name)}`); } export async function executeCommand( name: string, args: string = '' ): Promise<{ success: boolean; data?: CommandExecuteResult; error?: string }> { return request('POST', `/commands/${encodeURIComponent(name)}/execute`, { arguments: args, }); } export async function searchCommands( query: string, limit: number = 10 ): Promise<{ success: boolean; data: CommandSearchResult[] }> { return request('POST', '/commands/search', { query, limit }); } export async function reloadCommands(): Promise<{ success: boolean; data: { message: string; stats: { total: number; bySource: Record } }; }> { return request('POST', '/commands/reload'); } // Commands CRUD export async function createCommand( input: CreateCommandInput ): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> { return request('POST', '/commands', input); } export async function getCommandContent( name: string ): Promise<{ success: boolean; data?: CommandContent; error?: string }> { return request('GET', `/commands/${encodeURIComponent(name)}/content`); } export async function updateCommand( name: string, input: UpdateCommandInput ): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> { return request('PUT', `/commands/${encodeURIComponent(name)}`, input); } export async function deleteCommand( name: string ): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> { return request('DELETE', `/commands/${encodeURIComponent(name)}`); } // ============ MCP API ============ /** * 获取所有 MCP 服务器状态 */ export async function listMCPServers(): Promise<{ success: boolean; data: MCPServerStatus[]; error?: string; }> { return request('GET', '/mcp/servers'); } /** * 获取单个 MCP 服务器详情 */ export async function getMCPServer(name: string): Promise<{ success: boolean; data?: MCPServerStatus; error?: string; }> { return request('GET', `/mcp/servers/${encodeURIComponent(name)}`); } /** * 连接 MCP 服务器 */ export async function connectMCPServer(name: string): Promise<{ success: boolean; data?: { message: string; status: MCPServerStatus }; error?: string; }> { return request('POST', `/mcp/servers/${encodeURIComponent(name)}/connect`); } /** * 断开 MCP 服务器 */ export async function disconnectMCPServer(name: string): Promise<{ success: boolean; data?: { message: string; status: MCPServerStatus }; error?: string; }> { return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disconnect`); } /** * 启用 MCP 服务器 */ export async function enableMCPServer(name: string): Promise<{ success: boolean; data?: { message: string; status: MCPServerStatus }; error?: string; }> { return request('POST', `/mcp/servers/${encodeURIComponent(name)}/enable`); } /** * 禁用 MCP 服务器 */ export async function disableMCPServer(name: string): Promise<{ success: boolean; data?: { message: string; status: MCPServerStatus }; error?: string; }> { return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disable`); } /** * 获取所有 MCP 工具 */ export async function listMCPTools(): Promise<{ success: boolean; data: MCPToolInfo[]; error?: string; }> { return request('GET', '/mcp/tools'); } /** * 获取单个 MCP 工具详情 */ export async function getMCPTool(name: string): Promise<{ success: boolean; data?: MCPToolInfo; error?: string; }> { return request('GET', `/mcp/tools/${encodeURIComponent(name)}`); } /** * 获取 MCP 配置 */ export async function getMCPConfig(): Promise<{ success: boolean; data?: MCPConfig; error?: string; }> { return request('GET', '/mcp/config'); } // ============ Hooks API ============ /** * 获取完整钩子配置 */ export async function getHooksConfig(): Promise<{ success: boolean; data: HookConfig; error?: string; }> { return request('GET', '/hooks/config'); } /** * 更新完整钩子配置 */ export async function updateHooksConfig(config: HookConfig): Promise<{ success: boolean; data: HookConfig; error?: string; }> { return request('PUT', '/hooks/config', config); } /** * 获取 file_edited 钩子配置 */ export async function getFileEditedHooks(): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('GET', '/hooks/file-edited'); } /** * 更新 file_edited 钩子配置 */ export async function updateFileEditedHooks(hooks: FileHookConfig): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('PUT', '/hooks/file-edited', hooks); } /** * 获取 file_created 钩子配置 */ export async function getFileCreatedHooks(): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('GET', '/hooks/file-created'); } /** * 更新 file_created 钩子配置 */ export async function updateFileCreatedHooks(hooks: FileHookConfig): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('PUT', '/hooks/file-created', hooks); } /** * 获取 file_deleted 钩子配置 */ export async function getFileDeletedHooks(): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('GET', '/hooks/file-deleted'); } /** * 更新 file_deleted 钩子配置 */ export async function updateFileDeletedHooks(hooks: FileHookConfig): Promise<{ success: boolean; data: FileHookConfig; error?: string; }> { return request('PUT', '/hooks/file-deleted', hooks); } /** * 获取 session_completed 钩子配置 */ export async function getSessionCompletedHooks(): Promise<{ success: boolean; data: ShellCommandConfig[]; error?: string; }> { return request('GET', '/hooks/session-completed'); } /** * 更新 session_completed 钩子配置 */ export async function updateSessionCompletedHooks(hooks: ShellCommandConfig[]): Promise<{ success: boolean; data: ShellCommandConfig[]; error?: string; }> { return request('PUT', '/hooks/session-completed', hooks); } /** * 测试执行钩子命令 */ export async function testHookCommand(command: ShellCommandConfig): Promise<{ success: boolean; data?: HookTestResult; error?: string; }> { return request('POST', '/hooks/test', command); } // ============ Agents API ============ /** * 获取所有 Agent 列表 */ export async function listAgents(): Promise<{ success: boolean; data: AgentListItem[]; error?: string; }> { return request('GET', '/agents'); } /** * 获取单个 Agent 详情 */ export async function getAgent(name: string): Promise<{ success: boolean; data?: AgentDetail; error?: string; }> { return request('GET', `/agents/${encodeURIComponent(name)}`); } /** * 创建新 Agent */ export async function createAgent( name: string, config: AgentInput ): Promise<{ success: boolean; data?: AgentDetail; error?: string; }> { return request('POST', '/agents', { name, ...config }); } /** * 更新 Agent */ export async function updateAgent( name: string, config: AgentInput ): Promise<{ success: boolean; data?: AgentDetail; error?: string; }> { return request('PUT', `/agents/${encodeURIComponent(name)}`, config); } /** * 删除 Agent */ export async function deleteAgent(name: string): Promise<{ success: boolean; error?: string; }> { return request('DELETE', `/agents/${encodeURIComponent(name)}`); } /** * 获取预设 Agent 列表 */ export async function listPresetAgents(): Promise<{ success: boolean; data: AgentListItem[]; error?: string; }> { return request('GET', '/agents/presets'); } /** * 获取全局默认配置 */ export async function getAgentDefaults(): Promise<{ success: boolean; data: AgentDefaults; error?: string; }> { return request('GET', '/agents/defaults'); } /** * 更新全局默认配置 */ export async function updateAgentDefaults(defaults: AgentDefaults): Promise<{ success: boolean; data: AgentDefaults; error?: string; }> { return request('PUT', '/agents/defaults', defaults); } // ============ Checkpoints API ============ /** * 获取所有检查点列表 */ export async function listCheckpoints(): Promise<{ success: boolean; data: CheckpointListItem[]; error?: string; }> { return request('GET', '/checkpoints'); } /** * 获取检查点统计信息 */ export async function getCheckpointStats(): Promise<{ success: boolean; data: CheckpointStats; error?: string; }> { return request('GET', '/checkpoints/stats'); } /** * 获取最新检查点 */ export async function getLatestCheckpoint(): Promise<{ success: boolean; data: CheckpointDetail | null; error?: string; }> { return request('GET', '/checkpoints/latest'); } /** * 获取单个检查点详情 */ export async function getCheckpoint(id: string): Promise<{ success: boolean; data?: CheckpointDetail; error?: string; }> { return request('GET', `/checkpoints/${encodeURIComponent(id)}`); } /** * 创建手动检查点 */ export async function createCheckpoint(options?: { name?: string; description?: string; }): Promise<{ success: boolean; data?: CheckpointDetail; error?: string; }> { return request('POST', '/checkpoints', options || {}); } /** * 删除检查点 */ export async function deleteCheckpoint(id: string): Promise<{ success: boolean; data?: { deleted: boolean }; error?: string; }> { return request('DELETE', `/checkpoints/${encodeURIComponent(id)}`); } /** * 获取检查点与当前工作区的差异 */ export async function getCheckpointDiff(id: string): Promise<{ success: boolean; data?: DiffInfo; error?: string; }> { return request('GET', `/checkpoints/${encodeURIComponent(id)}/diff`); } /** * 获取单个文件的详细差异 */ export async function getFileDiff(checkpointId: string, filePath: string): Promise<{ success: boolean; data?: FileDiffDetail; error?: string; }> { const params = new URLSearchParams({ path: filePath }); return request('GET', `/checkpoints/${encodeURIComponent(checkpointId)}/file-diff?${params}`); } /** * 回滚到检查点 */ export async function restoreCheckpoint( id: string, options?: RestoreOptions ): Promise<{ success: boolean; data?: RestoreResult; error?: string; }> { return request('POST', `/checkpoints/${encodeURIComponent(id)}/restore`, options || {}); } /** * 预览回滚(dry run) */ export async function previewRestore( id: string, options?: { mode?: RestoreOptions['mode']; files?: string[] } ): Promise<{ success: boolean; data?: RestoreResult; error?: string; }> { const params = new URLSearchParams(); if (options?.mode) params.set('mode', options.mode); if (options?.files) params.set('files', options.files.join(',')); return request('GET', `/checkpoints/${encodeURIComponent(id)}/restore/preview?${params}`); } /** * 撤销最近一次回滚 */ export async function unrevert(): Promise<{ success: boolean; data?: UnrevertResult; error?: string; }> { return request('POST', '/checkpoints/unrevert'); } /** * 检查是否可撤销回滚 */ export async function getUnrevertStatus(): Promise<{ success: boolean; data?: UnrevertStatus; error?: string; }> { return request('GET', '/checkpoints/unrevert/status'); } /** * 执行安全检查 */ export async function checkSafety(id: string): Promise<{ success: boolean; data?: SafetyCheckResult; error?: string; }> { return request('GET', `/checkpoints/${encodeURIComponent(id)}/safety-check`); } /** * 清理过期检查点 */ export async function cleanupCheckpoints(): Promise<{ success: boolean; data?: { deleted: number }; error?: string; }> { return request('POST', '/checkpoints/cleanup'); } /** * 获取会话的所有检查点 */ export async function getSessionCheckpoints(sessionId: string): Promise<{ success: boolean; data: CheckpointListItem[]; error?: string; }> { return request('GET', `/checkpoints/sessions/${encodeURIComponent(sessionId)}`); } /** * 获取消息关联的检查点 */ export async function getMessageCheckpoints(messageId: string): Promise<{ success: boolean; data: CheckpointListItem[]; error?: string; }> { return request('GET', `/checkpoints/messages/${encodeURIComponent(messageId)}`); } // ============ Providers API ============ /** * 获取所有提供商列表 */ export async function listProviders(): Promise<{ success: boolean; data: ProviderListItem[]; error?: string; }> { return request('GET', '/providers'); } /** * 获取单个提供商详情 */ export async function getProvider(id: string): Promise<{ success: boolean; data?: ProviderDetail; error?: string; }> { return request('GET', `/providers/${encodeURIComponent(id)}`); } /** * 获取提供商的模型列表 */ export async function getProviderModels(id: string): Promise<{ success: boolean; data: ModelInfo[]; error?: string; }> { return request('GET', `/providers/${encodeURIComponent(id)}/models`); } /** * 测试提供商连接 */ export async function testProviderConnection( id: string, apiKey?: string ): Promise<{ success: boolean; data: ConnectionTestResult; error?: string; }> { return request('POST', `/providers/${encodeURIComponent(id)}/test`, apiKey ? { apiKey } : {}); } /** * 注册自定义提供商 */ export async function registerProvider( definition: CustomProviderDefinition ): Promise<{ success: boolean; message?: string; error?: string; }> { return request('POST', '/providers', definition); } /** * 更新提供商配置 */ export async function updateProviderConfig( id: string, config: ProviderConfig ): Promise<{ success: boolean; message?: string; error?: string; }> { return request('PUT', `/providers/${encodeURIComponent(id)}`, config); } /** * 删除自定义提供商 */ export async function deleteProvider(id: string): Promise<{ success: boolean; message?: string; error?: string; }> { return request('DELETE', `/providers/${encodeURIComponent(id)}`); } /** * 添加自定义模型 */ export async function addProviderModel( providerId: string, model: ModelInfo ): Promise<{ success: boolean; message?: string; error?: string; }> { return request('POST', `/providers/${encodeURIComponent(providerId)}/models`, model); } /** * 删除自定义模型 */ export async function deleteProviderModel( providerId: string, modelId: string ): Promise<{ success: boolean; message?: string; error?: string; }> { return request('DELETE', `/providers/${encodeURIComponent(providerId)}/models/${encodeURIComponent(modelId)}`); } // ============ Context Compression API ============ /** * 获取会话上下文使用情况 */ export async function getContextUsage(sessionId: string): Promise<{ success: boolean; data?: ContextUsageInfo; error?: string; }> { return request('GET', `/sessions/${encodeURIComponent(sessionId)}/context`); } /** * 触发上下文压缩 */ export async function compressContext( sessionId: string, options?: { force?: boolean } ): Promise<{ success: boolean; data?: CompressionResult; error?: string; }> { return request('POST', `/sessions/${encodeURIComponent(sessionId)}/compress`, options || {}); } // ============ File Search API ============ /** * 模糊搜索项目文件 */ export async function searchFiles( query: string = '', limit: number = 10, type: 'file' | 'directory' | 'all' = 'file' ): Promise { const params = new URLSearchParams({ query, limit: String(limit), type, }); return request('GET', `/files/search?${params}`); } // ============ LSP API ============ /** * 获取所有语言服务器列表 */ export async function listLSPServers(): Promise<{ success: boolean; data: LSPServer[]; error?: string; }> { return request('GET', '/lsp/servers'); } /** * 获取单个语言服务器详情 */ export async function getLSPServer(id: string): Promise<{ success: boolean; data?: LSPServer; error?: string; }> { return request('GET', `/lsp/servers/${encodeURIComponent(id)}`); } /** * 安装语言服务器 */ export async function installLSPServer(id: string): Promise<{ success: boolean; data?: { message: string; server: LSPServer }; error?: string; }> { return request('POST', `/lsp/servers/${encodeURIComponent(id)}/install`); } /** * 启动语言服务器 * @param id 服务器 ID * @param filePath 需要提供一个文件路径来触发对应语言的服务器 */ export async function startLSPServer( id: string, filePath: string ): Promise<{ success: boolean; data?: { message: string; isFirstStart: boolean; runningServers: string[] }; error?: string; }> { return request('POST', `/lsp/servers/${encodeURIComponent(id)}/start`, { filePath }); } /** * 停止语言服务器 */ export async function stopLSPServer(id: string): Promise<{ success: boolean; data?: { message: string; runningServers: string[] }; error?: string; }> { return request('POST', `/lsp/servers/${encodeURIComponent(id)}/stop`); } /** * 获取正在运行的语言服务器列表 */ export async function getRunningLSPServers(): Promise<{ success: boolean; data: string[]; error?: string; }> { return request('GET', '/lsp/running'); } /** * 获取诊断信息 * @param file 可选,指定文件路径获取单个文件的诊断 */ export async function getLSPDiagnostics(file?: string): Promise<{ success: boolean; data: DiagnosticsResponse; error?: string; }> { const params = file ? `?file=${encodeURIComponent(file)}` : ''; return request('GET', `/lsp/diagnostics${params}`); }