Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions LocalMind-Backend/src/api/ai/core/AIModelRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// src/api/ai/core/AIModelRegistry.ts

import { AICapability } from './types'

export type AIModel = {
id: string
provider: string
capabilities: AICapability[]
}

class AIModelRegistry {
private models: AIModel[] = []

// --------------------
// Model management
// --------------------

register(model: AIModel) {
if (this.models.some(m => m.id === model.id && m.provider === model.provider)) {
throw new Error(
`Model "${model.id}" from provider "${model.provider}" is already registered.`
)
}
this.models.push(model)
}

listProviders(): string[] {
return [...new Set(this.models.map(m => m.provider))]
}

listModelsByProvider(provider: string): AIModel[] {
return this.models.filter(m => m.provider === provider)
}

getModel(provider: string, modelId: string): AIModel | undefined {
return this.models.find(
m => m.provider === provider && m.id === modelId
)
}

listAll(): AIModel[] {
return [...this.models]
}
}

export const aiModelRegistry = new AIModelRegistry()

// Register default models
aiModelRegistry.register({
id: 'gemini-pro',
provider: 'gemini',
capabilities: ['cloud'],
})

aiModelRegistry.register({
id: 'gemini-pro-vision',
provider: 'gemini',
capabilities: ['cloud', 'multimodal'],
})
110 changes: 95 additions & 15 deletions LocalMind-Backend/src/api/ai/core/AIProviderRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,114 @@
import { AIProvider } from './AIProvider'
import { AICapability } from './types'

type ProviderHealth = {
failures: number
lastFailureAt?: number
}

const MAX_FAILURES = 3
const COOLDOWN_PERIOD_MS = 300000 // 5 minutes

class AIProviderRegistry {
private providers = new Map<string, AIProvider>()
private health = new Map<string, ProviderHealth>()

// --------------------
// Provider management
// --------------------

register(provider: AIProvider) {
if (this.providers.has(provider.name)) {
throw new Error(
`A provider with the name "${provider.name}" is already registered.`
if (this.providers.has(provider.name)) {
throw new Error(
`A provider with the name "${provider.name}" is already registered.`
)
}

this.providers.set(provider.name, provider)
this.health.set(provider.name, { failures: 0 })
}

get(name: string): AIProvider | undefined {
return this.providers.get(name)
}

list(): AIProvider[] {
return Array.from(this.providers.values())
}

findByCapabilities(capabilities: AICapability[]): AIProvider[] {
return Array.from(this.providers.values()).filter(provider =>
capabilities.every(cap => provider.supports(cap))
)
}
this.providers.set(provider.name, provider)
}

// --------------------
// Health tracking with cooldown recovery
// --------------------

private isHealthy(providerName: string): boolean {
const info = this.health.get(providerName)
if (!info) return true

get(name: string): AIProvider | undefined {
return this.providers.get(name)
if (info.failures < MAX_FAILURES) {
return true
}

// Allow retry after cooldown period
if (info.lastFailureAt && Date.now() - info.lastFailureAt > COOLDOWN_PERIOD_MS) {
this.markSuccess(providerName) // Reset health to allow retry
return true
}

return false
}

findByCapabilities(capabilities: AICapability[]): AIProvider[] {
return Array.from(this.providers.values()).filter(provider =>
capabilities.every(cap => provider.supports(cap))
)
}
private markFailure(providerName: string) {
const info = this.health.get(providerName)
if (!info) return

info.failures += 1
info.lastFailureAt = Date.now()
}

list(): AIProvider[] {
return Array.from(this.providers.values())
private markSuccess(providerName: string) {
const info = this.health.get(providerName)
if (!info) return

info.failures = 0
}

// --------------------
// Provider + Model routing
// --------------------

async generateTextWithModel(
providerName: string,
modelId: string,
input: { prompt: string }
): Promise<string> {
const provider = this.get(providerName)

if (!provider) {
throw new Error(`Provider "${providerName}" not found`)
}

if (!this.isHealthy(provider.name)) {
throw new Error(`Provider "${providerName}" is currently degraded`)
}

try {
const result = await provider.generateText({
...input,
context: modelId,
})
this.markSuccess(provider.name)
return result
} catch (err) {
this.markFailure(provider.name)
throw err
}
}
}

export const aiProviderRegistry = new AIProviderRegistry()
export const aiProviderRegistry = new AIProviderRegistry()