@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.
Files changed (204) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/src/analysis/types.d.ts +2 -0
  3. package/dist/src/analysis/types.d.ts.map +1 -1
  4. package/dist/src/analysis/types.js +13 -1
  5. package/dist/src/analysis/types.js.map +1 -1
  6. package/dist/src/analytics/AnalyticsRepository.d.ts +4 -0
  7. package/dist/src/analytics/AnalyticsRepository.d.ts.map +1 -1
  8. package/dist/src/analytics/AnalyticsRepository.js +26 -44
  9. package/dist/src/analytics/AnalyticsRepository.js.map +1 -1
  10. package/dist/src/analytics/schema.d.ts +1 -1
  11. package/dist/src/analytics/schema.d.ts.map +1 -1
  12. package/dist/src/analytics/schema.js +68 -0
  13. package/dist/src/analytics/schema.js.map +1 -1
  14. package/dist/src/api/client.d.ts +33 -29
  15. package/dist/src/api/client.d.ts.map +1 -1
  16. package/dist/src/api/client.js +15 -10
  17. package/dist/src/api/client.js.map +1 -1
  18. package/dist/src/billing/BillingService.d.ts +139 -0
  19. package/dist/src/billing/BillingService.d.ts.map +1 -0
  20. package/dist/src/billing/BillingService.js +393 -0
  21. package/dist/src/billing/BillingService.js.map +1 -0
  22. package/dist/src/billing/GDPRComplianceService.d.ts +176 -0
  23. package/dist/src/billing/GDPRComplianceService.d.ts.map +1 -0
  24. package/dist/src/billing/GDPRComplianceService.js +361 -0
  25. package/dist/src/billing/GDPRComplianceService.js.map +1 -0
  26. package/dist/src/billing/StripeClient.d.ts +177 -0
  27. package/dist/src/billing/StripeClient.d.ts.map +1 -0
  28. package/dist/src/billing/StripeClient.js +462 -0
  29. package/dist/src/billing/StripeClient.js.map +1 -0
  30. package/dist/src/billing/StripeReconciliationJob.d.ts +95 -0
  31. package/dist/src/billing/StripeReconciliationJob.d.ts.map +1 -0
  32. package/dist/src/billing/StripeReconciliationJob.js +405 -0
  33. package/dist/src/billing/StripeReconciliationJob.js.map +1 -0
  34. package/dist/src/billing/StripeWebhookHandler.d.ts +92 -0
  35. package/dist/src/billing/StripeWebhookHandler.d.ts.map +1 -0
  36. package/dist/src/billing/StripeWebhookHandler.js +409 -0
  37. package/dist/src/billing/StripeWebhookHandler.js.map +1 -0
  38. package/dist/src/billing/index.d.ts +18 -0
  39. package/dist/src/billing/index.d.ts.map +1 -0
  40. package/dist/src/billing/index.js +19 -0
  41. package/dist/src/billing/index.js.map +1 -0
  42. package/dist/src/billing/types.d.ts +266 -0
  43. package/dist/src/billing/types.d.ts.map +1 -0
  44. package/dist/src/billing/types.js +23 -0
  45. package/dist/src/billing/types.js.map +1 -0
  46. package/dist/src/embeddings/hnsw-store.d.ts +568 -0
  47. package/dist/src/embeddings/hnsw-store.d.ts.map +1 -0
  48. package/dist/src/embeddings/hnsw-store.js +805 -0
  49. package/dist/src/embeddings/hnsw-store.js.map +1 -0
  50. package/dist/src/embeddings/index.d.ts +2 -0
  51. package/dist/src/embeddings/index.d.ts.map +1 -1
  52. package/dist/src/embeddings/index.js +2 -0
  53. package/dist/src/embeddings/index.js.map +1 -1
  54. package/dist/src/index.d.ts +1 -0
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +2 -0
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/learning/PatternStore.d.ts +457 -0
  59. package/dist/src/learning/PatternStore.d.ts.map +1 -0
  60. package/dist/src/learning/PatternStore.js +893 -0
  61. package/dist/src/learning/PatternStore.js.map +1 -0
  62. package/dist/src/learning/ReasoningBankIntegration.d.ts +403 -0
  63. package/dist/src/learning/ReasoningBankIntegration.d.ts.map +1 -0
  64. package/dist/src/learning/ReasoningBankIntegration.js +627 -0
  65. package/dist/src/learning/ReasoningBankIntegration.js.map +1 -0
  66. package/dist/src/learning/index.d.ts +15 -0
  67. package/dist/src/learning/index.d.ts.map +1 -0
  68. package/dist/src/learning/index.js +15 -0
  69. package/dist/src/learning/index.js.map +1 -0
  70. package/dist/src/routing/SONARouter.d.ts +154 -0
  71. package/dist/src/routing/SONARouter.d.ts.map +1 -0
  72. package/dist/src/routing/SONARouter.js +679 -0
  73. package/dist/src/routing/SONARouter.js.map +1 -0
  74. package/dist/src/routing/index.d.ts +9 -0
  75. package/dist/src/routing/index.d.ts.map +1 -0
  76. package/dist/src/routing/index.js +10 -0
  77. package/dist/src/routing/index.js.map +1 -0
  78. package/dist/src/routing/types.d.ts +331 -0
  79. package/dist/src/routing/types.d.ts.map +1 -0
  80. package/dist/src/routing/types.js +203 -0
  81. package/dist/src/routing/types.js.map +1 -0
  82. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
  83. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  84. package/dist/src/security/SkillSandbox.d.ts +156 -0
  85. package/dist/src/security/SkillSandbox.d.ts.map +1 -0
  86. package/dist/src/security/SkillSandbox.js +303 -0
  87. package/dist/src/security/SkillSandbox.js.map +1 -0
  88. package/dist/src/security/index.d.ts +3 -1
  89. package/dist/src/security/index.d.ts.map +1 -1
  90. package/dist/src/security/index.js +5 -1
  91. package/dist/src/security/index.js.map +1 -1
  92. package/dist/src/security/rate-limiter/presets.d.ts +12 -0
  93. package/dist/src/security/rate-limiter/presets.d.ts.map +1 -1
  94. package/dist/src/security/rate-limiter/presets.js +12 -0
  95. package/dist/src/security/rate-limiter/presets.js.map +1 -1
  96. package/dist/src/security/sanitization.d.ts +85 -0
  97. package/dist/src/security/sanitization.d.ts.map +1 -1
  98. package/dist/src/security/sanitization.js +133 -0
  99. package/dist/src/security/sanitization.js.map +1 -1
  100. package/dist/src/security/scanner/SecurityScanner.d.ts +23 -0
  101. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  102. package/dist/src/security/scanner/SecurityScanner.js +232 -28
  103. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  104. package/dist/src/security/scanner/patterns.d.ts +13 -0
  105. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  106. package/dist/src/security/scanner/patterns.js +51 -0
  107. package/dist/src/security/scanner/patterns.js.map +1 -1
  108. package/dist/src/security/scanner/types.d.ts +13 -1
  109. package/dist/src/security/scanner/types.d.ts.map +1 -1
  110. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  111. package/dist/src/security/scanner/weights.js +1 -0
  112. package/dist/src/security/scanner/weights.js.map +1 -1
  113. package/dist/src/session/SessionManager.d.ts +7 -0
  114. package/dist/src/session/SessionManager.d.ts.map +1 -1
  115. package/dist/src/session/SessionManager.js +117 -10
  116. package/dist/src/session/SessionManager.js.map +1 -1
  117. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  118. package/dist/src/sync/SyncEngine.js +52 -32
  119. package/dist/src/sync/SyncEngine.js.map +1 -1
  120. package/dist/src/testing/MultiLLMProvider.d.ts +374 -0
  121. package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -0
  122. package/dist/src/testing/MultiLLMProvider.js +720 -0
  123. package/dist/src/testing/MultiLLMProvider.js.map +1 -0
  124. package/dist/src/testing/index.d.ts +8 -0
  125. package/dist/src/testing/index.d.ts.map +1 -0
  126. package/dist/src/testing/index.js +9 -0
  127. package/dist/src/testing/index.js.map +1 -0
  128. package/dist/src/types.d.ts +3 -0
  129. package/dist/src/types.d.ts.map +1 -1
  130. package/dist/tests/SecurityScanner.test.js +337 -1
  131. package/dist/tests/SecurityScanner.test.js.map +1 -1
  132. package/dist/tests/billing/BillingService.test.d.ts +7 -0
  133. package/dist/tests/billing/BillingService.test.d.ts.map +1 -0
  134. package/dist/tests/billing/BillingService.test.js +168 -0
  135. package/dist/tests/billing/BillingService.test.js.map +1 -0
  136. package/dist/tests/billing/GDPRCompliance.test.d.ts +7 -0
  137. package/dist/tests/billing/GDPRCompliance.test.d.ts.map +1 -0
  138. package/dist/tests/billing/GDPRCompliance.test.js +195 -0
  139. package/dist/tests/billing/GDPRCompliance.test.js.map +1 -0
  140. package/dist/tests/billing/StripeReconciliation.test.d.ts +7 -0
  141. package/dist/tests/billing/StripeReconciliation.test.d.ts.map +1 -0
  142. package/dist/tests/billing/StripeReconciliation.test.js +266 -0
  143. package/dist/tests/billing/StripeReconciliation.test.js.map +1 -0
  144. package/dist/tests/billing/stripe-validators.test.d.ts +7 -0
  145. package/dist/tests/billing/stripe-validators.test.d.ts.map +1 -0
  146. package/dist/tests/billing/stripe-validators.test.js +107 -0
  147. package/dist/tests/billing/stripe-validators.test.js.map +1 -0
  148. package/dist/tests/embeddings/hnsw-store.test.d.ts +7 -0
  149. package/dist/tests/embeddings/hnsw-store.test.d.ts.map +1 -0
  150. package/dist/tests/embeddings/hnsw-store.test.js +295 -0
  151. package/dist/tests/embeddings/hnsw-store.test.js.map +1 -0
  152. package/dist/tests/integration/neural/e2e-learning.test.d.ts +17 -0
  153. package/dist/tests/integration/neural/e2e-learning.test.d.ts.map +1 -0
  154. package/dist/tests/integration/neural/e2e-learning.test.js +238 -0
  155. package/dist/tests/integration/neural/e2e-learning.test.js.map +1 -0
  156. package/dist/tests/integration/neural/helpers.d.ts +132 -0
  157. package/dist/tests/integration/neural/helpers.d.ts.map +1 -0
  158. package/dist/tests/integration/neural/helpers.js +287 -0
  159. package/dist/tests/integration/neural/helpers.js.map +1 -0
  160. package/dist/tests/integration/neural/personalization.test.d.ts +21 -0
  161. package/dist/tests/integration/neural/personalization.test.d.ts.map +1 -0
  162. package/dist/tests/integration/neural/personalization.test.js +304 -0
  163. package/dist/tests/integration/neural/personalization.test.js.map +1 -0
  164. package/dist/tests/integration/neural/preference-learner.test.d.ts +23 -0
  165. package/dist/tests/integration/neural/preference-learner.test.d.ts.map +1 -0
  166. package/dist/tests/integration/neural/preference-learner.test.js +289 -0
  167. package/dist/tests/integration/neural/preference-learner.test.js.map +1 -0
  168. package/dist/tests/integration/neural/privacy.test.d.ts +19 -0
  169. package/dist/tests/integration/neural/privacy.test.d.ts.map +1 -0
  170. package/dist/tests/integration/neural/privacy.test.js +249 -0
  171. package/dist/tests/integration/neural/privacy.test.js.map +1 -0
  172. package/dist/tests/integration/neural/setup.d.ts +175 -0
  173. package/dist/tests/integration/neural/setup.d.ts.map +1 -0
  174. package/dist/tests/integration/neural/setup.js +487 -0
  175. package/dist/tests/integration/neural/setup.js.map +1 -0
  176. package/dist/tests/integration/neural/signal-collection.test.d.ts +21 -0
  177. package/dist/tests/integration/neural/signal-collection.test.d.ts.map +1 -0
  178. package/dist/tests/integration/neural/signal-collection.test.js +232 -0
  179. package/dist/tests/integration/neural/signal-collection.test.js.map +1 -0
  180. package/dist/tests/learning/PatternStore.test.d.ts +8 -0
  181. package/dist/tests/learning/PatternStore.test.d.ts.map +1 -0
  182. package/dist/tests/learning/PatternStore.test.js +589 -0
  183. package/dist/tests/learning/PatternStore.test.js.map +1 -0
  184. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts +8 -0
  185. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts.map +1 -0
  186. package/dist/tests/learning/ReasoningBankIntegration.test.js +269 -0
  187. package/dist/tests/learning/ReasoningBankIntegration.test.js.map +1 -0
  188. package/dist/tests/routing/SONARouter.test.d.ts +8 -0
  189. package/dist/tests/routing/SONARouter.test.d.ts.map +1 -0
  190. package/dist/tests/routing/SONARouter.test.js +400 -0
  191. package/dist/tests/routing/SONARouter.test.js.map +1 -0
  192. package/dist/tests/security/ContinuousSecurity.test.js +10 -12
  193. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  194. package/dist/tests/security/SkillSandbox.test.d.ts +8 -0
  195. package/dist/tests/security/SkillSandbox.test.d.ts.map +1 -0
  196. package/dist/tests/security/SkillSandbox.test.js +321 -0
  197. package/dist/tests/security/SkillSandbox.test.js.map +1 -0
  198. package/dist/tests/sync/SyncEngine.test.js +4 -2
  199. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  200. package/dist/tests/testing/MultiLLMProvider.test.d.ts +14 -0
  201. package/dist/tests/testing/MultiLLMProvider.test.d.ts.map +1 -0
  202. package/dist/tests/testing/MultiLLMProvider.test.js +438 -0
  203. package/dist/tests/testing/MultiLLMProvider.test.js.map +1 -0
  204. 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