@skillsmith/core 2.1.0 → 2.1.2
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/types.d.ts +2 -0
- package/dist/src/analysis/types.d.ts.map +1 -1
- package/dist/src/analysis/types.js +13 -1
- package/dist/src/analysis/types.js.map +1 -1
- package/dist/src/analytics/AnalyticsRepository.d.ts +4 -0
- package/dist/src/analytics/AnalyticsRepository.d.ts.map +1 -1
- package/dist/src/analytics/AnalyticsRepository.js +26 -44
- package/dist/src/analytics/AnalyticsRepository.js.map +1 -1
- package/dist/src/analytics/schema.d.ts +1 -1
- package/dist/src/analytics/schema.d.ts.map +1 -1
- package/dist/src/analytics/schema.js +68 -0
- package/dist/src/analytics/schema.js.map +1 -1
- package/dist/src/api/client.d.ts +33 -29
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +15 -10
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/billing/BillingService.d.ts +139 -0
- package/dist/src/billing/BillingService.d.ts.map +1 -0
- package/dist/src/billing/BillingService.js +393 -0
- package/dist/src/billing/BillingService.js.map +1 -0
- package/dist/src/billing/GDPRComplianceService.d.ts +176 -0
- package/dist/src/billing/GDPRComplianceService.d.ts.map +1 -0
- package/dist/src/billing/GDPRComplianceService.js +361 -0
- package/dist/src/billing/GDPRComplianceService.js.map +1 -0
- package/dist/src/billing/StripeClient.d.ts +177 -0
- package/dist/src/billing/StripeClient.d.ts.map +1 -0
- package/dist/src/billing/StripeClient.js +462 -0
- package/dist/src/billing/StripeClient.js.map +1 -0
- package/dist/src/billing/StripeReconciliationJob.d.ts +95 -0
- package/dist/src/billing/StripeReconciliationJob.d.ts.map +1 -0
- package/dist/src/billing/StripeReconciliationJob.js +405 -0
- package/dist/src/billing/StripeReconciliationJob.js.map +1 -0
- package/dist/src/billing/StripeWebhookHandler.d.ts +92 -0
- package/dist/src/billing/StripeWebhookHandler.d.ts.map +1 -0
- package/dist/src/billing/StripeWebhookHandler.js +409 -0
- package/dist/src/billing/StripeWebhookHandler.js.map +1 -0
- package/dist/src/billing/index.d.ts +18 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +19 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/types.d.ts +266 -0
- package/dist/src/billing/types.d.ts.map +1 -0
- package/dist/src/billing/types.js +23 -0
- package/dist/src/billing/types.js.map +1 -0
- package/dist/src/embeddings/hnsw-store.d.ts +568 -0
- package/dist/src/embeddings/hnsw-store.d.ts.map +1 -0
- package/dist/src/embeddings/hnsw-store.js +805 -0
- package/dist/src/embeddings/hnsw-store.js.map +1 -0
- package/dist/src/embeddings/index.d.ts +2 -0
- package/dist/src/embeddings/index.d.ts.map +1 -1
- package/dist/src/embeddings/index.js +2 -0
- package/dist/src/embeddings/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/learning/PatternStore.d.ts +457 -0
- package/dist/src/learning/PatternStore.d.ts.map +1 -0
- package/dist/src/learning/PatternStore.js +893 -0
- package/dist/src/learning/PatternStore.js.map +1 -0
- package/dist/src/learning/ReasoningBankIntegration.d.ts +403 -0
- package/dist/src/learning/ReasoningBankIntegration.d.ts.map +1 -0
- package/dist/src/learning/ReasoningBankIntegration.js +627 -0
- package/dist/src/learning/ReasoningBankIntegration.js.map +1 -0
- package/dist/src/learning/index.d.ts +15 -0
- package/dist/src/learning/index.d.ts.map +1 -0
- package/dist/src/learning/index.js +15 -0
- package/dist/src/learning/index.js.map +1 -0
- package/dist/src/routing/SONARouter.d.ts +154 -0
- package/dist/src/routing/SONARouter.d.ts.map +1 -0
- package/dist/src/routing/SONARouter.js +679 -0
- package/dist/src/routing/SONARouter.js.map +1 -0
- package/dist/src/routing/index.d.ts +9 -0
- package/dist/src/routing/index.d.ts.map +1 -0
- package/dist/src/routing/index.js +10 -0
- package/dist/src/routing/index.js.map +1 -0
- package/dist/src/routing/types.d.ts +331 -0
- package/dist/src/routing/types.d.ts.map +1 -0
- package/dist/src/routing/types.js +203 -0
- package/dist/src/routing/types.js.map +1 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/security/SkillSandbox.d.ts +156 -0
- package/dist/src/security/SkillSandbox.d.ts.map +1 -0
- package/dist/src/security/SkillSandbox.js +303 -0
- package/dist/src/security/SkillSandbox.js.map +1 -0
- package/dist/src/security/index.d.ts +3 -1
- package/dist/src/security/index.d.ts.map +1 -1
- package/dist/src/security/index.js +5 -1
- package/dist/src/security/index.js.map +1 -1
- package/dist/src/security/rate-limiter/presets.d.ts +12 -0
- package/dist/src/security/rate-limiter/presets.d.ts.map +1 -1
- package/dist/src/security/rate-limiter/presets.js +12 -0
- package/dist/src/security/rate-limiter/presets.js.map +1 -1
- package/dist/src/security/sanitization.d.ts +85 -0
- package/dist/src/security/sanitization.d.ts.map +1 -1
- package/dist/src/security/sanitization.js +133 -0
- package/dist/src/security/sanitization.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.d.ts +23 -0
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +232 -28
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +13 -0
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.js +51 -0
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +13 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +1 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/session/SessionManager.d.ts +7 -0
- package/dist/src/session/SessionManager.d.ts.map +1 -1
- package/dist/src/session/SessionManager.js +117 -10
- package/dist/src/session/SessionManager.js.map +1 -1
- package/dist/src/sync/SyncEngine.d.ts.map +1 -1
- package/dist/src/sync/SyncEngine.js +52 -32
- package/dist/src/sync/SyncEngine.js.map +1 -1
- package/dist/src/testing/MultiLLMProvider.d.ts +374 -0
- package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -0
- package/dist/src/testing/MultiLLMProvider.js +720 -0
- package/dist/src/testing/MultiLLMProvider.js.map +1 -0
- package/dist/src/testing/index.d.ts +8 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +9 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/types.d.ts +3 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tests/SecurityScanner.test.js +337 -1
- package/dist/tests/SecurityScanner.test.js.map +1 -1
- package/dist/tests/billing/BillingService.test.d.ts +7 -0
- package/dist/tests/billing/BillingService.test.d.ts.map +1 -0
- package/dist/tests/billing/BillingService.test.js +168 -0
- package/dist/tests/billing/BillingService.test.js.map +1 -0
- package/dist/tests/billing/GDPRCompliance.test.d.ts +7 -0
- package/dist/tests/billing/GDPRCompliance.test.d.ts.map +1 -0
- package/dist/tests/billing/GDPRCompliance.test.js +195 -0
- package/dist/tests/billing/GDPRCompliance.test.js.map +1 -0
- package/dist/tests/billing/StripeReconciliation.test.d.ts +7 -0
- package/dist/tests/billing/StripeReconciliation.test.d.ts.map +1 -0
- package/dist/tests/billing/StripeReconciliation.test.js +266 -0
- package/dist/tests/billing/StripeReconciliation.test.js.map +1 -0
- package/dist/tests/billing/stripe-validators.test.d.ts +7 -0
- package/dist/tests/billing/stripe-validators.test.d.ts.map +1 -0
- package/dist/tests/billing/stripe-validators.test.js +107 -0
- package/dist/tests/billing/stripe-validators.test.js.map +1 -0
- package/dist/tests/embeddings/hnsw-store.test.d.ts +7 -0
- package/dist/tests/embeddings/hnsw-store.test.d.ts.map +1 -0
- package/dist/tests/embeddings/hnsw-store.test.js +295 -0
- package/dist/tests/embeddings/hnsw-store.test.js.map +1 -0
- package/dist/tests/integration/neural/e2e-learning.test.d.ts +17 -0
- package/dist/tests/integration/neural/e2e-learning.test.d.ts.map +1 -0
- package/dist/tests/integration/neural/e2e-learning.test.js +238 -0
- package/dist/tests/integration/neural/e2e-learning.test.js.map +1 -0
- package/dist/tests/integration/neural/helpers.d.ts +132 -0
- package/dist/tests/integration/neural/helpers.d.ts.map +1 -0
- package/dist/tests/integration/neural/helpers.js +287 -0
- package/dist/tests/integration/neural/helpers.js.map +1 -0
- package/dist/tests/integration/neural/personalization.test.d.ts +21 -0
- package/dist/tests/integration/neural/personalization.test.d.ts.map +1 -0
- package/dist/tests/integration/neural/personalization.test.js +304 -0
- package/dist/tests/integration/neural/personalization.test.js.map +1 -0
- package/dist/tests/integration/neural/preference-learner.test.d.ts +23 -0
- package/dist/tests/integration/neural/preference-learner.test.d.ts.map +1 -0
- package/dist/tests/integration/neural/preference-learner.test.js +289 -0
- package/dist/tests/integration/neural/preference-learner.test.js.map +1 -0
- package/dist/tests/integration/neural/privacy.test.d.ts +19 -0
- package/dist/tests/integration/neural/privacy.test.d.ts.map +1 -0
- package/dist/tests/integration/neural/privacy.test.js +249 -0
- package/dist/tests/integration/neural/privacy.test.js.map +1 -0
- package/dist/tests/integration/neural/setup.d.ts +175 -0
- package/dist/tests/integration/neural/setup.d.ts.map +1 -0
- package/dist/tests/integration/neural/setup.js +487 -0
- package/dist/tests/integration/neural/setup.js.map +1 -0
- package/dist/tests/integration/neural/signal-collection.test.d.ts +21 -0
- package/dist/tests/integration/neural/signal-collection.test.d.ts.map +1 -0
- package/dist/tests/integration/neural/signal-collection.test.js +232 -0
- package/dist/tests/integration/neural/signal-collection.test.js.map +1 -0
- package/dist/tests/learning/PatternStore.test.d.ts +8 -0
- package/dist/tests/learning/PatternStore.test.d.ts.map +1 -0
- package/dist/tests/learning/PatternStore.test.js +589 -0
- package/dist/tests/learning/PatternStore.test.js.map +1 -0
- package/dist/tests/learning/ReasoningBankIntegration.test.d.ts +8 -0
- package/dist/tests/learning/ReasoningBankIntegration.test.d.ts.map +1 -0
- package/dist/tests/learning/ReasoningBankIntegration.test.js +269 -0
- package/dist/tests/learning/ReasoningBankIntegration.test.js.map +1 -0
- package/dist/tests/routing/SONARouter.test.d.ts +8 -0
- package/dist/tests/routing/SONARouter.test.d.ts.map +1 -0
- package/dist/tests/routing/SONARouter.test.js +400 -0
- package/dist/tests/routing/SONARouter.test.js.map +1 -0
- package/dist/tests/security/ContinuousSecurity.test.js +10 -12
- package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
- package/dist/tests/security/SkillSandbox.test.d.ts +8 -0
- package/dist/tests/security/SkillSandbox.test.d.ts.map +1 -0
- package/dist/tests/security/SkillSandbox.test.js +321 -0
- package/dist/tests/security/SkillSandbox.test.js.map +1 -0
- package/dist/tests/sync/SyncEngine.test.js +4 -2
- package/dist/tests/sync/SyncEngine.test.js.map +1 -1
- package/dist/tests/testing/MultiLLMProvider.test.d.ts +14 -0
- package/dist/tests/testing/MultiLLMProvider.test.d.ts.map +1 -0
- package/dist/tests/testing/MultiLLMProvider.test.js +438 -0
- package/dist/tests/testing/MultiLLMProvider.test.js.map +1 -0
- package/package.json +16 -3
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Multi-LLM Provider Chain for Skill Compatibility Testing
|
|
3
|
+
* @module @skillsmith/core/testing/MultiLLMProvider
|
|
4
|
+
* @see SMI-1523: Configure multi-LLM provider chain
|
|
5
|
+
*
|
|
6
|
+
* Provides a unified interface for testing skills across multiple LLM providers:
|
|
7
|
+
* - Claude (Anthropic) - Primary
|
|
8
|
+
* - GPT (OpenAI) - Fallback 1
|
|
9
|
+
* - Gemini (Google) - Fallback 2
|
|
10
|
+
* - Cohere (Command) - Fallback 3
|
|
11
|
+
* - Ollama (Local) - Fallback 4
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Automatic failover with configurable strategy
|
|
15
|
+
* - Health monitoring and circuit breaker pattern
|
|
16
|
+
* - Cost-aware provider selection
|
|
17
|
+
* - Skill compatibility testing across providers
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
/**
|
|
21
|
+
* Default multi-LLM provider configuration
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_MULTI_LLM_CONFIG = {
|
|
24
|
+
providers: {
|
|
25
|
+
anthropic: {
|
|
26
|
+
provider: 'anthropic',
|
|
27
|
+
model: 'claude-sonnet-4-20250514',
|
|
28
|
+
enabled: true,
|
|
29
|
+
priority: 'quality',
|
|
30
|
+
maxConcurrency: 50,
|
|
31
|
+
timeoutMs: 60000,
|
|
32
|
+
costPerInputToken: 0.003,
|
|
33
|
+
costPerOutputToken: 0.015,
|
|
34
|
+
},
|
|
35
|
+
openai: {
|
|
36
|
+
provider: 'openai',
|
|
37
|
+
model: 'gpt-4-turbo-preview',
|
|
38
|
+
enabled: true,
|
|
39
|
+
priority: 'speed',
|
|
40
|
+
maxConcurrency: 100,
|
|
41
|
+
timeoutMs: 30000,
|
|
42
|
+
costPerInputToken: 0.01,
|
|
43
|
+
costPerOutputToken: 0.03,
|
|
44
|
+
},
|
|
45
|
+
google: {
|
|
46
|
+
provider: 'google',
|
|
47
|
+
model: 'gemini-pro',
|
|
48
|
+
enabled: true,
|
|
49
|
+
priority: 'cost',
|
|
50
|
+
maxConcurrency: 60,
|
|
51
|
+
timeoutMs: 45000,
|
|
52
|
+
costPerInputToken: 0.00025,
|
|
53
|
+
costPerOutputToken: 0.0005,
|
|
54
|
+
},
|
|
55
|
+
cohere: {
|
|
56
|
+
provider: 'cohere',
|
|
57
|
+
model: 'command-r-plus',
|
|
58
|
+
enabled: true,
|
|
59
|
+
priority: 'cost',
|
|
60
|
+
maxConcurrency: 40,
|
|
61
|
+
timeoutMs: 45000,
|
|
62
|
+
costPerInputToken: 0.003,
|
|
63
|
+
costPerOutputToken: 0.015,
|
|
64
|
+
},
|
|
65
|
+
ollama: {
|
|
66
|
+
provider: 'ollama',
|
|
67
|
+
model: 'llama2',
|
|
68
|
+
apiUrl: 'http://localhost:11434',
|
|
69
|
+
enabled: false, // Disabled by default (requires local setup)
|
|
70
|
+
priority: 'privacy',
|
|
71
|
+
maxConcurrency: 10,
|
|
72
|
+
timeoutMs: 120000,
|
|
73
|
+
costPerInputToken: 0,
|
|
74
|
+
costPerOutputToken: 0,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
defaultProvider: 'anthropic',
|
|
78
|
+
fallbackStrategy: {
|
|
79
|
+
name: 'resilient',
|
|
80
|
+
enabled: true,
|
|
81
|
+
maxAttempts: 3,
|
|
82
|
+
rules: [
|
|
83
|
+
{
|
|
84
|
+
condition: 'rate_limit',
|
|
85
|
+
fallbackProviders: ['openai', 'google', 'cohere'],
|
|
86
|
+
retryOriginal: false,
|
|
87
|
+
retryDelayMs: 5000,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
condition: 'unavailable',
|
|
91
|
+
fallbackProviders: ['openai', 'google', 'cohere', 'ollama'],
|
|
92
|
+
retryOriginal: true,
|
|
93
|
+
retryDelayMs: 10000,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
condition: 'timeout',
|
|
97
|
+
fallbackProviders: ['openai', 'google'],
|
|
98
|
+
retryOriginal: false,
|
|
99
|
+
retryDelayMs: 2000,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
condition: 'error',
|
|
103
|
+
fallbackProviders: ['openai', 'google', 'cohere'],
|
|
104
|
+
retryOriginal: false,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
loadBalancing: {
|
|
109
|
+
enabled: true,
|
|
110
|
+
strategy: 'cost-based',
|
|
111
|
+
},
|
|
112
|
+
costOptimization: {
|
|
113
|
+
enabled: true,
|
|
114
|
+
maxCostPerRequest: 0.05, // $0.05 max per request
|
|
115
|
+
preferredProviders: ['google', 'cohere', 'anthropic'],
|
|
116
|
+
},
|
|
117
|
+
circuitBreaker: {
|
|
118
|
+
timeoutMs: 30000,
|
|
119
|
+
errorThresholdPercentage: 50,
|
|
120
|
+
resetTimeoutMs: 30000,
|
|
121
|
+
volumeThreshold: 10,
|
|
122
|
+
},
|
|
123
|
+
enableMetrics: true,
|
|
124
|
+
useV3Integration: true,
|
|
125
|
+
};
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Circuit Breaker Implementation
|
|
128
|
+
// ============================================================================
|
|
129
|
+
/**
|
|
130
|
+
* Simple circuit breaker for provider failover
|
|
131
|
+
*/
|
|
132
|
+
class CircuitBreaker {
|
|
133
|
+
config;
|
|
134
|
+
state = 'closed';
|
|
135
|
+
failures = 0;
|
|
136
|
+
successes = 0;
|
|
137
|
+
lastFailureTime = null;
|
|
138
|
+
totalRequests = 0;
|
|
139
|
+
constructor(config) {
|
|
140
|
+
this.config = config;
|
|
141
|
+
}
|
|
142
|
+
getState() {
|
|
143
|
+
if (this.state === 'open' && this.lastFailureTime) {
|
|
144
|
+
const elapsed = Date.now() - this.lastFailureTime.getTime();
|
|
145
|
+
if (elapsed >= this.config.resetTimeoutMs) {
|
|
146
|
+
this.state = 'half-open';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return this.state;
|
|
150
|
+
}
|
|
151
|
+
recordSuccess() {
|
|
152
|
+
this.successes++;
|
|
153
|
+
this.totalRequests++;
|
|
154
|
+
if (this.state === 'half-open') {
|
|
155
|
+
this.state = 'closed';
|
|
156
|
+
this.failures = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
recordFailure() {
|
|
160
|
+
this.failures++;
|
|
161
|
+
this.totalRequests++;
|
|
162
|
+
this.lastFailureTime = new Date();
|
|
163
|
+
if (this.totalRequests >= this.config.volumeThreshold) {
|
|
164
|
+
const errorRate = this.failures / this.totalRequests;
|
|
165
|
+
if (errorRate * 100 >= this.config.errorThresholdPercentage) {
|
|
166
|
+
this.state = 'open';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
canExecute() {
|
|
171
|
+
const state = this.getState();
|
|
172
|
+
return state === 'closed' || state === 'half-open';
|
|
173
|
+
}
|
|
174
|
+
reset() {
|
|
175
|
+
this.state = 'closed';
|
|
176
|
+
this.failures = 0;
|
|
177
|
+
this.successes = 0;
|
|
178
|
+
this.totalRequests = 0;
|
|
179
|
+
this.lastFailureTime = null;
|
|
180
|
+
}
|
|
181
|
+
getMetrics() {
|
|
182
|
+
const errorRate = this.totalRequests > 0 ? this.failures / this.totalRequests : 0;
|
|
183
|
+
return {
|
|
184
|
+
failures: this.failures,
|
|
185
|
+
successes: this.successes,
|
|
186
|
+
errorRate,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Multi-LLM Provider Implementation
|
|
192
|
+
// ============================================================================
|
|
193
|
+
/**
|
|
194
|
+
* Multi-LLM Provider for skill compatibility testing
|
|
195
|
+
*
|
|
196
|
+
* Provides unified access to multiple LLM providers with automatic failover,
|
|
197
|
+
* load balancing, and cost optimization.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const provider = new MultiLLMProvider()
|
|
202
|
+
* await provider.initialize()
|
|
203
|
+
*
|
|
204
|
+
* // Complete a request with automatic failover
|
|
205
|
+
* const response = await provider.complete({
|
|
206
|
+
* messages: [{ role: 'user', content: 'Explain this skill' }]
|
|
207
|
+
* })
|
|
208
|
+
*
|
|
209
|
+
* // Test skill compatibility across providers
|
|
210
|
+
* const compatibility = await provider.testSkillCompatibility('commit')
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export class MultiLLMProvider extends EventEmitter {
|
|
214
|
+
config;
|
|
215
|
+
initialized = false;
|
|
216
|
+
circuitBreakers = new Map();
|
|
217
|
+
providerMetrics = new Map();
|
|
218
|
+
activeRequests = new Map();
|
|
219
|
+
roundRobinIndex = 0;
|
|
220
|
+
// V3 integration
|
|
221
|
+
v3ProviderManager = null;
|
|
222
|
+
constructor(config = {}) {
|
|
223
|
+
super();
|
|
224
|
+
this.config = {
|
|
225
|
+
...DEFAULT_MULTI_LLM_CONFIG,
|
|
226
|
+
...config,
|
|
227
|
+
providers: { ...DEFAULT_MULTI_LLM_CONFIG.providers, ...config.providers },
|
|
228
|
+
fallbackStrategy: {
|
|
229
|
+
...DEFAULT_MULTI_LLM_CONFIG.fallbackStrategy,
|
|
230
|
+
...config.fallbackStrategy,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Initialize the multi-LLM provider
|
|
236
|
+
*/
|
|
237
|
+
async initialize() {
|
|
238
|
+
if (this.initialized)
|
|
239
|
+
return;
|
|
240
|
+
// Initialize circuit breakers for each enabled provider
|
|
241
|
+
for (const [providerType, providerConfig] of Object.entries(this.config.providers)) {
|
|
242
|
+
if (providerConfig?.enabled) {
|
|
243
|
+
this.circuitBreakers.set(providerType, new CircuitBreaker(this.config.circuitBreaker));
|
|
244
|
+
this.activeRequests.set(providerType, 0);
|
|
245
|
+
this.initializeMetrics(providerType);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Try V3 integration
|
|
249
|
+
if (this.config.useV3Integration) {
|
|
250
|
+
await this.initializeV3Integration();
|
|
251
|
+
}
|
|
252
|
+
this.initialized = true;
|
|
253
|
+
this.emit('initialized', {
|
|
254
|
+
providers: this.getEnabledProviders(),
|
|
255
|
+
defaultProvider: this.config.defaultProvider,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if provider is initialized
|
|
260
|
+
*/
|
|
261
|
+
isInitialized() {
|
|
262
|
+
return this.initialized;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Attempt to initialize V3 ProviderManager integration
|
|
266
|
+
*/
|
|
267
|
+
async initializeV3Integration() {
|
|
268
|
+
try {
|
|
269
|
+
const { ProviderManager } = await import(
|
|
270
|
+
// @ts-expect-error - V3 types not available at compile time
|
|
271
|
+
'claude-flow/providers');
|
|
272
|
+
this.v3ProviderManager = new ProviderManager(console, null, {
|
|
273
|
+
providers: this.config.providers,
|
|
274
|
+
defaultProvider: this.config.defaultProvider,
|
|
275
|
+
fallbackStrategy: this.config.fallbackStrategy,
|
|
276
|
+
loadBalancing: this.config.loadBalancing,
|
|
277
|
+
costOptimization: this.config.costOptimization,
|
|
278
|
+
monitoring: { enabled: this.config.enableMetrics, metricsInterval: 60000 },
|
|
279
|
+
});
|
|
280
|
+
this.emit('v3_integration', { enabled: true });
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
this.emit('v3_integration', { enabled: false, reason: 'V3 not available' });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get enabled providers
|
|
288
|
+
*/
|
|
289
|
+
getEnabledProviders() {
|
|
290
|
+
return Object.entries(this.config.providers)
|
|
291
|
+
.filter(([, config]) => config?.enabled)
|
|
292
|
+
.map(([type]) => type);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get available providers (enabled + circuit closed)
|
|
296
|
+
*/
|
|
297
|
+
getAvailableProviders() {
|
|
298
|
+
return this.getEnabledProviders().filter((provider) => {
|
|
299
|
+
const breaker = this.circuitBreakers.get(provider);
|
|
300
|
+
return breaker?.canExecute() ?? false;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Check provider health
|
|
305
|
+
*/
|
|
306
|
+
async healthCheck(provider) {
|
|
307
|
+
const startTime = Date.now();
|
|
308
|
+
try {
|
|
309
|
+
// Simple health check - attempt minimal completion
|
|
310
|
+
const config = this.config.providers[provider];
|
|
311
|
+
if (!config?.enabled) {
|
|
312
|
+
return {
|
|
313
|
+
healthy: false,
|
|
314
|
+
error: 'Provider not enabled',
|
|
315
|
+
timestamp: new Date(),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
// In production, this would make an actual API call
|
|
319
|
+
// For now, return healthy if circuit is closed
|
|
320
|
+
const breaker = this.circuitBreakers.get(provider);
|
|
321
|
+
const healthy = breaker?.canExecute() ?? false;
|
|
322
|
+
return {
|
|
323
|
+
healthy,
|
|
324
|
+
latencyMs: Date.now() - startTime,
|
|
325
|
+
timestamp: new Date(),
|
|
326
|
+
details: {
|
|
327
|
+
circuitState: breaker?.getState() ?? 'unknown',
|
|
328
|
+
model: config.model,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return {
|
|
334
|
+
healthy: false,
|
|
335
|
+
latencyMs: Date.now() - startTime,
|
|
336
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
337
|
+
timestamp: new Date(),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get provider status
|
|
343
|
+
*/
|
|
344
|
+
getProviderStatus(provider) {
|
|
345
|
+
const breaker = this.circuitBreakers.get(provider);
|
|
346
|
+
const activeReqs = this.activeRequests.get(provider) ?? 0;
|
|
347
|
+
const config = this.config.providers[provider];
|
|
348
|
+
const maxConcurrency = config?.maxConcurrency ?? 50;
|
|
349
|
+
return {
|
|
350
|
+
available: breaker?.canExecute() ?? false,
|
|
351
|
+
currentLoad: activeReqs / maxConcurrency,
|
|
352
|
+
queueLength: 0, // Would be tracked in production
|
|
353
|
+
activeRequests: activeReqs,
|
|
354
|
+
circuitState: breaker?.getState() ?? 'closed',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Complete a request with automatic failover
|
|
359
|
+
*/
|
|
360
|
+
async complete(request) {
|
|
361
|
+
this.ensureInitialized();
|
|
362
|
+
const startTime = Date.now();
|
|
363
|
+
const provider = request.provider ?? this.selectProvider(request);
|
|
364
|
+
const fallbackChain = [];
|
|
365
|
+
let currentProvider = provider;
|
|
366
|
+
let attempts = 0;
|
|
367
|
+
const maxAttempts = this.config.fallbackStrategy.maxAttempts;
|
|
368
|
+
while (attempts < maxAttempts) {
|
|
369
|
+
attempts++;
|
|
370
|
+
try {
|
|
371
|
+
const response = await this.executeRequest(currentProvider, request);
|
|
372
|
+
// Record success
|
|
373
|
+
this.circuitBreakers.get(currentProvider)?.recordSuccess();
|
|
374
|
+
this.updateMetrics(currentProvider, {
|
|
375
|
+
success: true,
|
|
376
|
+
latencyMs: Date.now() - startTime,
|
|
377
|
+
cost: response.cost,
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
...response,
|
|
381
|
+
usedFallback: fallbackChain.length > 0,
|
|
382
|
+
fallbackChain: fallbackChain.length > 0 ? fallbackChain : undefined,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// Record failure
|
|
387
|
+
this.circuitBreakers.get(currentProvider)?.recordFailure();
|
|
388
|
+
this.updateMetrics(currentProvider, {
|
|
389
|
+
success: false,
|
|
390
|
+
latencyMs: Date.now() - startTime,
|
|
391
|
+
});
|
|
392
|
+
this.emit('provider_error', { provider: currentProvider, error });
|
|
393
|
+
// Get fallback provider
|
|
394
|
+
const fallbackProvider = this.getFallbackProvider(currentProvider, error);
|
|
395
|
+
if (fallbackProvider && attempts < maxAttempts) {
|
|
396
|
+
fallbackChain.push(currentProvider);
|
|
397
|
+
currentProvider = fallbackProvider;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
throw new Error(`All providers failed after ${maxAttempts} attempts`);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Test skill compatibility across providers
|
|
407
|
+
*/
|
|
408
|
+
async testSkillCompatibility(skillId) {
|
|
409
|
+
this.ensureInitialized();
|
|
410
|
+
const results = {};
|
|
411
|
+
const enabledProviders = this.getEnabledProviders();
|
|
412
|
+
const testPrompt = `Analyze if you can effectively help a user with a skill called "${skillId}".
|
|
413
|
+
Respond with a brief assessment of your capability.`;
|
|
414
|
+
for (const provider of enabledProviders) {
|
|
415
|
+
const startTime = Date.now();
|
|
416
|
+
try {
|
|
417
|
+
const response = await this.complete({
|
|
418
|
+
messages: [{ role: 'user', content: testPrompt }],
|
|
419
|
+
provider,
|
|
420
|
+
maxTokens: 100,
|
|
421
|
+
});
|
|
422
|
+
results[provider] = {
|
|
423
|
+
compatible: true,
|
|
424
|
+
score: this.calculateCompatibilityScore(response),
|
|
425
|
+
latencyMs: response.latencyMs,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
results[provider] = {
|
|
430
|
+
compatible: false,
|
|
431
|
+
score: 0,
|
|
432
|
+
latencyMs: Date.now() - startTime,
|
|
433
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Calculate overall score
|
|
438
|
+
const scores = Object.values(results)
|
|
439
|
+
.filter((r) => r.compatible)
|
|
440
|
+
.map((r) => r.score);
|
|
441
|
+
const overallScore = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0;
|
|
442
|
+
// Recommend providers (compatible with score > 0.7)
|
|
443
|
+
const recommendedProviders = Object.entries(results)
|
|
444
|
+
.filter(([, r]) => r.compatible && r.score >= 0.7)
|
|
445
|
+
.sort(([, a], [, b]) => b.score - a.score)
|
|
446
|
+
.map(([p]) => p);
|
|
447
|
+
return {
|
|
448
|
+
skillId,
|
|
449
|
+
results,
|
|
450
|
+
overallScore,
|
|
451
|
+
recommendedProviders,
|
|
452
|
+
testedAt: new Date(),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get provider metrics
|
|
457
|
+
*/
|
|
458
|
+
getMetrics() {
|
|
459
|
+
return new Map(this.providerMetrics);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get aggregated metrics
|
|
463
|
+
*/
|
|
464
|
+
getAggregatedMetrics() {
|
|
465
|
+
let totalRequests = 0;
|
|
466
|
+
let totalCost = 0;
|
|
467
|
+
let totalLatency = 0;
|
|
468
|
+
let totalSuccessRate = 0;
|
|
469
|
+
const providerBreakdown = {};
|
|
470
|
+
for (const [provider, metrics] of this.providerMetrics) {
|
|
471
|
+
totalRequests += metrics.totalRequests;
|
|
472
|
+
totalCost += metrics.totalCost;
|
|
473
|
+
totalLatency += metrics.avgLatencyMs * metrics.totalRequests;
|
|
474
|
+
totalSuccessRate += metrics.successRate;
|
|
475
|
+
providerBreakdown[provider] = metrics.totalRequests;
|
|
476
|
+
}
|
|
477
|
+
const providerCount = this.providerMetrics.size;
|
|
478
|
+
return {
|
|
479
|
+
totalRequests,
|
|
480
|
+
totalCost,
|
|
481
|
+
avgLatencyMs: totalRequests > 0 ? totalLatency / totalRequests : 0,
|
|
482
|
+
avgSuccessRate: providerCount > 0 ? totalSuccessRate / providerCount : 0,
|
|
483
|
+
providerBreakdown,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Close and cleanup
|
|
488
|
+
*
|
|
489
|
+
* After close, the provider cannot be used until initialize() is called again.
|
|
490
|
+
*/
|
|
491
|
+
close() {
|
|
492
|
+
this.circuitBreakers.clear();
|
|
493
|
+
this.providerMetrics.clear();
|
|
494
|
+
this.activeRequests.clear();
|
|
495
|
+
this.removeAllListeners();
|
|
496
|
+
this.initialized = false;
|
|
497
|
+
this.v3ProviderManager = null;
|
|
498
|
+
this.roundRobinIndex = 0;
|
|
499
|
+
}
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// Private Methods
|
|
502
|
+
// ============================================================================
|
|
503
|
+
ensureInitialized() {
|
|
504
|
+
if (!this.initialized) {
|
|
505
|
+
throw new Error('MultiLLMProvider not initialized. Call initialize() first.');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
selectProvider(request) {
|
|
509
|
+
const available = this.getAvailableProviders();
|
|
510
|
+
if (available.length === 0) {
|
|
511
|
+
throw new Error('No providers available');
|
|
512
|
+
}
|
|
513
|
+
// Cost constraint check
|
|
514
|
+
if (request.costConstraints?.maxCost && this.config.costOptimization.enabled) {
|
|
515
|
+
const costSorted = available.sort((a, b) => {
|
|
516
|
+
const configA = this.config.providers[a];
|
|
517
|
+
const configB = this.config.providers[b];
|
|
518
|
+
const costA = (configA?.costPerInputToken ?? 0) + (configA?.costPerOutputToken ?? 0);
|
|
519
|
+
const costB = (configB?.costPerInputToken ?? 0) + (configB?.costPerOutputToken ?? 0);
|
|
520
|
+
return costA - costB;
|
|
521
|
+
});
|
|
522
|
+
return costSorted[0];
|
|
523
|
+
}
|
|
524
|
+
// Load balancing
|
|
525
|
+
if (this.config.loadBalancing.enabled) {
|
|
526
|
+
switch (this.config.loadBalancing.strategy) {
|
|
527
|
+
case 'round-robin':
|
|
528
|
+
return this.selectRoundRobin(available);
|
|
529
|
+
case 'least-loaded':
|
|
530
|
+
return this.selectLeastLoaded(available);
|
|
531
|
+
case 'latency-based':
|
|
532
|
+
return this.selectByLatency(available);
|
|
533
|
+
case 'cost-based':
|
|
534
|
+
return this.selectByCost(available);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Default to configured default
|
|
538
|
+
if (available.includes(this.config.defaultProvider)) {
|
|
539
|
+
return this.config.defaultProvider;
|
|
540
|
+
}
|
|
541
|
+
return available[0];
|
|
542
|
+
}
|
|
543
|
+
selectRoundRobin(providers) {
|
|
544
|
+
const selected = providers[this.roundRobinIndex % providers.length];
|
|
545
|
+
this.roundRobinIndex++;
|
|
546
|
+
return selected;
|
|
547
|
+
}
|
|
548
|
+
selectLeastLoaded(providers) {
|
|
549
|
+
return providers.reduce((best, current) => {
|
|
550
|
+
const bestStatus = this.getProviderStatus(best);
|
|
551
|
+
const currentStatus = this.getProviderStatus(current);
|
|
552
|
+
return currentStatus.currentLoad < bestStatus.currentLoad ? current : best;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
selectByLatency(providers) {
|
|
556
|
+
return providers.reduce((best, current) => {
|
|
557
|
+
const bestMetrics = this.providerMetrics.get(best);
|
|
558
|
+
const currentMetrics = this.providerMetrics.get(current);
|
|
559
|
+
const bestLatency = bestMetrics?.avgLatencyMs ?? Infinity;
|
|
560
|
+
const currentLatency = currentMetrics?.avgLatencyMs ?? Infinity;
|
|
561
|
+
return currentLatency < bestLatency ? current : best;
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
selectByCost(providers) {
|
|
565
|
+
return providers.reduce((best, current) => {
|
|
566
|
+
const configBest = this.config.providers[best];
|
|
567
|
+
const configCurrent = this.config.providers[current];
|
|
568
|
+
const costBest = (configBest?.costPerInputToken ?? 0) + (configBest?.costPerOutputToken ?? 0);
|
|
569
|
+
const costCurrent = (configCurrent?.costPerInputToken ?? 0) + (configCurrent?.costPerOutputToken ?? 0);
|
|
570
|
+
return costCurrent < costBest ? current : best;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
getFallbackProvider(currentProvider, error) {
|
|
574
|
+
if (!this.config.fallbackStrategy.enabled)
|
|
575
|
+
return null;
|
|
576
|
+
const condition = this.getErrorCondition(error);
|
|
577
|
+
const rule = this.config.fallbackStrategy.rules.find((r) => r.condition === condition);
|
|
578
|
+
if (!rule)
|
|
579
|
+
return null;
|
|
580
|
+
const available = this.getAvailableProviders();
|
|
581
|
+
for (const fallback of rule.fallbackProviders) {
|
|
582
|
+
if (fallback !== currentProvider && available.includes(fallback)) {
|
|
583
|
+
return fallback;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
getErrorCondition(error) {
|
|
589
|
+
if (error instanceof Error) {
|
|
590
|
+
const message = error.message.toLowerCase();
|
|
591
|
+
if (message.includes('rate limit') || message.includes('429')) {
|
|
592
|
+
return 'rate_limit';
|
|
593
|
+
}
|
|
594
|
+
if (message.includes('timeout') || message.includes('timed out')) {
|
|
595
|
+
return 'timeout';
|
|
596
|
+
}
|
|
597
|
+
if (message.includes('unavailable') ||
|
|
598
|
+
message.includes('503') ||
|
|
599
|
+
message.includes('connection')) {
|
|
600
|
+
return 'unavailable';
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return 'error';
|
|
604
|
+
}
|
|
605
|
+
async executeRequest(provider, request) {
|
|
606
|
+
const config = this.config.providers[provider];
|
|
607
|
+
if (!config) {
|
|
608
|
+
throw new Error(`Provider ${provider} not configured`);
|
|
609
|
+
}
|
|
610
|
+
const startTime = Date.now();
|
|
611
|
+
// Increment active requests
|
|
612
|
+
const current = this.activeRequests.get(provider) ?? 0;
|
|
613
|
+
this.activeRequests.set(provider, current + 1);
|
|
614
|
+
try {
|
|
615
|
+
// Simulate request execution
|
|
616
|
+
// In production, this would make actual API calls
|
|
617
|
+
await this.simulateRequest(config);
|
|
618
|
+
const latencyMs = Date.now() - startTime;
|
|
619
|
+
const inputTokens = this.estimateTokens(request.messages.map((m) => m.content).join(' '));
|
|
620
|
+
const outputTokens = 100; // Simulated
|
|
621
|
+
return {
|
|
622
|
+
content: `[Simulated response from ${provider}]`,
|
|
623
|
+
provider,
|
|
624
|
+
model: request.model ?? config.model,
|
|
625
|
+
usage: {
|
|
626
|
+
inputTokens,
|
|
627
|
+
outputTokens,
|
|
628
|
+
totalTokens: inputTokens + outputTokens,
|
|
629
|
+
},
|
|
630
|
+
cost: this.calculateCost(config, inputTokens, outputTokens),
|
|
631
|
+
latencyMs,
|
|
632
|
+
usedFallback: false,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
finally {
|
|
636
|
+
// Decrement active requests
|
|
637
|
+
const currentAfter = this.activeRequests.get(provider) ?? 1;
|
|
638
|
+
this.activeRequests.set(provider, Math.max(0, currentAfter - 1));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async simulateRequest(_config) {
|
|
642
|
+
// Simulate network latency (50-200ms)
|
|
643
|
+
// In production, this would use _config to make actual API calls
|
|
644
|
+
const latency = 50 + Math.random() * 150;
|
|
645
|
+
await new Promise((resolve) => setTimeout(resolve, latency));
|
|
646
|
+
// Simulate occasional failures (5% rate)
|
|
647
|
+
if (Math.random() < 0.05) {
|
|
648
|
+
throw new Error('Simulated provider error');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
estimateTokens(text) {
|
|
652
|
+
// Rough estimate: ~4 characters per token
|
|
653
|
+
return Math.ceil(text.length / 4);
|
|
654
|
+
}
|
|
655
|
+
calculateCost(config, inputTokens, outputTokens) {
|
|
656
|
+
const inputCost = ((config.costPerInputToken ?? 0) * inputTokens) / 1000;
|
|
657
|
+
const outputCost = ((config.costPerOutputToken ?? 0) * outputTokens) / 1000;
|
|
658
|
+
return inputCost + outputCost;
|
|
659
|
+
}
|
|
660
|
+
calculateCompatibilityScore(response) {
|
|
661
|
+
// Simple scoring based on latency and success
|
|
662
|
+
// Lower latency = higher score, max score 1.0
|
|
663
|
+
const latencyScore = Math.max(0, 1 - response.latencyMs / 5000);
|
|
664
|
+
return Math.min(1, latencyScore + 0.3); // Base 0.3 for successful response
|
|
665
|
+
}
|
|
666
|
+
initializeMetrics(provider) {
|
|
667
|
+
this.providerMetrics.set(provider, {
|
|
668
|
+
provider,
|
|
669
|
+
timestamp: new Date(),
|
|
670
|
+
avgLatencyMs: 0,
|
|
671
|
+
errorRate: 0,
|
|
672
|
+
successRate: 1,
|
|
673
|
+
load: 0,
|
|
674
|
+
totalCost: 0,
|
|
675
|
+
totalRequests: 0,
|
|
676
|
+
availability: 1,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
updateMetrics(provider, update) {
|
|
680
|
+
const metrics = this.providerMetrics.get(provider);
|
|
681
|
+
if (!metrics)
|
|
682
|
+
return;
|
|
683
|
+
metrics.totalRequests++;
|
|
684
|
+
metrics.timestamp = new Date();
|
|
685
|
+
// Rolling average for latency
|
|
686
|
+
metrics.avgLatencyMs =
|
|
687
|
+
(metrics.avgLatencyMs * (metrics.totalRequests - 1) + update.latencyMs) /
|
|
688
|
+
metrics.totalRequests;
|
|
689
|
+
// Update success/error rates
|
|
690
|
+
const breaker = this.circuitBreakers.get(provider);
|
|
691
|
+
if (breaker) {
|
|
692
|
+
const breakerMetrics = breaker.getMetrics();
|
|
693
|
+
metrics.errorRate = breakerMetrics.errorRate;
|
|
694
|
+
metrics.successRate = 1 - breakerMetrics.errorRate;
|
|
695
|
+
}
|
|
696
|
+
// Update cost
|
|
697
|
+
if (update.cost) {
|
|
698
|
+
metrics.totalCost += update.cost;
|
|
699
|
+
}
|
|
700
|
+
// Update load
|
|
701
|
+
const status = this.getProviderStatus(provider);
|
|
702
|
+
metrics.load = status.currentLoad;
|
|
703
|
+
this.emit('metrics', metrics);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// ============================================================================
|
|
707
|
+
// Factory Function
|
|
708
|
+
// ============================================================================
|
|
709
|
+
/**
|
|
710
|
+
* Create and initialize a MultiLLMProvider instance
|
|
711
|
+
*
|
|
712
|
+
* @param config - Configuration options
|
|
713
|
+
* @returns Initialized MultiLLMProvider
|
|
714
|
+
*/
|
|
715
|
+
export async function createMultiLLMProvider(config = {}) {
|
|
716
|
+
const provider = new MultiLLMProvider(config);
|
|
717
|
+
await provider.initialize();
|
|
718
|
+
return provider;
|
|
719
|
+
}
|
|
720
|
+
//# sourceMappingURL=MultiLLMProvider.js.map
|