@entro314labs/ai-changelog-generator
Version:
AI-powered changelog generator with MCP server support - works with most providers, online and local models
220 lines (219 loc) • 7.66 kB
JavaScript
/**
* Mock Provider
* Used for testing without real API credentials
*/
import { ProviderError } from '../../../shared/utils/error-classes.js';
import { BaseProvider } from '../core/base-provider.js';
import { applyMixins } from '../utils/base-provider-helpers.js';
class MockProvider extends BaseProvider {
constructor(config = {}) {
super(config);
this.name = 'mock';
this.mockResponses = config.MOCK_RESPONSES || {};
this.shouldFail = config.MOCK_SHOULD_FAIL === 'true';
this.failureRate = Number.parseFloat(config.MOCK_FAILURE_RATE || '0.1');
this.latency = Number.parseInt(config.MOCK_LATENCY || '500', 10);
this.models = ['mock-basic', 'mock-standard', 'mock-advanced'];
}
getName() {
return this.name;
}
isAvailable() {
return true;
}
getRequiredEnvVars() {
return [];
}
getDefaultModel() {
return 'mock-standard';
}
/**
* Simulate network latency
* @param {number} ms - Milliseconds to wait
* @returns {Promise} - Promise that resolves after delay
*/
async _simulateLatency() {
return new Promise((resolve) => setTimeout(resolve, this.latency));
}
/**
* Determine if operation should fail based on failure rate
* @returns {boolean} - Whether operation should fail
*/
_shouldFailOperation() {
if (this.shouldFail) {
return true;
}
return Math.random() < this.failureRate;
}
/**
* Generate completion with mock data
* @param {Array} messages - Messages for completion
* @param {Object} options - Generation options
* @returns {Promise} - Promise that resolves with mock completion
*/
async generateCompletion(messages, options = {}) {
await this._simulateLatency();
if (this._shouldFailOperation()) {
throw new ProviderError('Mock provider simulated failure', 'mock_error', this.getName(), options.model || 'mock-standard');
}
const model = options.model || 'mock-standard';
const lastMessage = messages.at(-1);
const prompt = typeof lastMessage === 'string' ? lastMessage : lastMessage.content || '';
// Check for predefined responses
if (this.mockResponses[prompt]) {
return {
content: this.mockResponses[prompt],
tokens: this.mockResponses[prompt].length / 4, // Rough estimate
model,
};
}
// Generate mock response based on commit convention if it looks like a commit message
if (prompt.match(/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?:/)) {
return {
content: this._generateMockChangelog(prompt),
tokens: 150,
model,
};
}
// Default mock response
return {
content: `This is a mock response from the ${model} model.`,
tokens: 10,
model,
};
}
/**
* Generate mock changelog based on commit message
* @param {string} commitMessage - Commit message
* @returns {string} - Mock changelog entry
*/
_generateMockChangelog(commitMessage) {
const typeMatch = commitMessage.match(/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?:/);
if (!typeMatch) {
return 'Mock changelog entry for conventional commit';
}
const type = typeMatch[1];
const scope = typeMatch[2] ? typeMatch[2].replace(/[()]/g, '') : '';
const description = commitMessage.split(':')[1]?.trim() || '';
const typeMap = {
feat: 'Feature',
fix: 'Bug Fix',
docs: 'Documentation',
style: 'Style',
refactor: 'Code Refactoring',
perf: 'Performance',
test: 'Tests',
build: 'Build',
ci: 'CI',
chore: 'Chore',
};
const title = `${typeMap[type]}${scope ? ` (${scope})` : ''}`;
return `### ${title}\n\n- ${description}\n`;
}
/**
* Get model recommendation based on mock rules
* @param {Object} commitInfo - Commit information
* @returns {Object} Model recommendation
*/
getModelRecommendation(commitInfo = {}) {
if (!commitInfo.message) {
return {
model: 'mock-standard',
reason: 'Default model selected due to insufficient commit information',
};
}
// Simple logic based on commit complexity
const filesChanged = commitInfo.files?.length || 0;
const linesChanged = (commitInfo.additions || 0) + (commitInfo.deletions || 0);
if (filesChanged > 10 || linesChanged > 500 || commitInfo.breaking) {
return {
model: 'mock-advanced',
reason: 'Complex commit detected, using advanced model',
};
}
if (filesChanged > 3 || linesChanged > 100) {
return {
model: 'mock-standard',
reason: 'Moderate commit detected, using standard model',
};
}
return {
model: 'mock-basic',
reason: 'Simple commit detected, using basic model',
};
}
/**
* Validate model availability
* @param {string} model - Model name
* @returns {Promise} - Promise that resolves with validation result
*/
async validateModelAvailability(model) {
await this._simulateLatency();
if (this._shouldFailOperation()) {
return {
available: false,
error: 'Mock validation failure',
alternatives: this.models,
};
}
const isAvailable = this.models.includes(model);
return {
available: isAvailable,
model: isAvailable ? model : null,
alternatives: isAvailable ? [] : this.models,
};
}
/**
* Test connection to mock provider
* @returns {Promise} - Promise that resolves with connection test result
*/
async testConnection() {
await this._simulateLatency();
if (this._shouldFailOperation()) {
return {
success: false,
error: 'Mock connection failure',
provider: this.getName(),
};
}
return {
success: true,
provider: this.getName(),
model: 'mock-standard',
response: 'Mock connection successful',
};
}
/**
* Get provider capabilities
* @returns {Object} - Capabilities object
*/
getCapabilities() {
return {
streaming: false,
tool_use: true,
vision: false,
json_mode: true,
};
}
async getAvailableModels() {
return [
{
id: 'mock-basic',
name: 'Mock Basic Model',
description: 'Basic mock model for simple testing',
},
{
id: 'mock-standard',
name: 'Mock Standard Model',
description: 'Standard mock model for moderate testing',
},
{
id: 'mock-advanced',
name: 'Mock Advanced Model',
description: 'Advanced mock model for complex testing scenarios',
},
];
}
}
// Apply mixins for enhanced functionality
export default applyMixins ? applyMixins(MockProvider, 'mock') : MockProvider;