@entro314labs/ai-changelog-generator
Version:
AI-powered changelog generator with MCP server support - works with most providers, online and local models
152 lines (130 loc) • 4.56 kB
JavaScript
import { Ollama } from 'ollama';
import { BaseProvider } from '../core/base-provider.js';
import { ProviderError } from '../../../shared/utils/utils.js';
import { applyMixins } from '../utils/base-provider-helpers.js';
import { buildClientOptions } from '../utils/provider-utils.js';
class OllamaProvider extends BaseProvider {
constructor(config) {
super(config);
this.client = null;
if (this.isAvailable()) {
this.initializeClient();
}
}
initializeClient() {
const clientOptions = buildClientOptions(this.getProviderConfig(), {
host: 'http://localhost:11434'
});
this.client = new Ollama({
host: clientOptions.host
});
}
getName() {
return 'ollama';
}
isAvailable() {
return !!this.config.OLLAMA_HOST;
}
async generateCompletion(messages, options = {}) {
if (!this.isAvailable()) {
return this.handleProviderError(
new Error('Ollama provider is not configured'),
'generate_completion'
);
}
try {
// Test connection first time if not already done
if (!this._connectionTested) {
try {
await this.client.list();
this._connectionTested = true;
} catch (connectionError) {
return this.handleProviderError(
new Error(`Ollama server unreachable: ${connectionError.message}. Please run 'ollama serve' first.`),
'generate_completion'
);
}
}
const modelConfig = this.getProviderModelConfig();
const modelName = options.model || modelConfig.standardModel;
const params = {
model: modelName,
messages: messages,
stream: !!options.stream,
options: {
temperature: options.temperature || 0.7,
top_p: options.top_p || 0.9,
num_predict: options.max_tokens || 1024,
stop: options.stop || [],
},
};
if (options.tools && this.getCapabilities(modelName).tool_use) {
params.tools = options.tools;
}
if (options.response_format?.type === 'json_object' && this.getCapabilities(modelName).json_mode) {
params.format = 'json';
}
if (params.stream) {
const stream = await this.client.chat(params);
return { stream, model: modelName };
}
const response = await this.client.chat(params);
return {
content: response.message.content,
model: response.model,
tokens: response.eval_count,
finish_reason: response.done ? 'stop' : 'incomplete',
tool_calls: response.message.tool_calls,
};
} catch (error) {
return this.handleProviderError(error, 'generate_completion', { model: options.model });
}
}
async generateEmbedding(text, options = {}) {
if (!this.isAvailable()) {
throw new ProviderError('Ollama provider is not configured', 'ollama', 'isAvailable');
}
const modelName = options.model || this.config.OLLAMA_EMBEDDING_MODEL || this.config.AI_MODEL_EMBEDDING || 'nomic-embed-text';
const response = await this.client.embeddings({
model: modelName,
prompt: text,
options: {
temperature: options.temperature || 0.0
}
});
return {
embedding: response.embedding,
model: modelName,
tokens: response.token_count || 0
};
}
// Ollama-specific helper methods
async getAvailableModels() {
if (!this.isAvailable()) return [];
try {
const response = await this.client.list();
return response.models.map(m => m.name);
} catch (error) {
// Only log connection errors in development mode or when explicitly used
if (!this._connectionErrorLogged && (process.env.NODE_ENV === 'development' || process.env.DEBUG)) {
console.warn(`⚠️ Ollama connection failed: ${error.message}`);
console.warn(`💡 Make sure Ollama is running: ollama serve`);
this._connectionErrorLogged = true;
}
return [];
}
}
async pullModel(modelName) {
if (!this.isAvailable()) {
throw new ProviderError('Ollama provider is not configured', 'ollama', 'isAvailable');
}
try {
const pullStream = await this.client.pull({ model: modelName, stream: true });
return { stream: pullStream, model: modelName };
} catch (error) {
throw new ProviderError(`Failed to pull model ${modelName}: ${error.message}`, 'ollama', 'pullModel', error, { modelName });
}
}
}
// Apply mixins to add standard provider functionality
export default applyMixins(OllamaProvider, 'ollama');