UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

230 lines 7.87 kB
import chalk from 'chalk'; export class LocalModelsService { models = []; lastRefresh = null; refreshInterval = 60000; servers = []; constructor() { this.servers = [ { type: 'ollama', baseUrl: 'http://localhost:11434', available: false, models: [] }, { type: 'openai', baseUrl: 'http://localhost:1234', available: false, models: [] }, { type: 'openai', baseUrl: 'http://localhost:8080', available: false, models: [] }, ]; this.discoverModels().catch(() => { }); } getAvailableModels() { if (this.shouldRefresh()) { this.discoverModels().catch(() => { }); } return this.models; } getModelsByProvider(provider) { const server = this.servers.find(s => (provider === 'ollama' && s.type === 'ollama') || (provider === 'lmstudio' && s.baseUrl.includes('1234')) || (provider === 'local' && s.available)); if (!server) return []; return server.models.map(name => ({ name, supports_tools: true, })); } async isAnyServerAvailable() { for (const server of this.servers) { if (await this.checkServer(server)) { return true; } } return false; } async getAvailableServerUrl() { for (const server of this.servers) { if (await this.checkServer(server)) { return server.baseUrl; } } return null; } async discoverModels() { const allModels = []; for (const server of this.servers) { try { const models = await this.discoverFromServer(server); if (models.length > 0) { server.available = true; server.models = models; allModels.push(...models); } else { server.available = false; server.models = []; } } catch { server.available = false; server.models = []; } } this.models = [...new Set(allModels)]; this.lastRefresh = new Date(); } async discoverFromServer(server) { if (server.type === 'ollama') { return this.discoverOllamaModels(server.baseUrl); } else { return this.discoverOpenAIModels(server.baseUrl); } } async discoverOllamaModels(baseUrl) { try { const response = await fetch(`${baseUrl}/api/tags`, { method: 'GET', signal: AbortSignal.timeout(2000), }); if (!response.ok) return []; const data = await response.json(); const models = data.models || []; return models.map((model) => { return model.name || model.model; }); } catch { return []; } } async discoverOpenAIModels(baseUrl) { try { const response = await fetch(`${baseUrl}/v1/models`, { method: 'GET', signal: AbortSignal.timeout(2000), }); if (!response.ok) return []; const data = await response.json(); const models = data.data || []; return models.map((model) => model.id); } catch { return []; } } async checkServer(server) { try { const endpoints = server.type === 'ollama' ? [`${server.baseUrl}/api/version`] : [`${server.baseUrl}/v1/models`]; for (const endpoint of endpoints) { const response = await fetch(endpoint, { method: 'GET', signal: AbortSignal.timeout(1000), }); if (response.ok) { return true; } } return false; } catch { return false; } } shouldRefresh() { if (!this.lastRefresh) return true; const now = new Date(); const diff = now.getTime() - this.lastRefresh.getTime(); return diff > this.refreshInterval; } async getModelInfo(modelName) { const ollamaServer = this.servers.find(s => s.type === 'ollama' && s.available); if (!ollamaServer) return null; try { const response = await fetch(`${ollamaServer.baseUrl}/api/show`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: modelName }), signal: AbortSignal.timeout(2000), }); if (!response.ok) return null; const data = await response.json(); return { name: modelName, size: data.details?.parameter_size, modified: data.modified_at, context_length: data.details?.context_length || 4096, supports_tools: true, }; } catch { return null; } } async pullModel(modelName, onProgress) { const ollamaServer = this.servers.find(s => s.type === 'ollama'); if (!ollamaServer) { console.error(chalk.red('Ollama server not found')); return false; } try { const response = await fetch(`${ollamaServer.baseUrl}/api/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: modelName, stream: true }), }); if (!response.ok || !response.body) { return false; } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let done = false; while (!done) { const { value, done: isDone } = await reader.read(); done = isDone; if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (!line.trim()) continue; try { const data = JSON.parse(line); if (data.total && data.completed && onProgress) { const progress = (data.completed / data.total) * 100; onProgress(progress); } if (data.status === 'success') { await this.discoverModels(); return true; } } catch (e) { } } } return true; } catch (error) { console.error(chalk.red('Failed to pull model:'), error); return false; } } getServerStatus() { return this.servers.map(server => ({ name: server.type === 'ollama' ? 'Ollama' : server.baseUrl.includes('1234') ? 'LM Studio' : server.baseUrl.includes('8080') ? 'llama.cpp' : 'Local Server', url: server.baseUrl, available: server.available, models: server.models.length, })); } } export const localModelsService = new LocalModelsService(); //# sourceMappingURL=local-models.js.map