@entro314labs/ai-changelog-generator 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/ai-changelog-mcp.sh +0 -0
  2. package/ai-changelog.sh +0 -0
  3. package/bin/ai-changelog-dxt.js +0 -0
  4. package/package.json +72 -80
  5. package/src/ai-changelog-generator.js +5 -4
  6. package/src/application/orchestrators/changelog.orchestrator.js +12 -202
  7. package/src/cli.js +4 -5
  8. package/src/domains/ai/ai-analysis.service.js +2 -0
  9. package/src/domains/analysis/analysis.engine.js +714 -37
  10. package/src/domains/changelog/changelog.service.js +615 -30
  11. package/src/domains/changelog/workspace-changelog.service.js +418 -627
  12. package/src/domains/git/commit-tagger.js +552 -0
  13. package/src/domains/git/git-manager.js +357 -0
  14. package/src/domains/git/git.service.js +865 -16
  15. package/src/infrastructure/cli/cli.controller.js +14 -9
  16. package/src/infrastructure/config/configuration.manager.js +24 -2
  17. package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
  18. package/src/infrastructure/mcp/mcp-server.service.js +35 -11
  19. package/src/infrastructure/providers/core/base-provider.js +1 -1
  20. package/src/infrastructure/providers/implementations/anthropic.js +16 -173
  21. package/src/infrastructure/providers/implementations/azure.js +16 -63
  22. package/src/infrastructure/providers/implementations/dummy.js +13 -16
  23. package/src/infrastructure/providers/implementations/mock.js +13 -26
  24. package/src/infrastructure/providers/implementations/ollama.js +12 -4
  25. package/src/infrastructure/providers/implementations/openai.js +13 -165
  26. package/src/infrastructure/providers/provider-management.service.js +126 -412
  27. package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
  28. package/src/shared/utils/cli-ui.js +1 -1
  29. package/src/shared/utils/diff-processor.js +21 -19
  30. package/src/shared/utils/error-classes.js +33 -0
  31. package/src/shared/utils/utils.js +65 -60
  32. package/src/domains/git/git-repository.analyzer.js +0 -678
@@ -83,6 +83,11 @@ export class CLIController {
83
83
  }
84
84
 
85
85
  async ensureConfig() {
86
+ // Skip config setup in test environments to prevent hanging
87
+ if (process.env.NODE_ENV === 'test' || process.env.CI) {
88
+ return
89
+ }
90
+
86
91
  const configPath = path.join(process.cwd(), '.env.local')
87
92
  try {
88
93
  await access(configPath)
@@ -345,17 +350,17 @@ class ValidateCommand extends BaseCommand {
345
350
  const validation = await appService.validateConfiguration()
346
351
 
347
352
  if (validation.valid) {
348
- console.log(colors.successMessage('✅ Configuration is valid'))
353
+ EnhancedConsole.success('✅ Configuration is valid')
349
354
  } else {
350
- console.log(colors.errorMessage('❌ Configuration has issues:'))
355
+ EnhancedConsole.error('❌ Configuration has issues:')
351
356
  validation.issues.forEach((issue) => {
352
- console.log(` - ${issue}`)
357
+ EnhancedConsole.log(` - ${issue}`)
353
358
  })
354
359
 
355
360
  if (validation.recommendations.length > 0) {
356
- console.log(colors.infoMessage('\n💡 Recommendations:'))
361
+ EnhancedConsole.info('\n💡 Recommendations:')
357
362
  validation.recommendations.forEach((rec) => {
358
- console.log(` - ${rec}`)
363
+ EnhancedConsole.log(` - ${rec}`)
359
364
  })
360
365
  }
361
366
  }
@@ -554,7 +559,7 @@ class ProvidersCommand extends BaseCommand {
554
559
  colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider')
555
560
  )
556
561
  } catch (error) {
557
- console.error(colors.errorMessage(`Error listing providers: ${error.message}`))
562
+ EnhancedConsole.error(`Error listing providers: ${error.message}`)
558
563
  }
559
564
  }
560
565
 
@@ -577,7 +582,7 @@ class ProvidersCommand extends BaseCommand {
577
582
  )
578
583
  }
