UNPKG

@codervisor/devlog-cli

Version:

Command-line interface for devlog - Extract and stream chat history to devlog server

261 lines (234 loc) 7.36 kB
/** * HTTP Client for DevLog ChatHub API * * Handles communication with the devlog server API endpoints, * specifically for streaming chat data to the ChatHub service. */ import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { ChatSession, ChatMessage } from '@codervisor/devlog-core'; export interface ChatImportRequest { sessions: ChatSession[]; messages: ChatMessage[]; source: string; workspaceInfo?: { name?: string; path?: string; [key: string]: unknown; }; } export interface ChatImportResponse { success: boolean; importId: string; status: string; progress: { importId: string; status: 'pending' | 'processing' | 'completed' | 'failed'; progress: { sessionsProcessed: number; messagesProcessed: number; totalSessions: number; totalMessages: number; percentage: number; }; startedAt: string; completedAt?: string; error?: string; }; message: string; } export interface ChatProgressResponse { success: boolean; progress: { importId: string; status: 'pending' | 'processing' | 'completed' | 'failed'; progress: { sessionsProcessed: number; messagesProcessed: number; totalSessions: number; totalMessages: number; percentage: number; }; startedAt: string; completedAt?: string; error?: string; }; } export interface DevlogApiClientConfig { baseURL: string; timeout?: number; retries?: number; retryDelay?: number; } export class DevlogApiClient { private client: AxiosInstance; private config: DevlogApiClientConfig; constructor(config: DevlogApiClientConfig) { this.config = { timeout: 30000, retries: 3, retryDelay: 1000, ...config, }; this.client = axios.create({ baseURL: this.config.baseURL, timeout: this.config.timeout, headers: { 'Content-Type': 'application/json', }, }); // Add request/response interceptors for error handling this.setupInterceptors(); } private setupInterceptors(): void { // Request interceptor for logging this.client.interceptors.request.use( (config: InternalAxiosRequestConfig) => { console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`); return config; }, (error: any) => { console.error('[API] Request error:', error); return Promise.reject(error); }, ); // Response interceptor for error handling and retries this.client.interceptors.response.use( (response: AxiosResponse) => { console.log(`[API] ${response.status} ${response.config.url}`); return response; }, async (error: AxiosError) => { const originalRequest = error.config as any; // Don't retry if we've exceeded max retries if (originalRequest._retryCount >= (this.config.retries || 3)) { console.error('[API] Max retries exceeded:', error.message); return Promise.reject(this.formatError(error)); } // Retry on network errors or 5xx server errors if ( error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT' || (error.response?.status && error.response.status >= 500) ) { originalRequest._retryCount = (originalRequest._retryCount || 0) + 1; console.log(`[API] Retrying request (attempt ${originalRequest._retryCount})...`); // Wait before retrying await new Promise((resolve) => setTimeout(resolve, this.config.retryDelay! * originalRequest._retryCount), ); return this.client(originalRequest); } return Promise.reject(this.formatError(error)); }, ); } private formatError(error: AxiosError): Error { if (error.response) { // Server responded with error status const message = (error.response.data as any)?.error || error.response.statusText; return new Error(`API Error (${error.response.status}): ${message}`); } else if (error.request) { // Request made but no response received return new Error(`Network Error: Could not connect to server at ${this.config.baseURL}`); } else { // Something else happened return new Error(`Request Error: ${error.message}`); } } /** * Test connection to the devlog server */ async testConnection(): Promise<boolean> { try { const response = await this.client.get('/api/health'); return response.status === 200; } catch (error) { console.error('[API] Connection test failed:', error); return false; } } /** * Import chat data to a workspace */ async importChatData(projectId: string, data: ChatImportRequest): Promise<ChatImportResponse> { try { const response = await this.client.post(`/api/projects/${projectId}/chat/import`, data); return response.data; } catch (error) { throw error instanceof Error ? error : new Error('Failed to import chat data'); } } /** * Get import progress status */ async getImportProgress(projectId: string, importId: string): Promise<ChatProgressResponse> { try { const response = await this.client.get( `/api/projects/${projectId}/chat/import?importId=${importId}`, ); return response.data; } catch (error) { throw error instanceof Error ? error : new Error('Failed to get import progress'); } } /** * List workspaces available on the server */ async listProjects(): Promise<any[]> { try { const response = await this.client.get('/api/projects'); return response.data.workspaces || []; } catch (error) { throw error instanceof Error ? error : new Error('Failed to list workspaces'); } } /** * Get workspace details */ async getProject(projectId: string): Promise<any> { try { const response = await this.client.get(`/api/projects/${projectId}`); return response.data; } catch (error) { throw error instanceof Error ? error : new Error(`Failed to get workspace ${projectId}`); } } /** * Search chat content in a workspace */ async searchChatContent( projectId: string, query: string, options: { limit?: number; caseSensitive?: boolean; searchType?: 'exact' | 'fuzzy' | 'semantic'; } = {}, ): Promise<any> { try { const params = new URLSearchParams({ query, limit: (options.limit || 50).toString(), caseSensitive: (options.caseSensitive || false).toString(), searchType: options.searchType || 'exact', }); const response = await this.client.get( `/api/projects/${projectId}/chat/search?${params.toString()}`, ); return response.data; } catch (error) { throw error instanceof Error ? error : new Error('Failed to search chat content'); } } /** * Get chat statistics for a workspace */ async getChatStats(projectId: string): Promise<any> { try { const response = await this.client.get(`/api/projects/${projectId}/chat/stats`); return response.data; } catch (error) { throw error instanceof Error ? error : new Error('Failed to get chat statistics'); } } }