agentic-qe 2.2.0 → 2.2.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 (112) hide show
  1. package/.claude/agents/qe-security-scanner.md +26 -0
  2. package/.claude/skills/agentic-quality-engineering/SKILL.md +4 -4
  3. package/.claude/skills/cicd-pipeline-qe-orchestrator/README.md +14 -11
  4. package/.claude/skills/skills-manifest.json +2 -2
  5. package/CHANGELOG.md +75 -0
  6. package/README.md +92 -214
  7. package/dist/agents/BaseAgent.d.ts +5 -1
  8. package/dist/agents/BaseAgent.d.ts.map +1 -1
  9. package/dist/agents/BaseAgent.js +32 -17
  10. package/dist/agents/BaseAgent.js.map +1 -1
  11. package/dist/agents/index.js +3 -3
  12. package/dist/agents/index.js.map +1 -1
  13. package/dist/cli/commands/improve/index.d.ts +8 -1
  14. package/dist/cli/commands/improve/index.d.ts.map +1 -1
  15. package/dist/cli/commands/improve/index.js +18 -16
  16. package/dist/cli/commands/improve/index.js.map +1 -1
  17. package/dist/cli/commands/learn/index.d.ts +10 -2
  18. package/dist/cli/commands/learn/index.d.ts.map +1 -1
  19. package/dist/cli/commands/learn/index.js +99 -63
  20. package/dist/cli/commands/learn/index.js.map +1 -1
  21. package/dist/cli/commands/patterns/index.d.ts +8 -1
  22. package/dist/cli/commands/patterns/index.d.ts.map +1 -1
  23. package/dist/cli/commands/patterns/index.js +79 -45
  24. package/dist/cli/commands/patterns/index.js.map +1 -1
  25. package/dist/cli/commands/routing/index.d.ts +5 -0
  26. package/dist/cli/commands/routing/index.d.ts.map +1 -1
  27. package/dist/cli/commands/routing/index.js +11 -10
  28. package/dist/cli/commands/routing/index.js.map +1 -1
  29. package/dist/cli/init/agents.d.ts +1 -1
  30. package/dist/cli/init/agents.js +2 -2
  31. package/dist/cli/init/database-init.d.ts +7 -0
  32. package/dist/cli/init/database-init.d.ts.map +1 -1
  33. package/dist/cli/init/database-init.js +29 -48
  34. package/dist/cli/init/database-init.js.map +1 -1
  35. package/dist/core/memory/HNSWVectorMemory.d.ts +261 -0
  36. package/dist/core/memory/HNSWVectorMemory.d.ts.map +1 -0
  37. package/dist/core/memory/HNSWVectorMemory.js +647 -0
  38. package/dist/core/memory/HNSWVectorMemory.js.map +1 -0
  39. package/dist/core/memory/SwarmMemoryManager.d.ts +7 -0
  40. package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
  41. package/dist/core/memory/SwarmMemoryManager.js +9 -0
  42. package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
  43. package/dist/core/memory/index.d.ts +2 -0
  44. package/dist/core/memory/index.d.ts.map +1 -1
  45. package/dist/core/memory/index.js +11 -1
  46. package/dist/core/memory/index.js.map +1 -1
  47. package/dist/learning/ExplainableLearning.d.ts +191 -0
  48. package/dist/learning/ExplainableLearning.d.ts.map +1 -0
  49. package/dist/learning/ExplainableLearning.js +441 -0
  50. package/dist/learning/ExplainableLearning.js.map +1 -0
  51. package/dist/learning/GossipPatternSharingProtocol.d.ts +228 -0
  52. package/dist/learning/GossipPatternSharingProtocol.d.ts.map +1 -0
  53. package/dist/learning/GossipPatternSharingProtocol.js +590 -0
  54. package/dist/learning/GossipPatternSharingProtocol.js.map +1 -0
  55. package/dist/learning/LearningEngine.d.ts +4 -4
  56. package/dist/learning/LearningEngine.d.ts.map +1 -1
  57. package/dist/learning/LearningEngine.js +20 -13
  58. package/dist/learning/LearningEngine.js.map +1 -1
  59. package/dist/learning/PerformanceOptimizer.d.ts +268 -0
  60. package/dist/learning/PerformanceOptimizer.d.ts.map +1 -0
  61. package/dist/learning/PerformanceOptimizer.js +552 -0
  62. package/dist/learning/PerformanceOptimizer.js.map +1 -0
  63. package/dist/learning/PrivacyManager.d.ts +197 -0
  64. package/dist/learning/PrivacyManager.d.ts.map +1 -0
  65. package/dist/learning/PrivacyManager.js +551 -0
  66. package/dist/learning/PrivacyManager.js.map +1 -0
  67. package/dist/learning/TransferLearningManager.d.ts +212 -0
  68. package/dist/learning/TransferLearningManager.d.ts.map +1 -0
  69. package/dist/learning/TransferLearningManager.js +497 -0
  70. package/dist/learning/TransferLearningManager.js.map +1 -0
  71. package/dist/learning/algorithms/MAMLMetaLearner.d.ts +218 -0
  72. package/dist/learning/algorithms/MAMLMetaLearner.d.ts.map +1 -0
  73. package/dist/learning/algorithms/MAMLMetaLearner.js +532 -0
  74. package/dist/learning/algorithms/MAMLMetaLearner.js.map +1 -0
  75. package/dist/learning/algorithms/index.d.ts +4 -1
  76. package/dist/learning/algorithms/index.d.ts.map +1 -1
  77. package/dist/learning/algorithms/index.js +7 -1
  78. package/dist/learning/algorithms/index.js.map +1 -1
  79. package/dist/learning/index.d.ts +8 -0
  80. package/dist/learning/index.d.ts.map +1 -1
  81. package/dist/learning/index.js +17 -1
  82. package/dist/learning/index.js.map +1 -1
  83. package/dist/mcp/server-instructions.d.ts +1 -1
  84. package/dist/mcp/server-instructions.js +1 -1
  85. package/dist/providers/HybridRouter.d.ts +217 -0
  86. package/dist/providers/HybridRouter.d.ts.map +1 -0
  87. package/dist/providers/HybridRouter.js +679 -0
  88. package/dist/providers/HybridRouter.js.map +1 -0
  89. package/dist/providers/index.d.ts +1 -0
  90. package/dist/providers/index.d.ts.map +1 -1
  91. package/dist/providers/index.js +7 -1
  92. package/dist/providers/index.js.map +1 -1
  93. package/dist/telemetry/LearningTelemetry.d.ts +190 -0
  94. package/dist/telemetry/LearningTelemetry.d.ts.map +1 -0
  95. package/dist/telemetry/LearningTelemetry.js +403 -0
  96. package/dist/telemetry/LearningTelemetry.js.map +1 -0
  97. package/dist/telemetry/index.d.ts +1 -0
  98. package/dist/telemetry/index.d.ts.map +1 -1
  99. package/dist/telemetry/index.js +20 -2
  100. package/dist/telemetry/index.js.map +1 -1
  101. package/dist/telemetry/instrumentation/agent.d.ts +1 -1
  102. package/dist/telemetry/instrumentation/agent.js +1 -1
  103. package/dist/telemetry/instrumentation/index.d.ts +1 -1
  104. package/dist/telemetry/instrumentation/index.js +1 -1
  105. package/dist/utils/math.d.ts +11 -0
  106. package/dist/utils/math.d.ts.map +1 -0
  107. package/dist/utils/math.js +16 -0
  108. package/dist/utils/math.js.map +1 -0
  109. package/docs/reference/agents.md +1 -1
  110. package/docs/reference/skills.md +3 -3
  111. package/docs/reference/usage.md +4 -4
  112. package/package.json +14 -1
