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
JavaScript
;
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 = {};