0xSerocleand98768b10 months agoHistory
234 lines · 8.3 KB · TypeScript
RawDownload
/** * API client for vLLM Studio Controller * Minimal client matching the controller endpoints */ import type { Recipe, RecipeWithStatus, HealthResponse, ProcessInfo } from './types'; const API_KEY_STORAGE = 'vllmstudio_api_key'; function getStoredApiKey(): string {  if (typeof window === 'undefined') return '';  try {    return window.localStorage.getItem(API_KEY_STORAGE) || '';  } catch {    return '';  }} class APIClient {  private baseUrl: string;  private useProxy: boolean;   constructor(baseUrl: string, useProxy = false) {    this.baseUrl = baseUrl;    this.useProxy = useProxy;  }   private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {    const headers: Record<string, string> = { 'Content-Type': 'application/json' };     // Add auth header    const storedKey = getStoredApiKey();    if (storedKey) {      headers['Authorization'] = `Bearer ${storedKey}`;    }     const path = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;    const url = this.useProxy ? `${this.baseUrl}/${path}` : `${this.baseUrl}${endpoint}`;     const response = await fetch(url, { ...options, headers: { ...headers, ...options.headers } });     if (!response.ok) {      const error = await response.json().catch(() => ({ detail: 'Request failed' }));      throw new Error(error.detail || error.error?.message || `HTTP ${response.status}`);    }     const text = await response.text();    return text ? JSON.parse(text) : (null as unknown as T);  }   // Health & Status  async getHealth(): Promise<HealthResponse> {    return this.request('/health');  }   async getStatus(): Promise<{ running: boolean; process: ProcessInfo | null; inference_port: number }> {    const data = await this.request<{ running: boolean; process: ProcessInfo | null; inference_port: number }>('/status');    return {      running: data.running ?? !!data.process,      process: data.process ?? null,      inference_port: data.inference_port || 8000,    };  }   // Recipes  async getRecipes(): Promise<{ recipes: RecipeWithStatus[] }> {    const data = await this.request<RecipeWithStatus[]>('/recipes');    return { recipes: Array.isArray(data) ? data : [] };  }   async getRecipe(id: string): Promise<RecipeWithStatus> {    return this.request(`/recipes/${id}`);  }   async createRecipe(recipe: Recipe): Promise<{ success: boolean; id: string }> {    return this.request('/recipes', { method: 'POST', body: JSON.stringify(recipe) });  }   async updateRecipe(id: string, recipe: Recipe): Promise<{ success: boolean; id: string }> {    return this.request(`/recipes/${id}`, { method: 'PUT', body: JSON.stringify(recipe) });  }   async deleteRecipe(id: string): Promise<void> {    return this.request(`/recipes/${id}`, { method: 'DELETE' });  }   // Model lifecycle  async launch(recipeId: string, force = false): Promise<{ success: boolean; pid?: number; message: string }> {    return this.request(`/launch/${recipeId}?force=${force}`, { method: 'POST' });  }   async evict(force = false): Promise<{ success: boolean; evicted_pid?: number }> {    return this.request(`/evict?force=${force}`, { method: 'POST' });  }   async waitReady(timeout = 300): Promise<{ ready: boolean; elapsed: number; error?: string }> {    return this.request(`/wait-ready?timeout=${timeout}`);  }   // OpenAI models endpoint (proxied to vLLM)  async getOpenAIModels(): Promise<{ data: Array<{ id: string; root?: string; max_model_len?: number }> }> {    return this.request('/v1/models');  }   // Chat sessions  async getChatSessions(): Promise<{ sessions: Array<{ id: string; title: string; model?: string; created_at: string; updated_at: string }> }> {    const data = await this.request<Array<{ id: string; title: string; model?: string; created_at: string; updated_at: string }>>('/chats');    return { sessions: Array.isArray(data) ? data : [] };  }   async getChatSession(id: string): Promise<{ session: any }> {    return this.request(`/chats/${id}`);  }   async createChatSession(data: { title?: string; model?: string }): Promise<{ session: any }> {    return this.request('/chats', { method: 'POST', body: JSON.stringify(data) });  }   async updateChatSession(id: string, data: { title?: string; model?: string }): Promise<void> {    return this.request(`/chats/${id}`, { method: 'PUT', body: JSON.stringify(data) });  }   async deleteChatSession(id: string): Promise<void> {    return this.request(`/chats/${id}`, { method: 'DELETE' });  }   async forkChatSession(id: string, data: { message_id?: string; model?: string; title?: string }): Promise<{ session: any }> {    return this.request(`/chats/${id}/fork`, { method: 'POST', body: JSON.stringify(data) });  }   async addChatMessage(sessionId: string, message: any): Promise<any> {    return this.request(`/chats/${sessionId}/messages`, { method: 'POST', body: JSON.stringify(message) });  }   async getChatUsage(sessionId: string): Promise<{ prompt_tokens: number; completion_tokens: number; total_tokens: number; estimated_cost_usd?: number }> {    return this.request(`/chats/${sessionId}/usage`);  }   // MCP  async getMCPServers(): Promise<Array<{ name: string; command: string; args?: string[]; env?: Record<string, string>; enabled?: boolean }>> {    return this.request('/mcp/servers');  }   async getMCPTools(): Promise<{ tools: Array<{ name: string; description?: string; input_schema?: any; server: string }> }> {    return this.request('/mcp/tools');  }   async callMCPTool(server: string, tool: string, args: Record<string, unknown>): Promise<{ result: any }> {    return this.request(`/mcp/tools/${server}/${tool}`, { method: 'POST', body: JSON.stringify(args) });  }   // Tokenization  async tokenizeChatCompletions(data: { model: string; messages: unknown[]; tools?: unknown[] }): Promise<{ input_tokens?: number; breakdown?: { messages?: number; tools?: number } }> {    return this.request('/v1/chat/completions/tokenize', { method: 'POST', body: JSON.stringify(data) });  }   async countTextTokens(data: { model: string; text: string }): Promise<{ num_tokens?: number }> {    return this.request('/v1/tokens/count', { method: 'POST', body: JSON.stringify(data) });  }   // Log sessions  async getLogSessions(): Promise<{ sessions: any[] }> {    return this.request('/logs');  }   async getLogContent(sessionId: string, limit?: number): Promise<{ content: string }> {    const query = limit ? `?limit=${limit}` : '';    return this.request(`/logs/${sessionId}${query}`);  }   async getLogs(sessionId: string, limit?: number): Promise<{ logs: string[] }> {    const query = limit ? `?limit=${limit}` : '';    return this.request(`/logs/${sessionId}${query}`);  }   async deleteLogSession(sessionId: string): Promise<void> {    return this.request(`/logs/${sessionId}`, { method: 'DELETE' });  }   // Models discovery  async getModels(): Promise<{ models: any[] }> {    return this.request('/v1/studio/models');  }   async getGPUs(): Promise<{ gpus: any[] }> {    return this.request('/gpus');  }   async calculateVRAM(data: any): Promise<any> {    return this.request('/vram-calculator', { method: 'POST', body: JSON.stringify(data) });  }   async getMetrics(): Promise<any> {    return this.request('/v1/metrics/vllm');  }   // Model switching  async switchModel(recipeId: string, force = true): Promise<any> {    return this.launch(recipeId, force);  }   // MCP management  async addMCPServer(server: any): Promise<void> {    return this.request('/mcp/servers', { method: 'POST', body: JSON.stringify(server) });  }   async updateMCPServer(name: string, server: any): Promise<void> {    return this.request(`/mcp/servers/${name}`, { method: 'PUT', body: JSON.stringify(server) });  }   async removeMCPServer(name: string): Promise<void> {    return this.request(`/mcp/servers/${name}`, { method: 'DELETE' });  }   // Nav helpers  async evictModel(force = false): Promise<{ success: boolean }> {    return this.evict(force);  }   async exportRecipes(): Promise<{ content: any }> {    const { recipes } = await this.getRecipes();    return { content: { recipes } };  }} // Singleton for client-side (uses proxy)export const api = new APIClient('/api/proxy', true); // Factory for server-sideexport function createServerAPI(backendUrl?: string) {  return new APIClient(backendUrl || process.env.BACKEND_URL || 'http://localhost:8080');} export default api;