UNPKG

@entro314labs/ai-changelog-generator

Version:

AI-powered changelog generator with MCP server support - works with most providers, online and local models

200 lines (199 loc) 8.41 kB
import Anthropic from '@anthropic-ai/sdk'; import { BaseProvider } from '../core/base-provider.js'; import { applyMixins, ProviderResponseHandler } from '../utils/base-provider-helpers.js'; export class AnthropicProvider extends BaseProvider { constructor(config) { super(config); this.anthropic = null; if (this.isAvailable()) { this.initializeClient(); } } initializeClient() { const clientOptions = this.buildClientOptions({ timeout: 60000, maxRetries: 2, defaultHeaders: { 'X-Client-Name': 'ai-changelog-generator', 'X-Client-Version': '1.0.0', }, }); this.anthropic = new Anthropic({ apiKey: clientOptions.ANTHROPIC_API_KEY, baseURL: clientOptions.baseURL, timeout: clientOptions.timeout, maxRetries: clientOptions.maxRetries, defaultHeaders: clientOptions.defaultHeaders, }); } getName() { return 'anthropic'; } isAvailable() { return !!this.config.ANTHROPIC_API_KEY; } getRequiredEnvVars() { return ['ANTHROPIC_API_KEY']; } async generateCompletion(messages, options = {}) { return ProviderResponseHandler.executeWithErrorHandling(this, 'generate_completion', async () => { const modelConfig = this.getProviderModelConfig(); // Anthropic API uses a slightly different message format const systemMessage = messages.find((m) => m.role === 'system'); const userMessages = messages.filter((m) => m.role !== 'system'); const params = { model: options.model || modelConfig.standardModel || this.getDefaultModel(), system: systemMessage ? systemMessage.content : undefined, messages: userMessages, max_tokens: options.max_tokens || 4096, temperature: options.temperature || 0.3, }; // Claude 4.5 "effort" parameter (Opus 4.5 only) - controls reasoning depth // Valid values: 'low', 'medium', 'high' if (options.effort && this.isOpus45Model(params.model)) { params.effort = options.effort; } // Add tool calling if provided if (options.tools) { params.tools = options.tools; params.tool_choice = options.tool_choice || 'auto'; } // Tool search beta (advanced-tool-use-2025-11-20) for Claude 4.5 models // Enables working with hundreds/thousands of tools if (options.tool_search && this.is45Model(params.model)) { params.betas = params.betas || []; params.betas.push('advanced-tool-use-2025-11-20'); } // Extended context (1M tokens beta) for Claude 4.5 models if (options.extended_context && this.is45Model(params.model)) { params.betas = params.betas || []; params.betas.push('extended-context-2025-09-29'); } // Add streaming if requested if (options.stream) { params.stream = true; const stream = params.betas?.length || params.effort ? await this.anthropic.beta.messages.create(params) : await this.anthropic.messages.create(params); return { stream, model: params.model }; } const response = params.betas?.length || params.effort ? await this.anthropic.beta.messages.create(params) : await this.anthropic.messages.create(params); // Check if there are tool calls in the response const toolCalls = response.content.some((c) => c.type === 'tool_use') ? response.content .filter((c) => c.type === 'tool_use') .map((c) => ({ id: c.id, name: c.name, input: c.input })) : null; // Get the text content const textContent = response.content .filter((c) => c.type === 'text') .map((c) => c.text) .join('\n'); return { text: textContent, content: textContent, model: response.model, usage: response.usage, tokens: response.usage.input_tokens + response.usage.output_tokens, tool_calls: toolCalls, stop_reason: response.stop_reason, stop_sequence: response.stop_sequence, }; }, { model: options.model }); } // Check if model is a Claude 4.5 model (supports new features) is45Model(modelName) { return modelName && (modelName.includes('4-5') || modelName.includes('4.5')); } // Check if model is Opus 4.5 (supports effort parameter) isOpus45Model(modelName) { return modelName && modelName.includes('opus-4-5'); } async getAvailableModels() { // Anthropic doesn't provide a models endpoint, return known models // Claude 4.5 series (November 2025) - hybrid reasoning models return [ { name: 'claude-opus-4-5-20251124', id: 'claude-opus-4-5-20251124', description: 'Claude Opus 4.5 - Most intelligent model with effort parameter (Nov 2025)', features: [ 'hybrid_reasoning', 'extended_thinking', 'effort_parameter', 'tool_search', '200k_context', '1M_context_beta', ], }, { name: 'claude-sonnet-4-5-20250929', id: 'claude-sonnet-4-5-20250929', description: 'Claude Sonnet 4.5 - Best for coding, agents, and computer use (Sep 2025)', features: [ 'hybrid_reasoning', 'extended_thinking', 'agentic_coding', 'computer_use', '200k_context', '1M_context_beta', ], }, { name: 'claude-haiku-4-5-20251001', id: 'claude-haiku-4-5-20251001', description: 'Claude Haiku 4.5 - Fast hybrid reasoning model (Oct 2025)', features: [ 'hybrid_reasoning', 'extended_thinking', 'fast', 'cost_effective', '200k_context', ], }, { name: 'claude-sonnet-4-20250514', id: 'claude-sonnet-4-20250514', description: 'Claude Sonnet 4 - Balanced performance (May 2025)', features: ['coding_optimized', 'balanced', '200k_context'], }, { name: 'claude-opus-4-20250617', id: 'claude-opus-4-20250617', description: 'Claude Opus 4 - Previous generation flagship (June 2025)', features: ['extended_thinking', 'parallel_tools', '200k_context'], }, { name: 'claude-haiku-4-5-20251001', id: 'claude-haiku-4-5-20251001', description: 'Claude Haiku 4.5 - Fast and cost-efficient (Oct 2025)', features: ['fast', 'cost_effective', '200k_context'], }, ]; } // testConnection() and validateModelAvailability() now provided by mixins async testModel(modelName) { try { const response = await this.anthropic.messages.create({ model: modelName, messages: [{ role: 'user', content: 'Test' }], max_tokens: 1, }); return { success: true, model: response.model, sdk_version: '0.71.2', }; } catch (error) { return { success: false, error: error.message, }; } } } // Apply mixins to add standard provider functionality export default applyMixins ? applyMixins(AnthropicProvider, 'anthropic') : AnthropicProvider;