@@ -0,0 +1,679 @@
1
+ "use strict";
2
+ /**
3
+ * HybridRouter - Intelligent LLM Provider Router
4
+ *
5
+ * Provides intelligent routing between local (ruvllm) and cloud (Claude, GPT) providers
6
+ * with circuit breakers, cost optimization, and adaptive learning.
7
+ *
8
+ * Features:
9
+ * - Task complexity analysis for optimal routing
10
+ * - Latency-aware provider selection
11
+ * - Cost tracking and savings estimation
12
+ * - Circuit breaker pattern for failing providers
13
+ * - Priority queuing for urgent requests
14
+ * - Learning from routing outcomes
15
+ * - Privacy-first routing for sensitive data
16
+ *
17
+ * @module providers/HybridRouter
18
+ * @version 1.0.0
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.HybridRouter = exports.RoutingStrategy = exports.TaskComplexity = exports.RequestPriority = void 0;
22
+ const ILLMProvider_1 = require("./ILLMProvider");
23
+ const ClaudeProvider_1 = require("./ClaudeProvider");
24
+ const RuvllmProvider_1 = require("./RuvllmProvider");
25
+ const Logger_1 = require("../utils/Logger");
26
+ /**
27
+ * Request priority levels
28
+ */
29
+ var RequestPriority;
30
+ (function (RequestPriority) {
31
+ RequestPriority[RequestPriority["LOW"] = 0] = "LOW";
32
+ RequestPriority[RequestPriority["NORMAL"] = 1] = "NORMAL";
33
+ RequestPriority[RequestPriority["HIGH"] = 2] = "HIGH";
34
+ RequestPriority[RequestPriority["URGENT"] = 3] = "URGENT";
35
+ })(RequestPriority || (exports.RequestPriority = RequestPriority = {}));
36
+ /**
37
+ * Task complexity classification
38
+ */
39
+ var TaskComplexity;
40
+ (function (TaskComplexity) {
41
+ TaskComplexity["SIMPLE"] = "simple";
42
+ TaskComplexity["MODERATE"] = "moderate";
43
+ TaskComplexity["COMPLEX"] = "complex";
44
+ TaskComplexity["VERY_COMPLEX"] = "very_complex"; // Advanced analysis, architectural design
45
+ })(TaskComplexity || (exports.TaskComplexity = TaskComplexity = {}));
46
+ /**
47
+ * Routing strategy
48
+ */
49
+ var RoutingStrategy;
50
+ (function (RoutingStrategy) {
51
+ RoutingStrategy["COST_OPTIMIZED"] = "cost_optimized";
52
+ RoutingStrategy["LATENCY_OPTIMIZED"] = "latency_optimized";
53
+ RoutingStrategy["QUALITY_OPTIMIZED"] = "quality_optimized";
54
+ RoutingStrategy["BALANCED"] = "balanced";
55
+ RoutingStrategy["PRIVACY_FIRST"] = "privacy_first"; // Always use local
56
+ })(RoutingStrategy || (exports.RoutingStrategy = RoutingStrategy = {}));
57
+ /**
58
+ * Circuit breaker state
59
+ */
60
+ var CircuitState;
61
+ (function (CircuitState) {
62
+ CircuitState["CLOSED"] = "closed";
63
+ CircuitState["OPEN"] = "open";
64
+ CircuitState["HALF_OPEN"] = "half_open"; // Testing if provider recovered
65
+ })(CircuitState || (CircuitState = {}));
66
+ /**
67
+ * HybridRouter - Intelligent provider routing implementation
68
+ *
69
+ * Routes LLM requests between local and cloud providers based on:
70
+ * - Task complexity and required capabilities
71
+ * - Latency and cost constraints
72
+ * - Provider health and circuit breaker state
73
+ * - Request priority
74
+ * - Privacy requirements
75
+ */
76
+ class HybridRouter {
77
+ constructor(config = {}) {
78
+ this.logger = Logger_1.Logger.getInstance();
79
+ this.config = {
80
+ name: config.name || 'hybrid-router',
81
+ debug: config.debug ?? false,
82
+ timeout: config.timeout ?? 120000,
83
+ maxRetries: config.maxRetries ?? 2,
84
+ defaultStrategy: config.defaultStrategy ?? RoutingStrategy.BALANCED,
85
+ enableCircuitBreaker: config.enableCircuitBreaker ?? true,
86
+ circuitBreakerThreshold: config.circuitBreakerThreshold ?? 5,
87
+ circuitBreakerTimeout: config.circuitBreakerTimeout ?? 60000,
88
+ maxLocalLatency: config.maxLocalLatency ?? 5000,
89
+ enableLearning: config.enableLearning ?? true,
90
+ privacyKeywords: config.privacyKeywords ?? [
91
+ 'secret', 'password', 'token', 'key', 'credential',
92
+ 'private', 'confidential', 'internal', 'api_key'
93
+ ],
94
+ claude: config.claude,
95
+ ruvllm: config.ruvllm
96
+ };
97
+ this.circuitBreakers = new Map();
98
+ this.routingHistory = [];
99
+ this.isInitialized = false;
100
+ this.totalCost = 0;
101
+ this.requestCount = 0;
102
+ this.localRequestCount = 0;
103
+ this.cloudRequestCount = 0;
104
+ }
105
+ /**
106
+ * Initialize the hybrid router and its providers
107
+ */
108
+ async initialize() {
109
+ if (this.isInitialized) {
110
+ this.logger.warn('HybridRouter already initialized');
111
+ return;
112
+ }
113
+ // Initialize local provider (ruvllm)
114
+ try {
115
+ this.localProvider = new RuvllmProvider_1.RuvllmProvider(this.config.ruvllm ?? {});
116
+ await this.localProvider.initialize();
117
+ this.initCircuitBreaker('local');
118
+ this.logger.info('Local provider (ruvllm) initialized');
119
+ }
120
+ catch (error) {
121
+ this.logger.warn('Failed to initialize local provider', {
122
+ error: error.message
123
+ });
124
+ }
125
+ // Initialize cloud provider (Claude)
126
+ try {
127
+ this.cloudProvider = new ClaudeProvider_1.ClaudeProvider(this.config.claude ?? {});
128
+ await this.cloudProvider.initialize();
129
+ this.initCircuitBreaker('cloud');
130
+ this.logger.info('Cloud provider (Claude) initialized');
131
+ }
132
+ catch (error) {
133
+ this.logger.warn('Failed to initialize cloud provider', {
134
+ error: error.message
135
+ });
136
+ }
137
+ if (!this.localProvider && !this.cloudProvider) {
138
+ throw new ILLMProvider_1.LLMProviderError('Failed to initialize any providers', 'hybrid-router', 'INIT_ERROR', false);
139
+ }
140
+ this.isInitialized = true;
141
+ this.logger.info('HybridRouter initialized', {
142
+ hasLocal: !!this.localProvider,
143
+ hasCloud: !!this.cloudProvider,
144
+ strategy: this.config.defaultStrategy
145
+ });
146
+ }
147
+ /**
148
+ * Complete a prompt with intelligent routing
149
+ */
150
+ async complete(options) {
151
+ this.ensureInitialized();
152
+ const startTime = Date.now();
153
+ // Analyze request and make routing decision
154
+ const decision = this.makeRoutingDecision(options);
155
+ this.logger.debug('Routing decision made', {
156
+ provider: decision.provider,
157
+ reason: decision.reason,
158
+ complexity: decision.complexity
159
+ });
160
+ try {
161
+ let response;
162
+ if (decision.provider === 'local' && this.localProvider) {
163
+ response = await this.executeWithCircuitBreaker('local', () => this.localProvider.complete(options));
164
+ this.localRequestCount++;
165
+ }
166
+ else if (decision.provider === 'cloud' && this.cloudProvider) {
167
+ response = await this.executeWithCircuitBreaker('cloud', () => this.cloudProvider.complete(options));
168
+ this.cloudRequestCount++;
169
+ }
170
+ else {
171
+ throw new ILLMProvider_1.LLMProviderError(`Selected provider (${decision.provider}) not available`, 'hybrid-router', 'PROVIDER_UNAVAILABLE', true);
172
+ }
173
+ this.requestCount++;
174
+ // Track cost
175
+ const actualCost = this.calculateCost(response, decision.provider);
176
+ this.totalCost += actualCost;
177
+ // Record outcome for learning
178
+ if (this.config.enableLearning) {
179
+ this.recordOutcome({
180
+ decision,
181
+ actualCost,
182
+ actualLatency: Date.now() - startTime,
183
+ success: true
184
+ });
185
+ }
186
+ // Add routing metadata to response
187
+ response.metadata = {
188
+ ...response.metadata,
189
+ routingDecision: decision,
190
+ actualCost
191
+ };
192
+ return response;
193
+ }
194
+ catch (error) {
195
+ // Record failure
196
+ if (this.config.enableLearning) {
197
+ this.recordOutcome({
198
+ decision,
199
+ actualCost: 0,
200
+ actualLatency: Date.now() - startTime,
201
+ success: false,
202
+ error: error.message
203
+ });
204
+ }
205
+ // Try fallback if enabled
206
+ if (this.config.enableCircuitBreaker) {
207
+ return this.executeFallback(options, decision.provider);
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+ /**
213
+ * Stream completion with routing
214
+ */
215
+ async *streamComplete(options) {
216
+ this.ensureInitialized();
217
+ const decision = this.makeRoutingDecision(options);
218
+ try {
219
+ if (decision.provider === 'local' && this.localProvider) {
220
+ yield* this.localProvider.streamComplete(options);
221
+ this.localRequestCount++;
222
+ }
223
+ else if (decision.provider === 'cloud' && this.cloudProvider) {
224
+ yield* this.cloudProvider.streamComplete(options);
225
+ this.cloudRequestCount++;
226
+ }
227
+ else {
228
+ throw new ILLMProvider_1.LLMProviderError(`Selected provider (${decision.provider}) not available`, 'hybrid-router', 'PROVIDER_UNAVAILABLE', true);
229
+ }
230
+ this.requestCount++;
231
+ }
232
+ catch (error) {
233
+ throw new ILLMProvider_1.LLMProviderError(`Stream failed: ${error.message}`, 'hybrid-router', 'STREAM_ERROR', true, error);
234
+ }
235
+ }
236
+ /**
237
+ * Generate embeddings (prefer local)
238
+ */
239
+ async embed(options) {
240
+ this.ensureInitialized();
241
+ // Always prefer local for embeddings (privacy + cost)
242
+ if (this.localProvider && this.isProviderAvailable('local')) {
243
+ try {
244
+ return await this.localProvider.embed(options);
245
+ }
246
+ catch (error) {
247
+ this.logger.warn('Local embedding failed, no fallback available', {
248
+ error: error.message
249
+ });
250
+ throw error;
251
+ }
252
+ }
253
+ throw new ILLMProvider_1.LLMProviderError('Embeddings not available. Local provider required.', 'hybrid-router', 'UNSUPPORTED', false);
254
+ }
255
+ /**
256
+ * Count tokens
257
+ */
258
+ async countTokens(options) {
259
+ // Use local provider if available (faster, free)
260
+ if (this.localProvider) {
261
+ return this.localProvider.countTokens(options);
262
+ }
263
+ if (this.cloudProvider) {
264
+ return this.cloudProvider.countTokens(options);
265
+ }
266
+ throw new ILLMProvider_1.LLMProviderError('No provider available for token counting', 'hybrid-router', 'PROVIDER_UNAVAILABLE', false);
267
+ }
268
+ /**
269
+ * Health check all providers
270
+ */
271
+ async healthCheck() {
272
+ const checks = [];
273
+ if (this.localProvider) {
274
+ try {
275
+ const status = await this.localProvider.healthCheck();
276
+ checks.push({ provider: 'local', status });
277
+ }
278
+ catch (error) {
279
+ checks.push({
280
+ provider: 'local',
281
+ status: {
282
+ healthy: false,
283
+ error: error.message,
284
+ timestamp: new Date()
285
+ }
286
+ });
287
+ }
288
+ }
289
+ if (this.cloudProvider) {
290
+ try {
291
+ const status = await this.cloudProvider.healthCheck();
292
+ checks.push({ provider: 'cloud', status });
293
+ }
294
+ catch (error) {
295
+ checks.push({
296
+ provider: 'cloud',
297
+ status: {
298
+ healthy: false,
299
+ error: error.message,
300
+ timestamp: new Date()
301
+ }
302
+ });
303
+ }
304
+ }
305
+ const healthyCount = checks.filter(c => c.status.healthy).length;
306
+ return {
307
+ healthy: healthyCount > 0,
308
+ timestamp: new Date(),
309
+ metadata: {
310
+ providers: checks,
311
+ circuitBreakers: Object.fromEntries(this.circuitBreakers),
312
+ requestCount: this.requestCount,
313
+ localRequests: this.localRequestCount,
314
+ cloudRequests: this.cloudRequestCount
315
+ }
316
+ };
317
+ }
318
+ /**
319
+ * Get metadata (aggregated from all providers)
320
+ */
321
+ getMetadata() {
322
+ const metadata = [];
323
+ if (this.localProvider) {
324
+ metadata.push(this.localProvider.getMetadata());
325
+ }
326
+ if (this.cloudProvider) {
327
+ metadata.push(this.cloudProvider.getMetadata());
328
+ }
329
+ return {
330
+ name: 'hybrid-router',
331
+ version: '1.0.0',
332
+ models: [...new Set(metadata.flatMap(m => m.models))],
333
+ capabilities: {
334
+ streaming: metadata.some(m => m.capabilities.streaming),
335
+ caching: metadata.some(m => m.capabilities.caching),
336
+ embeddings: metadata.some(m => m.capabilities.embeddings),
337
+ vision: metadata.some(m => m.capabilities.vision)
338
+ },
339
+ costs: {
340
+ inputPerMillion: Math.min(...metadata.map(m => m.costs.inputPerMillion)),
341
+ outputPerMillion: Math.min(...metadata.map(m => m.costs.outputPerMillion))
342
+ },
343
+ location: 'cloud' // Hybrid, but defaults to cloud
344
+ };
345
+ }
346
+ /**
347
+ * Shutdown all providers
348
+ */
349
+ async shutdown() {
350
+ const shutdownPromises = [];
351
+ if (this.localProvider) {
352
+ shutdownPromises.push(this.localProvider.shutdown());
353
+ }
354
+ if (this.cloudProvider) {
355
+ shutdownPromises.push(this.cloudProvider.shutdown());
356
+ }
357
+ await Promise.allSettled(shutdownPromises);
358
+ this.isInitialized = false;
359
+ this.logger.info('HybridRouter shutdown', {
360
+ totalRequests: this.requestCount,
361
+ totalCost: this.totalCost
362
+ });
363
+ }
364
+ /**
365
+ * Track cost (aggregated across providers)
366
+ */
367
+ trackCost(usage) {
368
+ // This is handled internally per request
369
+ return 0;
370
+ }
371
+ /**
372
+ * Get cost savings report
373
+ */
374
+ getCostSavingsReport() {
375
+ // Estimate what cloud cost would have been for all requests
376
+ const cloudCostPerRequest = 0.01; // Rough estimate
377
+ const estimatedCloudCost = this.requestCount * cloudCostPerRequest;
378
+ const savings = estimatedCloudCost - this.totalCost;
379
+ const savingsPercentage = estimatedCloudCost > 0
380
+ ? (savings / estimatedCloudCost) * 100
381
+ : 0;
382
+ return {
383
+ totalRequests: this.requestCount,
384
+ localRequests: this.localRequestCount,
385
+ cloudRequests: this.cloudRequestCount,
386
+ totalCost: this.totalCost,
387
+ estimatedCloudCost,
388
+ savings,
389
+ savingsPercentage
390
+ };
391
+ }
392
+ /**
393
+ * Get routing statistics
394
+ */
395
+ getRoutingStats() {
396
+ const successCount = this.routingHistory.filter(o => o.success).length;
397
+ const localOutcomes = this.routingHistory.filter(o => o.decision.provider === 'local');
398
+ const cloudOutcomes = this.routingHistory.filter(o => o.decision.provider === 'cloud');
399
+ const avgLocalLatency = localOutcomes.length > 0
400
+ ? localOutcomes.reduce((sum, o) => sum + o.actualLatency, 0) / localOutcomes.length
401
+ : 0;
402
+ const avgCloudLatency = cloudOutcomes.length > 0
403
+ ? cloudOutcomes.reduce((sum, o) => sum + o.actualLatency, 0) / cloudOutcomes.length
404
+ : 0;
405
+ return {
406
+ totalDecisions: this.routingHistory.length,
407
+ localDecisions: localOutcomes.length,
408
+ cloudDecisions: cloudOutcomes.length,
409
+ averageLocalLatency: avgLocalLatency,
410
+ averageCloudLatency: avgCloudLatency,
411
+ successRate: this.routingHistory.length > 0
412
+ ? (successCount / this.routingHistory.length) * 100
413
+ : 0
414
+ };
415
+ }
416
+ /**
417
+ * Make intelligent routing decision
418
+ */
419
+ makeRoutingDecision(options, strategy, priority = RequestPriority.NORMAL) {
420
+ const activeStrategy = strategy || this.config.defaultStrategy;
421
+ const complexity = this.analyzeComplexity(options);
422
+ // Check for privacy-sensitive content
423
+ if (this.containsPrivacySensitiveData(options)) {
424
+ return this.createDecision('local', 'ruvllm', 'Privacy-sensitive data detected', complexity, priority);
425
+ }
426
+ // PRIVACY_FIRST strategy
427
+ if (activeStrategy === RoutingStrategy.PRIVACY_FIRST && this.isProviderAvailable('local')) {
428
+ return this.createDecision('local', 'ruvllm', 'Privacy-first strategy', complexity, priority);
429
+ }
430
+ // Check circuit breakers
431
+ const localAvailable = this.isProviderAvailable('local');
432
+ const cloudAvailable = this.isProviderAvailable('cloud');
433
+ if (!localAvailable && !cloudAvailable) {
434
+ throw new ILLMProvider_1.LLMProviderError('No providers available (circuit breakers open)', 'hybrid-router', 'NO_PROVIDERS', true);
435
+ }
436
+ // COST_OPTIMIZED strategy
437
+ if (activeStrategy === RoutingStrategy.COST_OPTIMIZED) {
438
+ if (localAvailable) {
439
+ return this.createDecision('local', 'ruvllm', 'Cost optimization (local is free)', complexity, priority);
440
+ }
441
+ }
442
+ // LATENCY_OPTIMIZED strategy
443
+ if (activeStrategy === RoutingStrategy.LATENCY_OPTIMIZED) {
444
+ // Use historical data to choose faster provider
445
+ const stats = this.getRoutingStats();
446
+ if (stats.averageLocalLatency > 0 && stats.averageCloudLatency > 0) {
447
+ if (stats.averageLocalLatency < stats.averageCloudLatency && localAvailable) {
448
+ return this.createDecision('local', 'ruvllm', 'Latency optimization (local faster)', complexity, priority);
449
+ }
450
+ }
451
+ }
452
+ // QUALITY_OPTIMIZED or BALANCED strategy
453
+ // Route based on complexity
454
+ switch (complexity) {
455
+ case TaskComplexity.SIMPLE:
456
+ case TaskComplexity.MODERATE:
457
+ // Simple tasks → local
458
+ if (localAvailable) {
459
+ return this.createDecision('local', 'ruvllm', `${complexity} task suitable for local`, complexity, priority);
460
+ }
461
+ break;
462
+ case TaskComplexity.COMPLEX:
463
+ // Complex tasks → prefer cloud for quality, but local if cost-sensitive
464
+ if (activeStrategy === RoutingStrategy.BALANCED && localAvailable) {
465
+ return this.createDecision('local', 'ruvllm', 'Balanced strategy, trying local first', complexity, priority);
466
+ }
467
+ if (cloudAvailable) {
468
+ return this.createDecision('cloud', 'claude', `${complexity} task needs cloud quality`, complexity, priority);
469
+ }
470
+ break;
471
+ case TaskComplexity.VERY_COMPLEX:
472
+ // Very complex → always cloud if available
473
+ if (cloudAvailable) {
474
+ return this.createDecision('cloud', 'claude', `${complexity} task requires cloud`, complexity, priority);
475
+ }
476
+ break;
477
+ }
478
+ // Fallback: use whatever is available
479
+ if (cloudAvailable) {
480
+ return this.createDecision('cloud', 'claude', 'Fallback to cloud', complexity, priority);
481
+ }
482
+ if (localAvailable) {
483
+ return this.createDecision('local', 'ruvllm', 'Fallback to local', complexity, priority);
484
+ }
485
+ throw new ILLMProvider_1.LLMProviderError('No providers available', 'hybrid-router', 'NO_PROVIDERS', true);
486
+ }
487
+ /**
488
+ * Analyze task complexity
489
+ */
490
+ analyzeComplexity(options) {
491
+ const maxTokens = options.maxTokens || 0;
492
+ const messageCount = options.messages.length;
493
+ const totalContent = options.messages
494
+ .map(m => typeof m.content === 'string' ? m.content : m.content.map(c => c.text || '').join(''))
495
+ .join(' ');
496
+ const contentLength = totalContent.length;
497
+ // Complexity heuristics
498
+ const codePatterns = /```|function|class|import|export|const|let|var/gi;
499
+ const hasCode = codePatterns.test(totalContent);
500
+ const complexKeywords = /architect|design|analyze|optimize|refactor|debug/gi;
501
+ const hasComplexKeywords = complexKeywords.test(totalContent);
502
+ // Scoring
503
+ let score = 0;
504
+ if (contentLength > 5000)
505
+ score += 2;
506
+ else if (contentLength > 2000)
507
+ score += 1;
508
+ if (maxTokens > 4000)
509
+ score += 2;
510
+ else if (maxTokens > 1000)
511
+ score += 1;
512
+ if (messageCount > 5)
513
+ score += 1;
514
+ if (hasCode)
515
+ score += 1;
516
+ if (hasComplexKeywords)
517
+ score += 1;
518
+ // Classification
519
+ if (score >= 6)
520
+ return TaskComplexity.VERY_COMPLEX;
521
+ if (score >= 4)
522
+ return TaskComplexity.COMPLEX;
523
+ if (score >= 2)
524
+ return TaskComplexity.MODERATE;
525
+ return TaskComplexity.SIMPLE;
526
+ }
527
+ /**
528
+ * Check for privacy-sensitive data
529
+ */
530
+ containsPrivacySensitiveData(options) {
531
+ const allContent = options.messages
532
+ .map(m => typeof m.content === 'string' ? m.content : m.content.map(c => c.text || '').join(''))
533
+ .join(' ')
534
+ .toLowerCase();
535
+ return this.config.privacyKeywords.some(keyword => allContent.includes(keyword.toLowerCase()));
536
+ }
537
+ /**
538
+ * Check if provider is available (circuit breaker check)
539
+ */
540
+ isProviderAvailable(provider) {
541
+ if (provider === 'local' && !this.localProvider)
542
+ return false;
543
+ if (provider === 'cloud' && !this.cloudProvider)
544
+ return false;
545
+ if (!this.config.enableCircuitBreaker)
546
+ return true;
547
+ const breaker = this.circuitBreakers.get(provider);
548
+ if (!breaker)
549
+ return true;
550
+ if (breaker.state === CircuitState.OPEN) {
551
+ // Check if timeout expired
552
+ if (breaker.nextAttemptTime && new Date() >= breaker.nextAttemptTime) {
553
+ breaker.state = CircuitState.HALF_OPEN;
554
+ this.logger.info(`Circuit breaker for ${provider} entering HALF_OPEN state`);
555
+ return true;
556
+ }
557
+ return false;
558
+ }
559
+ return true;
560
+ }
561
+ /**
562
+ * Execute with circuit breaker protection
563
+ */
564
+ async executeWithCircuitBreaker(provider, operation) {
565
+ const breaker = this.circuitBreakers.get(provider);
566
+ if (!breaker) {
567
+ return operation();
568
+ }
569
+ if (breaker.state === CircuitState.OPEN) {
570
+ throw new ILLMProvider_1.LLMProviderError(`Circuit breaker OPEN for ${provider}`, 'hybrid-router', 'CIRCUIT_OPEN', true);
571
+ }
572
+ try {
573
+ const result = await operation();
574
+ // Success
575
+ breaker.successCount++;
576
+ breaker.failureCount = 0;
577
+ if (breaker.state === CircuitState.HALF_OPEN) {
578
+ // Recovered, close circuit
579
+ breaker.state = CircuitState.CLOSED;
580
+ this.logger.info(`Circuit breaker for ${provider} closed (recovered)`);
581
+ }
582
+ return result;
583
+ }
584
+ catch (error) {
585
+ // Failure
586
+ breaker.failureCount++;
587
+ breaker.lastFailureTime = new Date();
588
+ if (breaker.failureCount >= this.config.circuitBreakerThreshold) {
589
+ breaker.state = CircuitState.OPEN;
590
+ breaker.nextAttemptTime = new Date(Date.now() + this.config.circuitBreakerTimeout);
591
+ this.logger.error(`Circuit breaker for ${provider} opened after ${breaker.failureCount} failures`);
592
+ }
593
+ throw error;
594
+ }
595
+ }
596
+ /**
597
+ * Execute fallback to alternative provider
598
+ */
599
+ async executeFallback(options, failedProvider) {
600
+ const fallbackProvider = failedProvider === 'local' ? 'cloud' : 'local';
601
+ this.logger.warn(`Attempting fallback to ${fallbackProvider} after ${failedProvider} failure`);
602
+ if (!this.isProviderAvailable(fallbackProvider)) {
603
+ throw new ILLMProvider_1.LLMProviderError(`Fallback provider ${fallbackProvider} not available`, 'hybrid-router', 'FALLBACK_FAILED', false);
604
+ }
605
+ if (fallbackProvider === 'local' && this.localProvider) {
606
+ return this.localProvider.complete(options);
607
+ }
608
+ if (fallbackProvider === 'cloud' && this.cloudProvider) {
609
+ return this.cloudProvider.complete(options);
610
+ }
611
+ throw new ILLMProvider_1.LLMProviderError('Fallback failed: no alternative provider', 'hybrid-router', 'FALLBACK_FAILED', false);
612
+ }
613
+ /**
614
+ * Initialize circuit breaker for a provider
615
+ */
616
+ initCircuitBreaker(provider) {
617
+ this.circuitBreakers.set(provider, {
618
+ state: CircuitState.CLOSED,
619
+ failureCount: 0,
620
+ successCount: 0
621
+ });
622
+ }
623
+ /**
624
+ * Create routing decision object
625
+ */
626
+ createDecision(provider, providerName, reason, complexity, priority) {
627
+ return {
628
+ provider,
629
+ providerName,
630
+ reason,
631
+ estimatedCost: provider === 'local' ? 0 : 0.01, // Rough estimate
632
+ estimatedLatency: provider === 'local' ? 2000 : 1000, // Rough estimate
633
+ complexity,
634
+ priority,
635
+ timestamp: new Date()
636
+ };
637
+ }
638
+ /**
639
+ * Calculate actual cost from response
640
+ */
641
+ calculateCost(response, provider) {
642
+ if (provider === 'local') {
643
+ return 0; // Local is free
644
+ }
645
+ // Use cloud provider's tracking
646
+ if (this.cloudProvider) {
647
+ return this.cloudProvider.trackCost(response.usage);
648
+ }
649
+ return 0;
650
+ }
651
+ /**
652
+ * Record routing outcome for learning
653
+ */
654
+ recordOutcome(outcome) {
655
+ this.routingHistory.push(outcome);
656
+ // Keep only last 1000 outcomes
657
+ if (this.routingHistory.length > 1000) {
658
+ this.routingHistory.shift();
659
+ }
660
+ if (this.config.debug) {
661
+ this.logger.debug('Routing outcome recorded', {
662
+ provider: outcome.decision.provider,
663
+ success: outcome.success,
664
+ latency: outcome.actualLatency,
665
+ cost: outcome.actualCost
666
+ });
667
+ }
668
+ }
669
+ /**
670
+ * Ensure router is initialized
671
+ */
672
+ ensureInitialized() {
673
+ if (!this.isInitialized) {
674
+ throw new ILLMProvider_1.LLMProviderError('HybridRouter not initialized. Call initialize() first.', 'hybrid-router', 'NOT_INITIALIZED', false);
675
+ }
676
+ }
677
+ }
678
+ exports.HybridRouter = HybridRouter;
679
+ //# sourceMappingURL=HybridRouter.js.map