579
584
  } catch (error) {
580
- console.error(colors.errorMessage(`Error switching provider: ${error.message}`))
585
+ EnhancedConsole.error(`Error switching provider: ${error.message}`)
581
586
  }
582
587
  }
583
588
 
@@ -637,7 +642,7 @@ class ProvidersCommand extends BaseCommand {
637
642
  console.log(colors.infoMessage('\nAfter adding the configuration, run:'))
638
643
  console.log(colors.highlight(`ai-changelog providers validate ${providerName}`))
639
644
  } catch (error) {
640
- console.error(colors.errorMessage(`Error configuring provider: ${error.message}`))
645
+ EnhancedConsole.error(`Error configuring provider: ${error.message}`)
641
646
  }
642
647
  }
643
648
 
@@ -676,7 +681,7 @@ class ProvidersCommand extends BaseCommand {
676
681
  }
677
682
  }
678
683
  } catch (error) {
679
- console.error(colors.errorMessage(`Error validating provider: ${error.message}`))
684
+ EnhancedConsole.error(`Error validating provider: ${error.message}`)
680
685
  }
681
686
  }
682
687
  }
@@ -151,7 +151,7 @@ export class ConfigurationManager {
151
151
  releaseTagGlobPattern: 'v[0-9]*.[0-9]*.[0-9]*',
152
152
  },
