@entro314labs/ai-changelog-generator
Version:
AI-powered changelog generator with MCP server support - works with most providers, online and local models
997 lines (996 loc) • 33.4 kB
JavaScript
/**
* Model configuration and capabilities for different AI providers
* Consolidates model-specific logic that was duplicated across providers
*/
// Cache for warnings to prevent spam (with size limit to prevent memory leaks)
const warningCache = new Set();
const MAX_WARNING_CACHE_SIZE = 100;
function addWarningToCache(key) {
if (warningCache.size >= MAX_WARNING_CACHE_SIZE) {
// Clear oldest entries by recreating the set
const entries = Array.from(warningCache);
warningCache.clear();
// Keep last 80% of entries
const keepCount = Math.floor(MAX_WARNING_CACHE_SIZE * 0.8);
entries.slice(-keepCount).forEach((entry) => {
warningCache.add(entry);
});
}
warningCache.add(key);
}
/**
* Standard model configurations for each provider
* Updated January 2026 - Latest models and SDK conventions
*/
export const MODEL_CONFIGS = {
openai: {
// GPT-5.6 family: Sol (flagship reasoning/coding), Terra (balanced),
// Luna (high-volume, cost-sensitive).
complexModel: 'gpt-5.6-sol',
standardModel: 'gpt-5.6-terra',
mediumModel: 'gpt-5.6-luna',
smallModel: 'gpt-5.4-nano',
reasoningModel: 'gpt-5.6-sol',
codingModel: 'gpt-5.3-codex',
fallbacks: ['gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.5', 'gpt-5.4'],
contextWindow: 1050000,
maxOutputTokens: 128000,
},
anthropic: {
// Claude 5 family. Model IDs are complete as-is — never append a date
// suffix; dated variants such as `claude-sonnet-4-5-20250929` are not valid
// current identifiers.
complexModel: 'claude-opus-5',
standardModel: 'claude-sonnet-5',
mediumModel: 'claude-sonnet-4-6',
smallModel: 'claude-haiku-4-5',
reasoningModel: 'claude-opus-5',
fallbacks: ['claude-sonnet-5', 'claude-opus-5', 'claude-sonnet-4-6', 'claude-haiku-4-5'],
contextWindow: 1000000, // 1M on the Claude 5 family
supportsEffortParameter: true, // low | medium | high | xhigh | max
supportsAdaptiveThinking: true, // thinking: { type: 'adaptive' }
},
google: {
// Gemini 3.x series.
complexModel: 'gemini-3.1-pro-preview',
standardModel: 'gemini-3.6-flash',
mediumModel: 'gemini-3.5-flash',
smallModel: 'gemini-3.1-flash-lite',
reasoningModel: 'gemini-3.1-pro-preview',
fallbacks: ['gemini-3.6-flash', 'gemini-3.5-flash', 'gemini-3-flash', 'gemini-2.5-pro'],
contextWindow: 1000000,
supportsThinkingLevel: true,
supportsThoughtSignatures: true,
},
vertex: {
// Gemini 3.x on Vertex AI
complexModel: 'gemini-3.1-pro-preview',
standardModel: 'gemini-3.6-flash',
mediumModel: 'gemini-3.5-flash',
smallModel: 'gemini-3.1-flash-lite',
reasoningModel: 'gemini-3.1-pro-preview',
fallbacks: ['gemini-3.6-flash', 'gemini-3.5-flash', 'gemini-3-flash', 'gemini-2.5-pro'],
// Vertex AI configuration
requiresAuth: true,
location: 'us-central1',
contextWindow: 1000000,
supportsThinkingLevel: true,
supportsThoughtSignatures: true,
},
azure: {
// Azure AI Foundry - v1 API (no api-version required). Deployment names are
// chosen per resource, so these are the model names to deploy, not fixed ids.
complexModel: 'gpt-5.6-sol',
standardModel: 'gpt-5.6-terra',
mediumModel: 'gpt-5.6-luna',
smallModel: 'gpt-5.4-nano',
reasoningModel: 'gpt-5.6-sol',
codingModel: 'gpt-5.3-codex',
fallbacks: ['gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.5', 'gpt-5.4'],
// Hub-specific configuration
isHub: true,
detectDeployments: true,
supportedProviders: ['openai', 'microsoft', 'meta', 'anthropic'],
defaultProvider: 'openai',
// v1 API (August 2025+) - no api-version required
useV1Api: true,
// Azure-specific models (available via deployments)
hubModels: {
openai: [
'gpt-5.6-sol',
'gpt-5.6-terra',
'gpt-5.6-luna',
'gpt-5.5',
'gpt-5.4',
'gpt-5.4-mini',
'gpt-5.4-nano',
'gpt-5.3-codex',
'gpt-image-2',
],
microsoft: ['phi-4'],
meta: ['llama-3.3', 'llama-4'],
anthropic: ['claude-opus-5', 'claude-sonnet-5', 'claude-opus-4-8', 'claude-haiku-4-5'],
openWeight: ['gpt-oss-120b', 'gpt-oss-20b'], // OpenAI's open-weight models
},
},
bedrock: {
// Amazon Bedrock. Claude on Bedrock takes an `anthropic.` prefix on the
// first-party model id.
complexModel: 'anthropic.claude-opus-5',
standardModel: 'anthropic.claude-sonnet-5',
mediumModel: 'anthropic.claude-sonnet-4-6',
smallModel: 'anthropic.claude-haiku-4-5',
reasoningModel: 'anthropic.claude-opus-5',
fallbacks: [
'anthropic.claude-sonnet-5',
'anthropic.claude-opus-5',
'anthropic.claude-sonnet-4-6',
'meta.llama4-scout-v1:0',
],
// Hub-specific configuration
isHub: true,
detectModels: true,
supportedProviders: ['anthropic', 'meta', 'amazon', 'ai21', 'cohere', 'stability'],
defaultProvider: 'anthropic',
region: 'us-east-1',
// Bedrock model mappings by provider
hubModels: {
anthropic: [
'anthropic.claude-opus-5',
'anthropic.claude-sonnet-5',
'anthropic.claude-opus-4-8',
'anthropic.claude-sonnet-4-6',
'anthropic.claude-haiku-4-5',
],
meta: [
'meta.llama4-scout-v1:0',
'meta.llama4-maverick-v1:0',
'meta.llama3-3-70b-instruct-v1:0',
'meta.llama3-2-90b-instruct-v1:0',
'meta.llama3-1-405b-instruct-v1:0',
'meta.llama3-1-70b-instruct-v1:0',
],
amazon: ['amazon.nova-2-lite-v1:0', 'amazon.nova-pro-v1:0', 'amazon.nova-lite-v1:0'],
ai21: ['ai21.jamba-1-5-large-v1:0', 'ai21.jamba-1-5-mini-v1:0'],
cohere: ['cohere.command-r-plus-v1:0', 'cohere.command-r-v1:0'],
},
// Extended context for Claude 4.5 (1M tokens in preview)
extendedContext: 1000000,
},
huggingface: {
// Open-weight models served through HF Inference. These are Hub repo ids,
// which are namespaced differently from the gateway's `<provider>/<model>`.
complexModel: 'Qwen/Qwen3-235B-A22B-Instruct-2507',
standardModel: 'Qwen/Qwen3-32B',
mediumModel: 'meta-llama/Llama-4-Scout-17B-16E-Instruct',
smallModel: 'Qwen/Qwen3-8B',
reasoningModel: 'deepseek-ai/DeepSeek-R1',
codingModel: 'Qwen/Qwen3-Coder-30B-A3B-Instruct',
fallbacks: [
'Qwen/Qwen3-32B',
'meta-llama/Llama-4-Scout-17B-16E-Instruct',
'deepseek-ai/DeepSeek-R1',
'mistralai/Mistral-Small-3.2-24B-Instruct-2506',
],
},
ollama: {
// Local Ollama tags. These are pull tags, not API model ids.
complexModel: 'qwen3:235b',
standardModel: 'llama4:scout',
mediumModel: 'qwen3:32b',
smallModel: 'qwen3:8b',
reasoningModel: 'deepseek-r1:32b',
codingModel: 'qwen3-coder:30b',
fallbacks: ['llama4:scout', 'qwen3', 'deepseek-r1', 'llama3.3'],
// Thinking mode support
supportsThinkingMode: true,
},
lmstudio: {
complexModel: 'qwen3-235b-a22b',
standardModel: 'qwen3-32b',
mediumModel: 'llama-4-scout-17b-16e-instruct',
smallModel: 'qwen3-8b',
fallbacks: ['local-model'],
},
// Vercel AI Gateway addresses every model as `<provider>/<model>`, so one
// credential spans vendors. Snapshots are pinned rather than floating: a
// changelog is a reproducible artifact, and an upstream alias rotation would
// otherwise change generated output with no code change to point at.
'vercel-gateway': {
complexModel: 'deepseek/deepseek-v4-pro',
standardModel: 'deepseek/deepseek-v4-flash-0731',
mediumModel: 'deepseek/deepseek-v4-flash-0731',
smallModel: 'deepseek/deepseek-v4-flash-0731',
reasoningModel: 'deepseek/deepseek-v4-pro',
codingModel: 'deepseek/deepseek-v4-flash-0731',
fallbacks: [
'deepseek/deepseek-v4-flash-0731',
'deepseek/deepseek-v4-pro',
'anthropic/claude-sonnet-5',
'openai/gpt-5.6-luna',
],
},
};
/**
* Get model configuration for a provider with config overrides
* @param {string} providerName - Name of the provider
* @param {Object} config - Configuration object with potential overrides
* @returns {Object} Model configuration with overrides applied
*/
export function getProviderModelConfig(providerName, config = {}, availableModels = []) {
const baseConfig = MODEL_CONFIGS[providerName] || MODEL_CONFIGS.openai;
const modelConfig = {
complexModel: config.AI_MODEL_COMPLEX ||
config[`${providerName.toUpperCase()}_MODEL_COMPLEX`] ||
baseConfig.complexModel,
standardModel: config.AI_MODEL || config[`${providerName.toUpperCase()}_MODEL`] || baseConfig.standardModel,
mediumModel: config.AI_MODEL_SIMPLE ||
config[`${providerName.toUpperCase()}_MODEL_SIMPLE`] ||
baseConfig.mediumModel,
smallModel: config.AI_MODEL_NANO ||
config[`${providerName.toUpperCase()}_MODEL_NANO`] ||
baseConfig.smallModel,
fallbacks: baseConfig.fallbacks,
};
// For hub providers, validate models against available deployments
if (baseConfig.isHub) {
if (availableModels.length > 0) {
// Use actual deployed models
const validateModel = (model) => {
if (availableModels.includes(model)) {
return model;
}
// Try fallbacks in order
for (const fallback of baseConfig.fallbacks) {
if (availableModels.includes(fallback)) {
return fallback;
}
}
// Try common deployments for Azure
if (providerName === 'azure' && baseConfig.commonDeployments) {
for (const common of baseConfig.commonDeployments) {
if (availableModels.includes(common)) {
return common;
}
}
}
return availableModels[0] || model; // Use first available or original
};
modelConfig.complexModel = validateModel(modelConfig.complexModel);
modelConfig.standardModel = validateModel(modelConfig.standardModel);
modelConfig.mediumModel = validateModel(modelConfig.mediumModel);
modelConfig.smallModel = validateModel(modelConfig.smallModel);
// Update fallbacks to only include available models
modelConfig.fallbacks = baseConfig.fallbacks.filter((model) => availableModels.includes(model));
modelConfig.availableModels = availableModels;
}
else {
// No deployment info available - use safer defaults for Azure
if (providerName === 'azure') {
const warningKey = 'azure-no-deployment-info';
if (!warningCache.has(warningKey)) {
console.log('ℹ️ Using default Azure deployment names (deployment detection runs async)');
addWarningToCache(warningKey);
}
// Azure deployment names are chosen per resource; fall back to the
// configured defaults rather than a hardcoded legacy deployment name.
modelConfig.complexModel = baseConfig.complexModel;
modelConfig.standardModel = baseConfig.standardModel;
modelConfig.mediumModel = baseConfig.mediumModel;
modelConfig.smallModel = baseConfig.smallModel;
modelConfig.availableModels = baseConfig.commonDeployments || [];
}
}
// Add hub-specific info
modelConfig.isHub = true;
modelConfig.hubInfo = {
supportedProviders: baseConfig.supportedProviders,
defaultProvider: baseConfig.defaultProvider,
hubModels: baseConfig.hubModels,
commonDeployments: baseConfig.commonDeployments,
};
}
return modelConfig;
}
/**
* Model capabilities database
* Defines what each model family supports
* Updated January 2026
*/
export const MODEL_CAPABILITIES = {
// Matching is substring-based (`model.includes(pattern)`) and first match
// wins, so newer, more specific families must be listed BEFORE older ones.
// OpenAI - GPT-5.6 family (Sol / Terra / Luna)
'gpt-5.6': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
coding_optimized: true,
context_window: 1050000,
max_output: 128000,
flagship: true,
},
'gpt-5.5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
context_window: 400000,
max_output: 128000,
},
'gpt-5.4': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
context_window: 400000,
max_output: 128000,
},
'gpt-5.3-codex': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
coding_optimized: true,
context_window: 400000,
},
// Anthropic - Claude 5 family
'claude-fable-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
adaptive_thinking: true,
effort_parameter: true, // low | medium | high | xhigh | max
coding_optimized: true,
parallel_tool_use: true,
most_capable: true,
context_window: 1000000,
max_output: 128000,
},
'claude-opus-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
adaptive_thinking: true,
effort_parameter: true,
coding_optimized: true,
parallel_tool_use: true,
computer_use: true,
context_window: 1000000,
max_output: 128000,
},
'claude-sonnet-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
adaptive_thinking: true,
effort_parameter: true,
coding_optimized: true,
agentic_coding: true,
context_window: 1000000,
max_output: 128000,
},
'claude-opus-4-8': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
adaptive_thinking: true,
effort_parameter: true,
coding_optimized: true,
context_window: 1000000,
},
'claude-sonnet-4-6': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
adaptive_thinking: true,
effort_parameter: true,
context_window: 1000000,
},
// DeepSeek - default family behind the Vercel AI Gateway
'deepseek-v4': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
streaming: true,
temperature_control: true,
coding_optimized: true,
context_window: 1000000,
},
'gpt-5.2': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
coding_optimized: true,
context_window: 400000,
max_output: 128000,
flagship: true,
},
'gpt-5.2-codex': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
coding_optimized: true,
agentic_coding: true,
context_compaction: true,
cybersecurity: true,
},
'gpt-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
context_window: 272000,
},
'gpt-4o': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
previous_generation: true,
},
'gpt-4.1': {
prompt_caching: true,
tool_use: true,
json_mode: true,
vision: true,
large_context: true,
coding_optimized: true,
cost_reduction: 0.75, // 75% cost reduction with caching
},
o4: {
reasoning: true,
advanced_reasoning: true,
tool_use: true,
large_context: true,
next_generation: true,
},
'o4-mini': {
reasoning: true,
advanced_reasoning: true,
tool_use: true,
large_context: true,
cost_effective: true,
},
o3: {
reasoning: true,
advanced_reasoning: true,
tool_use: true,
large_context: true,
},
o1: {
reasoning: true,
tool_use: true,
large_context: true,
advanced_reasoning: true,
previous_generation: true,
},
// Anthropic Models - Claude 4.5 series (November 2025)
'claude-opus-4-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
hybrid_reasoning: true,
extended_thinking: true,
coding_optimized: true,
parallel_tool_use: true,
effort_parameter: true, // low, medium, high
tool_search: true, // advanced-tool-use-2025-11-20
most_capable: true,
context_window: 200000,
extended_context: 1000000, // beta
},
'claude-sonnet-4-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
hybrid_reasoning: true,
extended_thinking: true,
coding_optimized: true,
agentic_coding: true,
computer_use: true,
context_window: 200000,
extended_context: 1000000, // beta
},
'claude-haiku-4-5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
hybrid_reasoning: true,
extended_thinking: true,
fast_processing: true,
cost_effective: true,
context_window: 200000,
},
'claude-sonnet-4': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
balanced_performance: true,
coding_optimized: true,
},
'claude-opus-4': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
hybrid_reasoning: true,
extended_thinking: true,
coding_optimized: true,
parallel_tool_use: true,
most_capable: true,
},
'claude-3.7': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
previous_generation: true,
deprecated: true, // Migrate to Claude 4.x series
},
'claude-3.5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
previous_generation: true,
deprecated: true, // Migrate to Claude 4.5 series
},
'claude-3': {
vision: true,
tool_use: true, // Only Opus and Sonnet
json_mode: true, // Only Opus and Sonnet
deprecated: true, // FULLY DEPRECATED - Migrate to Claude 4.5 series
},
// Google Models - Gemini 3 series (November-December 2025)
'gemini-3.6': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_level: true,
context_window: 1000000,
},
'gemini-3.5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_level: true,
context_window: 1000000,
},
'gemini-3.1': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_level: true,
context_window: 1000000,
},
'gemini-3-pro': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_level: true, // thinking_level parameter
thought_signatures: true, // encrypted reasoning chain
agentic_workflows: true,
autonomous_coding: true,
context_window: 1000000,
most_capable: true,
},
'gemini-3-flash': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_level: true,
thought_signatures: true,
fast_processing: true,
pro_level_intelligence: true,
context_window: 1000000,
},
'gemini-2.5-pro': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_mode: true,
context_window: 2097152,
previous_generation: true,
},
'gemini-2.5': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
thinking_mode: true,
previous_generation: true,
},
'gemini-2.0': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
fast_processing: true,
previous_generation: true,
},
'gemini-1.5': {
vision: true,
tool_use: true,
json_mode: true,
large_context: true,
multimodal: true,
deprecated: true,
},
// Open-source/Local Models - Llama 4, Qwen 3, DeepSeek
'llama-4': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
open_source: true,
community_license: true,
},
'llama-3.3': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
open_source: true,
},
'llama-3.2': {
tool_use: true,
json_mode: true,
reasoning: true,
open_source: true,
multimodal: true, // 90B has vision
lightweight: true,
},
'llama-3.1': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
open_source: true,
previous_generation: true,
},
qwen3: {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true, // qwen3-vl
thinking_mode: true,
open_source: true,
context_window: 32768,
extended_context: 131072, // with YaRN
},
'qwen2.5-coder': {
tool_use: true,
json_mode: true,
reasoning: true,
coding_optimized: true,
open_source: true,
},
'deepseek-r1': {
reasoning: true,
advanced_reasoning: true,
thinking_mode: true,
json_mode: true,
open_source: true,
cost_effective: true,
},
// Amazon Bedrock Models
'anthropic.claude': {
vision: true,
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
bedrock_hosted: true,
},
'meta.llama4': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
multimodal: true,
open_source: true,
bedrock_hosted: true,
},
'meta.llama': {
tool_use: true,
json_mode: true,
reasoning: true,
large_context: true,
open_source: true,
bedrock_hosted: true,
},
'amazon.titan': {
tool_use: false,
json_mode: true,
reasoning: false,
large_context: true,
bedrock_hosted: true,
aws_native: true,
},
// Local Models (Ollama/LM Studio)
'local-model': {
tool_use: false,
json_mode: false,
reasoning: false,
large_context: false,
offline: true,
privacy_focused: true,
},
};
/**
* Get capabilities for a specific model
* @param {string} modelName - Full model name
* @returns {Object} Model capabilities
*/
export function getModelCapabilities(modelName) {
if (!modelName) {
return {};
}
// Find the closest match in our capabilities database
for (const [pattern, capabilities] of Object.entries(MODEL_CAPABILITIES)) {
if (modelName.includes(pattern)) {
const baseCapabilities = {
vision: false,
tool_use: false,
json_mode: false,
reasoning: false,
large_context: false,
streaming: true,
temperature_control: true,
max_tokens_control: true,
};
return { ...baseCapabilities, ...capabilities };
}
}
// Return basic capabilities for unknown models
return {
vision: false,
tool_use: false,
json_mode: false,
reasoning: false,
large_context: false,
streaming: true,
temperature_control: true,
max_tokens_control: true,
};
}
/**
* Get suggested alternative models for a provider
* @param {string} providerName - Provider name
* @param {string} unavailableModel - Model that's not available
* @returns {Array<string>} Suggested alternative models
*/
export function getSuggestedModels(providerName, unavailableModel) {
const config = MODEL_CONFIGS[providerName];
if (!config) {
return [];
}
// Return fallbacks, but exclude the unavailable model
return config.fallbacks.filter((model) => model !== unavailableModel);
}
/**
* Check if a model supports a specific capability
* @param {string} modelName - Model name to check
* @param {string} capability - Capability to check for
* @returns {boolean} Whether the model supports the capability
*/
export function modelSupports(modelName, capability) {
const capabilities = getModelCapabilities(modelName);
return !!capabilities[capability];
}
/**
* Get the best model for a specific use case
* @param {string} providerName - Provider name
* @param {Array<string>} requiredCapabilities - Required capabilities
* @param {Object} config - Configuration with model overrides
* @returns {string} Best model name for the use case
*/
export function getBestModelForCapabilities(providerName, requiredCapabilities = [], config = {}) {
const modelConfig = getProviderModelConfig(providerName, config);
const modelsToCheck = [
modelConfig.complexModel,
modelConfig.standardModel,
modelConfig.mediumModel,
modelConfig.smallModel,
];
// Find the first model that supports all required capabilities
for (const model of modelsToCheck) {
const capabilities = getModelCapabilities(model);
const supportsAll = requiredCapabilities.every((capability) => capabilities[capability]);
if (supportsAll) {
return model;
}
}
// If no model supports all capabilities, return the most capable one
return modelConfig.complexModel;
}
/**
* Get all hub providers and their status
* @returns {Array<Object>} Hub provider information
*/
export function getAllHubProviders() {
return Object.entries(MODEL_CONFIGS)
.filter(([_name, config]) => config.isHub)
.map(([name, config]) => ({
name,
defaultProvider: config.defaultProvider,
supportedProviders: config.supportedProviders || [],
modelCount: config.hubModels ? Object.values(config.hubModels).flat().length : 0,
region: config.region,
location: config.location,
detectDeployments: config.detectDeployments,
detectModels: config.detectModels,
}));
}
/**
* Analyze commit complexity and recommend model tier
* @param {Object} commitInfo - Commit analysis information
* @param {string} providerName - Provider name
* @returns {Object} Model recommendation with reasoning
*/
export function analyzeCommitComplexity(commitInfo, providerName) {
const { files = [], additions = 0, deletions = 0, complex = false } = commitInfo;
const totalChanges = additions + deletions;
const fileCount = files.length;
let complexity = 'simple';
const reasoning = [];
// Analyze complexity factors
if (complex) {
complexity = 'complex';
reasoning.push('Commit marked as complex');
}
else if (fileCount > 20) {
complexity = 'complex';
reasoning.push(`High file count: ${fileCount} files`);
}
else if (totalChanges > 1000) {
complexity = 'complex';
reasoning.push(`Large change set: ${totalChanges} lines`);
}
else if (fileCount > 10 || totalChanges > 500) {
complexity = 'medium';
reasoning.push(`Moderate changes: ${fileCount} files, ${totalChanges} lines`);
}
else if (totalChanges > 100) {
complexity = 'standard';
reasoning.push(`Standard changes: ${totalChanges} lines`);
}
else {
complexity = 'simple';
reasoning.push(`Simple changes: ${totalChanges} lines in ${fileCount} files`);
}
const modelConfig = MODEL_CONFIGS[providerName];
const modelTiers = {
simple: modelConfig?.smallModel,
standard: modelConfig?.standardModel,
medium: modelConfig?.mediumModel,
complex: modelConfig?.complexModel,
};
return {
complexity,
recommendedModel: modelTiers[complexity] || modelConfig?.standardModel,
reasoning: reasoning.join(', '),
metrics: {
fileCount,
totalChanges,
additions,
deletions,
isComplex: complex,
},
};
}
/**
* Resolve the model tier to use for a given analysis mode.
*
* Maps the high-level analysis mode onto the optimal-model tiers exposed by
* {@link getProviderModelConfig}/`ConfigurationManager.getOptimalModelConfig()`:
* - "standard" => the default tier
* - "detailed" => escalate to the complex tier (falling back to medium, then default)
* - "enterprise" => escalate to the complex tier (falling back to default)
*
* Unknown modes fall back to the default tier so callers always receive a usable model.
*
* @param {string} analysisMode - Analysis mode ("standard" | "detailed" | "enterprise")
* @param {Record<string, any>} optimalModels - Tier->model map (e.g. { default, medium, complex })
* @returns {string} The model name selected for the analysis mode
*/
export function getModelForAnalysisMode(analysisMode, optimalModels = {}) {
switch (analysisMode) {
case 'detailed':
return optimalModels.complex ?? optimalModels.medium ?? optimalModels.default;
case 'enterprise':
return optimalModels.complex ?? optimalModels.default;
default:
return optimalModels.default;
}
}
/**
* Provider-specific model name normalization
* @param {string} providerName - Provider name
* @param {string} modelName - Raw model name
* @returns {string} Normalized model name
*/
export function normalizeModelName(providerName, modelName) {
if (!modelName) {
return null;
}
switch (providerName) {
case 'azure':
// Azure uses deployment names, so return as-is
return modelName;
case 'ollama':
// Ollama models often have version tags
return modelName.includes(':') ? modelName : `${modelName}:latest`;
case 'anthropic':
// Anthropic models need full version strings
if (modelName.includes('claude') && !modelName.includes('-')) {
return `${modelName}-20250514`; // Add default date if missing
}
return modelName;
default:
return modelName;
}
}