-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathAIProviderRegistry.ts
More file actions
120 lines (95 loc) · 2.89 KB
/
AIProviderRegistry.ts
File metadata and controls
120 lines (95 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// src/api/ai/core/AIProviderRegistry.ts
import { AIProvider } from './AIProvider'
import { AICapability } from './types'
import { GeminiProvider } from '../providers/GeminiProvider'
type ProviderHealth = {
failures: number
lastFailureAt?: number
}
const MAX_FAILURES = 3
class AIProviderRegistry {
private providers = new Map<string, AIProvider>()
private health = new Map<string, ProviderHealth>()
register(provider: AIProvider) {
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))
)
}
// --------------------
// Health tracking
// --------------------
private isHealthy(providerName: string): boolean {
const info = this.health.get(providerName)
if (!info) return true
return info.failures < MAX_FAILURES
}
markFailure(providerName: string) {
const info = this.health.get(providerName)
if (!info) return
info.failures += 1
info.lastFailureAt = Date.now()
}
markSuccess(providerName: string) {
const info = this.health.get(providerName)
if (!info) return
info.failures = 0
}
// --------------------
// Safe execution
// --------------------
async generateTextSafe(
providerName: string,
input: { prompt: string; context?: string }
): Promise<string> {
const provider = this.providers.get(providerName)
if (!provider) {
throw new Error(`Provider "${providerName}" not found`)
}
try {
const result = await provider.generateText(input)
this.markSuccess(provider.name)
return result
} catch (err) {
this.markFailure(provider.name)
throw err
}
}
// --------------------
// 🆕 Fallback routing
// --------------------
async generateTextWithFallback(
capabilities: AICapability[],
input: { prompt: string; context?: string }
): Promise<string> {
const candidates = this.findByCapabilities(capabilities)
for (const provider of candidates) {
if (!this.isHealthy(provider.name)) continue
try {
const result = await provider.generateText(input)
this.markSuccess(provider.name)
return result
} catch {
this.markFailure(provider.name)
}
}
throw new Error('No healthy AI provider available')
}
}
export const aiProviderRegistry = new AIProviderRegistry()
// register providers (safe)
aiProviderRegistry.register(new GeminiProvider())