153
153
  changelog: {
154
- commitTypes: ['feat', 'fix', 'perf', 'refactor', 'docs'],
154
+ commitTypes: ['feat', 'fix', 'perf', 'refactor', 'docs', 'merge'],
155
155
  includeInvalidCommits: true,
156
156
  commitIgnoreRegexPattern: '^WIP ',
157
157
  headlines: {
@@ -529,7 +529,21 @@ export class ConfigurationManager {
529
529
  }
530
530
 
531
531
  getCommitTypes() {
532
- return this.changelogConfig?.convention?.commitTypes || ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']
532
+ return (
533
+ this.changelogConfig?.convention?.commitTypes || [
534
+ 'feat',
535
+ 'fix',
536
+ 'docs',
537
+ 'style',
538
+ 'refactor',
539
+ 'perf',
540
+ 'test',
541
+ 'build',
542
+ 'ci',
543
+ 'chore',
544
+ 'revert',
545
+ ]
546
+ )
533
547
  }
534
548
 
535
549
  getChangelogCommitTypes() {
@@ -565,4 +579,12 @@ export class ConfigurationManager {
565
579
  const pattern = this.changelogConfig.changelog.commitIgnoreRegexPattern
566
580
  return pattern ? new RegExp(pattern) : /^WIP /
567
581
  }
582
+
583
+ /**
584
+ * Get the complete configuration object
585
+ * @returns {Object} Configuration object
586
+ */
587
+ getConfig() {
588
+ return this.config || {}
589
+ }
568
590
  }
@@ -198,6 +198,10 @@ Provide 3 suggestions.`
198
198
  return summary
199
199
  }
200
200
 
201
+ async generateChangelogForCommitHashes(commitHashes) {
202
+ return await this.generateChangelogForCommits(commitHashes)
203
+ }
204
+
201
205
  async selectSpecificCommits() {
202
206
  return await selectSpecificCommits()
203
207
  }
@@ -207,7 +211,10 @@ Provide 3 suggestions.`
207
211
  const spinner = new SimpleSpinner(`Generating changelog for recent ${count} commits...`)
208
212
  spinner.start()
209
213
 
210
- const commits = await this.gitService.getCommitsSince(null)
214
+ const commits =
215
+ (await this.gitService.getCommitsSince?.(null)) ||
216
+ (await this.gitService.getCommitAnalysis?.()) ||
217
+ []
211
218
  const recentCommits = commits.slice(0, count)
212
219
 
213
220
  if (recentCommits.length === 0) {
@@ -18,9 +18,9 @@ import { ChangelogOrchestrator } from '../../application/orchestrators/changelog
18
18
  // Import application services
19
19
  import { ApplicationService } from '../../application/services/application.service.js'
20
20
  import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
21
- import { GitRepositoryAnalyzer } from '../../domains/git/git-repository.analyzer.js'
21
+ import { GitService } from '../../domains/git/git.service.js'
22
22
  import { ConfigurationManager } from '../config/configuration.manager.js'
23
- import { ProviderManagementService } from '../providers/provider-management.service.js'
23
+ import { ProviderManagerService } from '../providers/provider-manager.service.js'
24
24
 
25
25
  const __filename = fileURLToPath(import.meta.url)
26
26
  const __dirname = dirname(__filename)
@@ -64,9 +64,9 @@ class AIChangelogMCPServer {
64
64
  this.configManager = new ConfigurationManager()
65
65
  this.applicationService = new ApplicationService()
66
66
  this.changelogOrchestrator = new ChangelogOrchestrator(this.configManager)
67
- this.gitAnalyzer = new GitRepositoryAnalyzer()
67
+ this.gitAnalyzer = new GitService()
68
68
  this.analysisEngine = new AnalysisEngine()
69
- this.providerService = new ProviderManagementService()
69
+ this.providerService = new ProviderManagerService(this.configManager.getConfig())
70
70
 
71
71
  // Log available configuration
72
72
  const hasProvider = process.env.AI_PROVIDER
@@ -419,15 +419,39 @@ class AIChangelogMCPServer {
419
419
  result += `\n${testResult}`
420
420
  }
421
421
  break
422
- case 'test':
423
- result = await this.providerService.testCurrentProvider()
422
+ case 'test': {
423
+ const activeProvider = this.providerService.getActiveProvider()
424
+ if (!activeProvider) {
425
+ throw new Error('No active provider found')
426
+ }
427
+ result = await this.providerService.testProvider(activeProvider.getName())
424
428
  break
425
- case 'configure':
426
- result = await this.providerService.configureProvider(provider)
429
+ }
430
+ case 'configure': {
431
+ if (!provider) {
432
+ throw new Error('Provider required for configure action')
433
+ }
434
+ const providerData = this.providerService.findProviderByName(provider)
435
+ if (!providerData) {
436
+ throw new Error(`Provider '${provider}' not found`)
437
+ }
438
+ result = {
439
+ name: provider,
440
+ available: providerData.available,
441
+ configuration: providerData.instance.getConfiguration
442
+ ? providerData.instance.getConfiguration()
443
+ : {},
444
+ requiredVars: providerData.instance.getRequiredEnvVars
445
+ ? providerData.instance.getRequiredEnvVars()
446
+ : [],
447
+ }
427
448
  break
428
- case 'validate':
429
- result = await this.providerService.validateModels(provider)
449
+ }
450
+ case 'validate': {
451
+ const validationResults = await this.providerService.validateAll()
452
+ result = validationResults
430
453
  break
454
+ }
431
455
  default:
432
456
  throw new Error(`Unknown action: ${action}`)
433
457
  }
@@ -436,7 +460,7 @@ class AIChangelogMCPServer {
436
460
  content: [
437
461
  {
438
462
  type: 'text',
439
- text: result,
463
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
440
464
  },
441
465
  ],
442
466
  }
@@ -2,7 +2,7 @@
2
2
  * Abstract Base Provider for AI models.
3
3
  * Defines the interface that all provider plugins must implement.
4
4
  */
5
- import { AbstractMethodError } from '../../../shared/utils/utils.js'
5
+ import { AbstractMethodError } from '../../../shared/utils/error-classes.js'
6
6
 
7
7
  export class BaseProvider {
8
8
  constructor(config) {
@@ -2,7 +2,6 @@ import Anthropic from '@anthropic-ai/sdk'
2
2
 
3
3
  import { BaseProvider } from '../core/base-provider.js'
4
4
  import { applyMixins, ProviderResponseHandler } from '../utils/base-provider-helpers.js'
5
- import { buildClientOptions } from '../utils/provider-utils.js'
6
5
 
7
6
  export class AnthropicProvider extends BaseProvider {
8
7
  constructor(config) {
@@ -14,7 +13,7 @@ export class AnthropicProvider extends BaseProvider {
14
13
  }
15
14
 
16
15
  initializeClient() {
17
- const clientOptions = buildClientOptions(this.getProviderConfig(), {
16
+ const clientOptions = this.buildClientOptions({
18
17
  timeout: 60000,
19
18
  maxRetries: 2,
20
19
  defaultHeaders: {
@@ -24,7 +23,7 @@ export class AnthropicProvider extends BaseProvider {
24
23
  })
25
24
 
26
25
  this.anthropic = new Anthropic({
27
- apiKey: clientOptions.apiKey,
26
+ apiKey: clientOptions.ANTHROPIC_API_KEY,
28
27
  baseURL: clientOptions.baseURL,
29
28
  timeout: clientOptions.timeout,
30
29
  maxRetries: clientOptions.maxRetries,
@@ -45,7 +44,8 @@ export class AnthropicProvider extends BaseProvider {
45
44
  }
46
45
 
47
46
  getDefaultModel() {
48
- return 'claude-sonnet-4-20250514'
47
+ const modelConfig = this.getProviderModelConfig()
48
+ return modelConfig.standardModel
49
49
  }
50
50
 
51
51
  async generateCompletion(messages, options = {}) {
@@ -115,127 +115,32 @@ export class AnthropicProvider extends BaseProvider {
115
115
 
116
116
  async getAvailableModels() {
117
117
  // Anthropic doesn't provide a models endpoint, return known models
118
+ // Use direct model names to avoid circular dependency with mixins
118
119
  return [
119
- {
120
- name: 'claude-sonnet-4-20250514',
121
- id: 'claude-sonnet-4-20250514',
122
- description: 'Claude Sonnet 4 - Latest balanced model (2025)',
123
- contextWindow: 200000,
124
- capabilities: {
125
- reasoning: true,
126
- function_calling: true,
127
- json_mode: true,
128
- multimodal: true,
129
- largeContext: true,
130
- toolUse: true,
131
- },
132
- },
133
120
  {
134
121
  name: 'claude-opus-4-20250514',
135
122
  id: 'claude-opus-4-20250514',
136
123
  description: 'Claude Opus 4 - Most capable model for complex tasks (2025)',
137
- contextWindow: 200000,
138
- capabilities: {
139
- reasoning: true,
140
- function_calling: true,
141
- json_mode: true,
142
- multimodal: true,
143
- largeContext: true,
144
- toolUse: true,
145
- advancedReasoning: true,
146
- },
124
+ },
125
+ {
126
+ name: 'claude-sonnet-4-20250514',
127
+ id: 'claude-sonnet-4-20250514',
128
+ description: 'Claude Sonnet 4 - Latest balanced model (2025)',
147
129
  },
148
130
  {
149
131
  name: 'claude-3.7-sonnet-20250219',
150
132
  id: 'claude-3.7-sonnet-20250219',
151
133
  description: 'Claude 3.7 Sonnet - Enhanced reasoning capabilities',
152
- contextWindow: 200000,
153
- capabilities: {
154
- reasoning: true,
155
- function_calling: true,
156
- json_mode: true,
157
- multimodal: true,
158
- largeContext: true,
159
- toolUse: true,
160
- },
161
- },
162
- {
163
- name: 'claude-3-5-sonnet-20241022',
164
- id: 'claude-3-5-sonnet-20241022',
165
- description: 'Claude 3.5 Sonnet - Previous generation',
166
- contextWindow: 200000,
167
- capabilities: {
168
- reasoning: true,
169
- function_calling: true,
170
- json_mode: true,
171
- multimodal: true,
172
- },
173
134
  },
174
135
  {
175
136
  name: 'claude-3-5-haiku-20241022',
176
137
  id: 'claude-3-5-haiku-20241022',
177
138
  description: 'Claude 3.5 Haiku - Fast and efficient',
178
- contextWindow: 200000,
179
- capabilities: {
180
- reasoning: true,
181
- function_calling: true,
182
- json_mode: true,
183
- multimodal: true,
184
- },
185
139
  },
186
140
  ]
187
141
  }
188
142
 
189
- async validateModelAvailability(modelName) {
190
- try {
191
- const models = await this.getAvailableModels()
192
- const model = models.find((m) => m.name === modelName)
193
-
194
- if (model) {
195
- return {
196
- available: true,
197
- model: modelName,
198
- capabilities: model.capabilities,
199
- contextWindow: model.contextWindow,
200
- }
201
- }
202
- const availableModels = models.map((m) => m.name)
203
- return {
204
- available: false,
205
- error: `Model '${modelName}' not available`,
206
- alternatives: availableModels.slice(0, 5),
207
- }
208
- } catch (error) {
209
- return {
210
- available: false,
211
- error: error.message,
212
- alternatives: [
213
- 'claude-sonnet-4-20250514',
214
- 'claude-opus-4-20250514',
215
- 'claude-3.7-sonnet-20250219',
216
- ],
217
- }
218
- }
219
- }
220
-
221
- async testConnection() {
222
- try {
223
- const response = await this.generateCompletion([{ role: 'user', content: 'Hello' }], {
224
- max_tokens: 5,
225
- })
226
-
227
- return {
228
- success: true,
229
- model: response.model,
230
- message: 'Connection successful',
231
- }
232
- } catch (error) {
233
- return {
234
- success: false,
235
- error: error.message,
236
- }
237
- }
238
- }
143
+ // testConnection() and validateModelAvailability() now provided by mixins
239
144
 
240
145
  async testModel(modelName) {
241
146
  try {
@@ -258,73 +163,11 @@ export class AnthropicProvider extends BaseProvider {
258
163
  }
259
164
  }
260
165
 
261
- getCapabilities(_modelName) {
262
- return {
263
- completion: true,
264
- streaming: true,
265
- function_calling: true,
266
- json_mode: true,
267
- reasoning: true,
268
- multimodal: true,
269
- large_context: true,
270
- }
271
- }
272
-
273
- getModelRecommendation(commitDetails) {
274
- const { files = 0, lines = 0, breaking = false, complex = false } = commitDetails
275
-
276
- // Use the most capable model for complex or breaking changes
277
- if (breaking || complex || files > 20 || lines > 1000) {
278
- return {
279
- model: 'claude-3-5-sonnet-20241022',
280
- reason: 'Complex or breaking change requiring advanced reasoning',
281
- }
282
- }
283
-
284
- // Use standard model for medium changes
285
- if (files > 5 || lines > 200) {
286
- return {
287
- model: 'claude-3-sonnet-20240229',
288
- reason: 'Medium-sized change requiring good analysis',
289
- }
290
- }
291
-
292
- // Use efficient model for small changes
293
- return {
294
- model: 'claude-3-haiku-20240307',
295
- reason: 'Small change, optimized for efficiency',
296
- }
297
- }
298
-
299
- async selectOptimalModel(commitInfo) {
300
- const recommendation = this.getModelRecommendation(commitInfo)
301
- const validation = await this.validateModelAvailability(recommendation.model)
302
-
303
- if (validation.available) {
304
- return {
305
- model: recommendation.model,
306
- reason: recommendation.reason,
307
- capabilities: validation.capabilities,
308
- }
309
- }
310
- return {
311
- model: this.getDefaultModel(),
312
- reason: 'Fallback to default model',
313
- capabilities: this.getCapabilities(this.getDefaultModel()),
314
- }
315
- }
316
-
317
- getProviderModelConfig() {
318
- return {
319
- smallModel: 'claude-3-5-haiku-20241022',
320
- mediumModel: 'claude-3.7-sonnet-20250219',
321
- standardModel: 'claude-sonnet-4-20250514',
322
- complexModel: 'claude-opus-4-20250514',
323
- default: 'claude-sonnet-4-20250514',
324
- temperature: 0.3,
325
- maxTokens: 4096,
326
- }
327
- }
166
+ // All common methods now provided by mixins:
167
+ // - getCapabilities() from CapabilitiesMixin
168
+ // - getModelRecommendation() from ModelRecommendationMixin
169
+ // - selectOptimalModel() from ModelRecommendationMixin
170
+ // - getProviderModelConfig() from ConfigurationMixin
328
171
  }
329
172
 
330
173
  // Apply mixins to add standard provider functionality
@@ -3,6 +3,7 @@ import { AzureOpenAI } from 'openai'
3
3
 
4
4
  import { BaseProvider } from '../core/base-provider.js'
5
5
  import { applyMixins, ProviderResponseHandler } from '../utils/base-provider-helpers.js'
6
+ import { getProviderModelConfig } from '../utils/model-config.js'
6
7
 
7
8
  class AzureOpenAIProvider extends BaseProvider {
8
9
  constructor(config) {
@@ -168,59 +169,11 @@ class AzureOpenAIProvider extends BaseProvider {
168
169
 
169
170
  // Azure-specific helper methods
170
171
  getDeploymentName() {
171
- return this.config.AZURE_OPENAI_DEPLOYMENT_NAME || this.getProviderModelConfig().standardModel
172
+ const modelConfig = this.getProviderModelConfig()
173
+ return this.config.AZURE_OPENAI_DEPLOYMENT_NAME || modelConfig.standardModel
172
174
  }
173
175
 
174
- getModelContextWindow(modelName) {
175
- const contextWindows = {
176
- // Latest 2025 models (Azure exclusive)
177
- o4: 500000,
178
- 'o4-mini': 200000,
179
- o3: 300000,
180
- 'o3-mini': 150000,
181
- // Standard 2025 models
182
- 'gpt-4o': 128000,
183
- 'gpt-4o-mini': 128000,
184
- o1: 200000,
185
- 'o1-mini': 128000,
186
- 'gpt-4.1': 200000,
187
- 'gpt-4.1-mini': 200000,
188
- 'gpt-4.1-nano': 200000,
189
- // Legacy models
190
- 'gpt-4': 8192,
191
- 'gpt-4-32k': 32768,
192
- 'gpt-4-turbo': 128000,
193
- 'gpt-35-turbo': 4096,
194
- 'gpt-35-turbo-16k': 16384,
195
- }
196
- return contextWindows[modelName] || 128000
197
- }
198
-
199
- getModelCapabilities(modelName) {
200
- return {
201
- reasoning:
202
- modelName.includes('o1') ||
203
- modelName.includes('o3') ||
204
- modelName.includes('o4') ||
205
- modelName.includes('gpt-4'),
206
- function_calling: !(
207
- modelName.includes('o1') ||
208
- modelName.includes('o3') ||
209
- modelName.includes('o4')
210
- ), // o-series models don't support function calling
211
- json_mode: true,
212
- multimodal: modelName.includes('gpt-4o') || modelName.includes('gpt-4.1'),
213
- largeContext:
214
- modelName.includes('4.1') ||
215
- modelName.includes('o1') ||
216
- modelName.includes('o3') ||
217
- modelName.includes('o4') ||
218
- modelName.includes('4o'),
219
- promptCaching: modelName.includes('4.1'),
220
- advancedReasoning: modelName.includes('o3') || modelName.includes('o4'),
221
- azureExclusive: modelName.includes('o3') || modelName.includes('o4'),
222
- }
223
- }
176
+ // Model capabilities now handled by centralized CapabilitiesMixin
224
177
 
225
178
  // Azure-specific method for testing deployment availability
226
179
  async testDeployment(deploymentName) {
@@ -257,19 +210,19 @@ class AzureOpenAIProvider extends BaseProvider {
257
210
  return this._cachedDeployments
258
211
  }
259
212
 
260
- // Get base config directly to avoid recursion
261
- const baseConfig = {
262
- commonDeployments: ['o4', 'o3', 'gpt-4.1', 'gpt-4o', 'gpt-35-turbo', 'o1'],
263
- fallbacks: ['gpt-4.1', 'gpt-4o', 'o1', 'gpt-35-turbo'],
264
- }
213
+ // Get model config from centralized system (avoiding circular dependency)
214
+ const modelConfig = getProviderModelConfig('azure', this.config)
265
215
 
266
216
  const potentialDeployments = [
267
217
  // User configured deployment
268
218
  this.config.AZURE_OPENAI_DEPLOYMENT_NAME,
269
- // Common deployment names
270
- ...baseConfig.commonDeployments,
219
+ // Models from config
220
+ modelConfig.complexModel,
221
+ modelConfig.standardModel,
222
+ modelConfig.mediumModel,
223
+ modelConfig.smallModel,
271
224
  // Fallback models
272
- ...baseConfig.fallbacks,
225
+ ...modelConfig.fallbacks,
273
226
  ]
274
227
  .filter(Boolean)
275
228
  .filter((v, i, a) => a.indexOf(v) === i) // Remove duplicates
@@ -294,16 +247,16 @@ class AzureOpenAIProvider extends BaseProvider {
294
247
 
295
248
  if (availableDeployments.length === 0) {
296
249
  console.warn('⚠️ No Azure deployments found. Using configured deployment name as fallback.')
297
- // Fallback to configured deployment even if untested, preferring latest models
298
- const fallback = this.config.AZURE_OPENAI_DEPLOYMENT_NAME || 'o4' || 'gpt-4.1'
250
+ // Fallback to configured deployment even if untested
251
+ const fallback = this.config.AZURE_OPENAI_DEPLOYMENT_NAME || modelConfig.standardModel
299
252
  return [fallback]
300
253
  }
301
254
 
302
255
  return availableDeployments
303
256
  } catch (error) {
304
257
  console.warn('⚠️ Failed to detect Azure deployments:', error.message)
305
- // Return common deployments as fallback
306
- return baseConfig.commonDeployments
258
+ // Return fallback models from config
259
+ return modelConfig.fallbacks
307
260
  }
308
261
  }
309
262
 
@@ -3,8 +3,9 @@
3
3
  * Fallback provider when no other providers are available
4
4
  */
5
5
 
6
- import { ProviderError } from '../../../shared/utils/utils.js'
6
+ import { ProviderError } from '../../../shared/utils/error-classes.js'
7
7
  import { BaseProvider } from '../core/base-provider.js'
8
+ import { applyMixins } from '../utils/base-provider-helpers.js'
8
9
 
9
10
  class DummyProvider extends BaseProvider {
10
11
  constructor(config = {}) {
@@ -12,22 +13,22 @@ class DummyProvider extends BaseProvider {
12
13
  this.name = 'dummy'
13
14
  }
14
15
 
15
- /**
16
- * Get provider name
17
- * @returns {string} Provider name
18
- */
19
16
  getName() {
20
17
  return this.name
21
18
  }
22
19
 
23
- /**
24
- * Check if provider is available
25
- * @returns {boolean} Always true for dummy provider
26
- */
27
20
  isAvailable() {
28
21
  return true
29
22
  }
30
23
 
24
+ getRequiredEnvVars() {
25
+ return []
26
+ }
27
+
28
+ getDefaultModel() {
29
+ return 'rule-based'
30
+ }
31
+
31
32
  /**
32
33
  * Generate completion (always fails with informative error)
33
34
  * @param {Array} messages - Messages for completion
@@ -93,20 +94,16 @@ class DummyProvider extends BaseProvider {
93
94
  }
94
95
  }
95
96
 
96
- getAvailableModels() {
97
+ async getAvailableModels() {
97
98
  return [
98
99
  {
99
100
  id: 'rule-based',
100
101
  name: 'Rule-based Fallback',
101
- contextWindow: 0,
102
- maxOutput: 0,
103
- inputCost: 0,
104
- outputCost: 0,
105
- features: [],
106
102
  description: 'No AI model available - configure a provider',
107
103
  },
108
104
  ]
109
105
  }
110
106
  }
111
107
 
112
- export default DummyProvider
108
+ // Apply minimal mixins (error handling only - dummy provider doesn't need full functionality)
109
+ export default applyMixins ? applyMixins(DummyProvider, 'dummy', []) : DummyProvider