UNPKG

claude-switcher

Version:

Cross-platform CLI tool for switching between different Claude AI model configurations. Supports automatic backup, rollback, and multi-platform configuration management for Claude API integrations.

567 lines (566 loc) 23.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelService = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); function getPackageVersion() { try { const packageJsonPath = path_1.default.join(__dirname, '..', '..', '..', 'package.json'); const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8')); return packageJson.version; } catch (error) { return '1.0.4'; } } class ModelService { static async getModels(providerId, config, forceRefresh = false) { try { if (!forceRefresh && this.isCacheValid(providerId)) { const cached = this.cache[providerId]; return { models: cached.models, provider: providerId, cached: true, lastFetched: cached.lastFetched, }; } const models = await this.fetchModelsFromProvider(providerId, config); this.updateCache(providerId, models); return { models, provider: providerId, cached: false, lastFetched: new Date(), }; } catch (error) { if (this.cache[providerId]) { const cached = this.cache[providerId]; return { models: cached.models, provider: providerId, cached: true, lastFetched: cached.lastFetched, error: error instanceof Error ? error.message : 'Failed to fetch models', }; } const defaultModels = this.getDefaultModels(providerId); return { models: defaultModels, provider: providerId, cached: false, lastFetched: new Date(), error: error instanceof Error ? error.message : 'Failed to fetch models', }; } } static async fetchModelsFromProvider(providerId, config) { const provider = this.getProviderConfig(providerId); if (!provider.supportsModelListing) { return this.getDefaultModels(providerId); } const baseUrl = config?.env?.ANTHROPIC_BASE_URL || provider.defaultBaseUrl; const apiKey = config?.env?.ANTHROPIC_AUTH_TOKEN; if (!apiKey) { throw new Error('API key required for fetching models'); } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.REQUEST_TIMEOUT); try { const response = await fetch(provider.modelsEndpoint(baseUrl), { method: 'GET', headers: { ...provider.getHeaders(apiKey), 'User-Agent': `claude-switcher/${getPackageVersion()}`, }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); return provider.parseModelsResponse(data, providerId); } finally { clearTimeout(timeoutId); } } static getProviderConfig(providerId) { const configs = { openai: { supportsModelListing: true, defaultBaseUrl: 'https://api.openai.com/v1', modelsEndpoint: (baseUrl) => `${baseUrl}/models`, getHeaders: (apiKey) => ({ 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }), parseModelsResponse: (data, providerId) => { if (!data.data || !Array.isArray(data.data)) { throw new Error('Invalid models response format'); } return data.data .filter((model) => model.id && !model.id.includes('whisper') && !model.id.includes('tts')) .map((model) => ({ id: model.id, name: model.id, displayName: this.formatModelName(model.id), description: this.getModelDescription(model.id, providerId), type: this.inferModelType(model.id), contextLength: this.getContextLength(model.id, providerId), isRecommended: this.isRecommendedModel(model.id, providerId), isDeprecated: model.deprecated || false, capabilities: this.getModelCapabilities(model.id, providerId), provider: providerId, lastUpdated: new Date(), })); }, }, anthropic: { supportsModelListing: false, defaultBaseUrl: 'https://api.anthropic.com', modelsEndpoint: () => '', getHeaders: () => ({}), parseModelsResponse: () => [], }, deepseek: { supportsModelListing: false, defaultBaseUrl: 'https://api.deepseek.com', modelsEndpoint: (baseUrl) => `${baseUrl}/models`, getHeaders: (apiKey) => ({ 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }), parseModelsResponse: (data, providerId) => { if (data.data && Array.isArray(data.data)) { return data.data.map((model) => ({ id: model.id, name: model.id, displayName: this.formatModelName(model.id), description: this.getModelDescription(model.id, providerId), type: this.inferModelType(model.id), contextLength: this.getContextLength(model.id, providerId), isRecommended: this.isRecommendedModel(model.id, providerId), provider: providerId, lastUpdated: new Date(), })); } throw new Error('Unsupported models response format'); }, }, qwen: { supportsModelListing: false, defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', modelsEndpoint: () => '', getHeaders: () => ({}), parseModelsResponse: () => [], }, ollama: { supportsModelListing: true, defaultBaseUrl: 'http://localhost:11434', modelsEndpoint: (baseUrl) => `${baseUrl}/api/tags`, getHeaders: () => ({ 'Content-Type': 'application/json', }), parseModelsResponse: (data, providerId) => { if (!data.models || !Array.isArray(data.models)) { throw new Error('Invalid Ollama models response format'); } return data.models.map((model) => ({ id: model.name, name: model.name, displayName: this.formatModelName(model.name), description: `Local model: ${model.name}`, type: this.inferModelType(model.name), contextLength: this.getContextLength(model.name, providerId), isRecommended: this.isRecommendedModel(model.name, providerId), capabilities: ['chat', 'completion'], provider: providerId, lastUpdated: new Date(model.modified_at || Date.now()), })); }, }, }; return configs[providerId] || configs.openai; } static getDefaultModels(providerId) { const defaultModels = { openai: [ { id: 'gpt-4o', name: 'gpt-4o', displayName: 'GPT-4o', description: 'Most advanced GPT-4 model with vision capabilities', type: 'chat', contextLength: 128000, isRecommended: true, capabilities: ['chat', 'vision', 'function-calling'], provider: 'openai', lastUpdated: new Date(), }, { id: 'gpt-4o-mini', name: 'gpt-4o-mini', displayName: 'GPT-4o Mini', description: 'Faster and more affordable GPT-4o model', type: 'chat', contextLength: 128000, isRecommended: true, capabilities: ['chat', 'vision', 'function-calling'], provider: 'openai', lastUpdated: new Date(), }, { id: 'gpt-4-turbo', name: 'gpt-4-turbo', displayName: 'GPT-4 Turbo', description: 'High-performance GPT-4 model', type: 'chat', contextLength: 128000, capabilities: ['chat', 'vision', 'function-calling'], provider: 'openai', lastUpdated: new Date(), }, { id: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo', description: 'Fast and efficient model for most tasks', type: 'chat', contextLength: 16385, capabilities: ['chat', 'function-calling'], provider: 'openai', lastUpdated: new Date(), }, ], anthropic: [ { id: 'claude-3-5-sonnet-20241022', name: 'claude-3-5-sonnet-20241022', displayName: 'Claude 3.5 Sonnet', description: 'Most intelligent Claude model with enhanced capabilities', type: 'chat', contextLength: 200000, isRecommended: true, capabilities: ['chat', 'reasoning', 'code', 'vision'], provider: 'anthropic', lastUpdated: new Date(), }, { id: 'claude-3-opus-20240229', name: 'claude-3-opus-20240229', displayName: 'Claude 3 Opus', description: 'Most powerful Claude model for complex tasks', type: 'chat', contextLength: 200000, capabilities: ['chat', 'reasoning', 'code', 'vision'], provider: 'anthropic', lastUpdated: new Date(), }, { id: 'claude-3-sonnet-20240229', name: 'claude-3-sonnet-20240229', displayName: 'Claude 3 Sonnet', description: 'Balanced performance and speed', type: 'chat', contextLength: 200000, capabilities: ['chat', 'reasoning', 'code', 'vision'], provider: 'anthropic', lastUpdated: new Date(), }, { id: 'claude-3-haiku-20240307', name: 'claude-3-haiku-20240307', displayName: 'Claude 3 Haiku', description: 'Fastest Claude model for quick responses', type: 'chat', contextLength: 200000, isRecommended: true, capabilities: ['chat', 'reasoning', 'code', 'vision'], provider: 'anthropic', lastUpdated: new Date(), }, ], deepseek: [ { id: 'deepseek-chat', name: 'deepseek-chat', displayName: 'DeepSeek Chat', description: 'General-purpose conversational model', type: 'chat', contextLength: 32768, isRecommended: true, capabilities: ['chat', 'reasoning'], provider: 'deepseek', lastUpdated: new Date(), }, { id: 'deepseek-coder', name: 'deepseek-coder', displayName: 'DeepSeek Coder', description: 'Specialized model for coding tasks', type: 'code', contextLength: 16384, isRecommended: true, capabilities: ['code', 'chat'], provider: 'deepseek', lastUpdated: new Date(), }, ], qwen: [ { id: 'qwen-turbo', name: 'qwen-turbo', displayName: 'Qwen Turbo', description: 'Fast and efficient Qwen model', type: 'chat', contextLength: 8192, isRecommended: true, capabilities: ['chat', 'reasoning'], provider: 'qwen', lastUpdated: new Date(), }, { id: 'qwen-plus', name: 'qwen-plus', displayName: 'Qwen Plus', description: 'Enhanced Qwen model with better capabilities', type: 'chat', contextLength: 32768, capabilities: ['chat', 'reasoning'], provider: 'qwen', lastUpdated: new Date(), }, { id: 'qwen-max', name: 'qwen-max', displayName: 'Qwen Max', description: 'Most powerful Qwen model', type: 'chat', contextLength: 8192, capabilities: ['chat', 'reasoning'], provider: 'qwen', lastUpdated: new Date(), }, ], glm: [ { id: 'glm-4', name: 'glm-4', displayName: 'GLM-4', description: 'Latest GLM model with enhanced capabilities', type: 'chat', contextLength: 128000, isRecommended: true, capabilities: ['chat', 'reasoning', 'code'], provider: 'glm', lastUpdated: new Date(), }, { id: 'glm-3-turbo', name: 'glm-3-turbo', displayName: 'GLM-3 Turbo', description: 'Fast GLM model for quick responses', type: 'chat', contextLength: 128000, capabilities: ['chat', 'reasoning'], provider: 'glm', lastUpdated: new Date(), }, ], kimi: [ { id: 'moonshot-v1-8k', name: 'moonshot-v1-8k', displayName: 'Moonshot v1 8K', description: 'Kimi model with 8K context length', type: 'chat', contextLength: 8192, isRecommended: true, capabilities: ['chat', 'reasoning'], provider: 'kimi', lastUpdated: new Date(), }, { id: 'moonshot-v1-32k', name: 'moonshot-v1-32k', displayName: 'Moonshot v1 32K', description: 'Kimi model with 32K context length', type: 'chat', contextLength: 32768, capabilities: ['chat', 'reasoning'], provider: 'kimi', lastUpdated: new Date(), }, { id: 'moonshot-v1-128k', name: 'moonshot-v1-128k', displayName: 'Moonshot v1 128K', description: 'Kimi model with 128K context length', type: 'chat', contextLength: 131072, capabilities: ['chat', 'reasoning'], provider: 'kimi', lastUpdated: new Date(), }, ], ollama: [ { id: 'llama3.1', name: 'llama3.1', displayName: 'Llama 3.1', description: 'Meta\'s Llama 3.1 model running locally', type: 'chat', contextLength: 128000, isRecommended: true, capabilities: ['chat', 'reasoning', 'code'], provider: 'ollama', lastUpdated: new Date(), }, { id: 'llama3.1:8b', name: 'llama3.1:8b', displayName: 'Llama 3.1 8B', description: '8B parameter version of Llama 3.1', type: 'chat', contextLength: 128000, capabilities: ['chat', 'reasoning'], provider: 'ollama', lastUpdated: new Date(), }, { id: 'codellama', name: 'codellama', displayName: 'Code Llama', description: 'Specialized model for code generation', type: 'code', contextLength: 16384, capabilities: ['code', 'chat'], provider: 'ollama', lastUpdated: new Date(), }, ], }; return defaultModels[providerId] || []; } static isCacheValid(providerId) { const cached = this.cache[providerId]; return cached && cached.expiresAt > new Date(); } static updateCache(providerId, models) { const now = new Date(); this.cache[providerId] = { models, lastFetched: now, expiresAt: new Date(now.getTime() + this.CACHE_DURATION), }; } static clearCache(providerId) { if (providerId) { delete this.cache[providerId]; } else { this.cache = {}; } } static getCacheStatus() { const status = {}; for (const [providerId, cached] of Object.entries(this.cache)) { status[providerId] = { lastFetched: cached.lastFetched, expiresAt: cached.expiresAt, modelCount: cached.models.length, }; } return status; } static formatModelName(modelId) { return modelId .replace(/[-_]/g, ' ') .replace(/\b\w/g, l => l.toUpperCase()) .replace(/\s+/g, ' ') .trim(); } static getModelDescription(modelId, providerId) { const descriptions = { openai: { 'gpt-4o': 'Most advanced GPT-4 model with vision capabilities', 'gpt-4o-mini': 'Faster and more affordable GPT-4o model', 'gpt-4-turbo': 'High-performance GPT-4 model', 'gpt-3.5-turbo': 'Fast and efficient model for most tasks', }, deepseek: { 'deepseek-chat': 'General-purpose conversational model', 'deepseek-coder': 'Specialized model for coding tasks', }, }; return descriptions[providerId]?.[modelId] || `${providerId} model: ${modelId}`; } static inferModelType(modelId) { const modelLower = modelId.toLowerCase(); if (modelLower.includes('code') || modelLower.includes('coder')) return 'code'; if (modelLower.includes('embed')) return 'embedding'; if (modelLower.includes('image') || modelLower.includes('vision')) return 'image'; if (modelLower.includes('reason')) return 'reasoning'; return 'chat'; } static getContextLength(modelId, providerId) { const contextLengths = { openai: { 'gpt-4o': 128000, 'gpt-4o-mini': 128000, 'gpt-4-turbo': 128000, 'gpt-3.5-turbo': 16385, }, anthropic: { 'claude-3-5-sonnet-20241022': 200000, 'claude-3-opus-20240229': 200000, 'claude-3-sonnet-20240229': 200000, 'claude-3-haiku-20240307': 200000, }, }; return contextLengths[providerId]?.[modelId] || 4096; } static isRecommendedModel(modelId, providerId) { const recommended = { openai: ['gpt-4o', 'gpt-4o-mini'], anthropic: ['claude-3-5-sonnet-20241022', 'claude-3-haiku-20240307'], deepseek: ['deepseek-chat', 'deepseek-coder'], qwen: ['qwen-turbo'], glm: ['glm-4'], kimi: ['moonshot-v1-8k'], ollama: ['llama3.1'], }; return recommended[providerId]?.includes(modelId) || false; } static getModelCapabilities(modelId, providerId) { const capabilities = { openai: { 'gpt-4o': ['chat', 'vision', 'function-calling', 'reasoning'], 'gpt-4o-mini': ['chat', 'vision', 'function-calling'], 'gpt-4-turbo': ['chat', 'vision', 'function-calling'], 'gpt-3.5-turbo': ['chat', 'function-calling'], }, anthropic: { 'claude-3-5-sonnet-20241022': ['chat', 'reasoning', 'code', 'vision'], 'claude-3-opus-20240229': ['chat', 'reasoning', 'code', 'vision'], 'claude-3-sonnet-20240229': ['chat', 'reasoning', 'code', 'vision'], 'claude-3-haiku-20240307': ['chat', 'reasoning', 'code', 'vision'], }, }; return capabilities[providerId]?.[modelId] || ['chat']; } } exports.ModelService = ModelService; ModelService.CACHE_DURATION = 1000 * 60 * 60; ModelService.REQUEST_TIMEOUT = 10000; ModelService.cache = {};