@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,679 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview SONARouter - Specialized Optimized Network Architecture Router
|
|
3
|
+
* @module @skillsmith/core/routing/SONARouter
|
|
4
|
+
* @see SMI-1521: SONA routing for MCP tool optimization
|
|
5
|
+
*
|
|
6
|
+
* Routes MCP tool requests through an 8-expert MoE (Mixture of Experts)
|
|
7
|
+
* network to optimize tool execution based on accuracy requirements,
|
|
8
|
+
* latency constraints, and load distribution.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - O(1) routing decisions with LRU caching
|
|
12
|
+
* - V3 MoE integration with fallback to local scoring
|
|
13
|
+
* - Adaptive load balancing across experts
|
|
14
|
+
* - Health monitoring and circuit breaker patterns
|
|
15
|
+
* - Feature flags for gradual rollout
|
|
16
|
+
* - Prometheus-compatible metrics
|
|
17
|
+
*
|
|
18
|
+
* Performance targets:
|
|
19
|
+
* - 2.8-4.4x speed improvement
|
|
20
|
+
* - <5ms routing overhead (P95)
|
|
21
|
+
* - >60% cache hit rate
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const router = new SONARouter()
|
|
26
|
+
* await router.initialize()
|
|
27
|
+
*
|
|
28
|
+
* const decision = await router.route({
|
|
29
|
+
* requestId: 'req-123',
|
|
30
|
+
* tool: 'search',
|
|
31
|
+
* arguments: { query: 'testing skills' },
|
|
32
|
+
* timestamp: new Date(),
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* console.log(decision.expertId) // 'accuracy-semantic'
|
|
36
|
+
* console.log(decision.confidence) // 0.92
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
import { DEFAULT_SONA_CONFIG, TOOL_WEIGHTS } from './types.js';
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// LRU Cache Implementation
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Simple LRU cache for routing decisions
|
|
45
|
+
*/
|
|
46
|
+
class LRUCache {
|
|
47
|
+
cache;
|
|
48
|
+
maxSize;
|
|
49
|
+
ttlMs;
|
|
50
|
+
constructor(maxSize, ttlMs) {
|
|
51
|
+
this.cache = new Map();
|
|
52
|
+
this.maxSize = maxSize;
|
|
53
|
+
this.ttlMs = ttlMs;
|
|
54
|
+
}
|
|
55
|
+
get(key) {
|
|
56
|
+
const entry = this.cache.get(key);
|
|
57
|
+
if (!entry)
|
|
58
|
+
return null;
|
|
59
|
+
// Check TTL
|
|
60
|
+
if (Date.now() - entry.timestamp > this.ttlMs) {
|
|
61
|
+
this.cache.delete(key);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// Move to end (most recently used)
|
|
65
|
+
this.cache.delete(key);
|
|
66
|
+
this.cache.set(key, entry);
|
|
67
|
+
return entry.value;
|
|
68
|
+
}
|
|
69
|
+
set(key, value) {
|
|
70
|
+
// Remove oldest if at capacity
|
|
71
|
+
if (this.cache.size >= this.maxSize) {
|
|
72
|
+
const firstKey = this.cache.keys().next().value;
|
|
73
|
+
if (firstKey !== undefined) {
|
|
74
|
+
this.cache.delete(firstKey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.cache.set(key, { value, timestamp: Date.now() });
|
|
78
|
+
}
|
|
79
|
+
clear() {
|
|
80
|
+
this.cache.clear();
|
|
81
|
+
}
|
|
82
|
+
get size() {
|
|
83
|
+
return this.cache.size;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Metrics Collector
|
|
88
|
+
// ============================================================================
|
|
89
|
+
/**
|
|
90
|
+
* Simple metrics collector for SONA routing
|
|
91
|
+
*/
|
|
92
|
+
class MetricsCollector {
|
|
93
|
+
totalRequests = 0;
|
|
94
|
+
requestsByTool = {};
|
|
95
|
+
requestsByExpert = {};
|
|
96
|
+
cacheHits = 0;
|
|
97
|
+
cacheMisses = 0;
|
|
98
|
+
totalRoutingTimeMs = 0;
|
|
99
|
+
totalExecutionTimeMs = 0;
|
|
100
|
+
errorCount = 0;
|
|
101
|
+
errorsByType = {};
|
|
102
|
+
recordRouting(tool, expertId, routingTimeMs, cacheHit) {
|
|
103
|
+
this.totalRequests++;
|
|
104
|
+
this.requestsByTool[tool] = (this.requestsByTool[tool] || 0) + 1;
|
|
105
|
+
this.requestsByExpert[expertId] = (this.requestsByExpert[expertId] || 0) + 1;
|
|
106
|
+
this.totalRoutingTimeMs += routingTimeMs;
|
|
107
|
+
if (cacheHit) {
|
|
108
|
+
this.cacheHits++;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.cacheMisses++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
recordExecution(executionTimeMs) {
|
|
115
|
+
this.totalExecutionTimeMs += executionTimeMs;
|
|
116
|
+
}
|
|
117
|
+
recordError(errorType) {
|
|
118
|
+
this.errorCount++;
|
|
119
|
+
this.errorsByType[errorType] = (this.errorsByType[errorType] || 0) + 1;
|
|
120
|
+
}
|
|
121
|
+
getMetrics() {
|
|
122
|
+
const totalCache = this.cacheHits + this.cacheMisses;
|
|
123
|
+
const avgRoutingMs = this.totalRequests > 0 ? this.totalRoutingTimeMs / this.totalRequests : 0;
|
|
124
|
+
const avgExecutionMs = this.totalRequests > 0 ? this.totalExecutionTimeMs / this.totalRequests : 0;
|
|
125
|
+
return {
|
|
126
|
+
totalRequests: this.totalRequests,
|
|
127
|
+
requestsByTool: this.requestsByTool,
|
|
128
|
+
requestsByExpert: this.requestsByExpert,
|
|
129
|
+
cache: {
|
|
130
|
+
hits: this.cacheHits,
|
|
131
|
+
misses: this.cacheMisses,
|
|
132
|
+
hitRate: totalCache > 0 ? this.cacheHits / totalCache : 0,
|
|
133
|
+
},
|
|
134
|
+
errors: {
|
|
135
|
+
total: this.errorCount,
|
|
136
|
+
byType: this.errorsByType,
|
|
137
|
+
byExpert: {},
|
|
138
|
+
},
|
|
139
|
+
speedImprovement: {
|
|
140
|
+
baselineMs: 100, // Baseline without SONA
|
|
141
|
+
currentMs: avgRoutingMs + avgExecutionMs,
|
|
142
|
+
improvementRatio: avgRoutingMs + avgExecutionMs > 0 ? 100 / (avgRoutingMs + avgExecutionMs) : 1,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
reset() {
|
|
147
|
+
this.totalRequests = 0;
|
|
148
|
+
this.requestsByTool = {};
|
|
149
|
+
this.requestsByExpert = {};
|
|
150
|
+
this.cacheHits = 0;
|
|
151
|
+
this.cacheMisses = 0;
|
|
152
|
+
this.totalRoutingTimeMs = 0;
|
|
153
|
+
this.totalExecutionTimeMs = 0;
|
|
154
|
+
this.errorCount = 0;
|
|
155
|
+
this.errorsByType = {};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Main SONARouter Class
|
|
160
|
+
// ============================================================================
|
|
161
|
+
/**
|
|
162
|
+
* SONARouter routes MCP tool requests through an 8-expert MoE network.
|
|
163
|
+
*
|
|
164
|
+
* The router uses a weighted scoring algorithm to select the optimal expert
|
|
165
|
+
* for each request based on tool-specific weight profiles and real-time
|
|
166
|
+
* expert health/load status.
|
|
167
|
+
*
|
|
168
|
+
* When V3 MoE integration is enabled, it leverages Claude-Flow's neural
|
|
169
|
+
* routing for improved accuracy. Otherwise, it uses the local scoring algorithm.
|
|
170
|
+
*/
|
|
171
|
+
export class SONARouter {
|
|
172
|
+
config;
|
|
173
|
+
experts;
|
|
174
|
+
expertStatus;
|
|
175
|
+
cache;
|
|
176
|
+
metrics;
|
|
177
|
+
v3MoE = null;
|
|
178
|
+
v3SONA = null;
|
|
179
|
+
initialized = false;
|
|
180
|
+
healthCheckInterval = null;
|
|
181
|
+
/**
|
|
182
|
+
* Create a new SONARouter instance.
|
|
183
|
+
*
|
|
184
|
+
* @param config - Router configuration
|
|
185
|
+
*/
|
|
186
|
+
constructor(config = {}) {
|
|
187
|
+
this.config = {
|
|
188
|
+
...DEFAULT_SONA_CONFIG,
|
|
189
|
+
...config,
|
|
190
|
+
fallback: {
|
|
191
|
+
...DEFAULT_SONA_CONFIG.fallback,
|
|
192
|
+
...config.fallback,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
// Initialize expert registry
|
|
196
|
+
this.experts = new Map();
|
|
197
|
+
this.expertStatus = new Map();
|
|
198
|
+
for (const expert of this.config.experts) {
|
|
199
|
+
this.experts.set(expert.id, expert);
|
|
200
|
+
this.expertStatus.set(expert.id, this.createInitialStatus(expert.id));
|
|
201
|
+
}
|
|
202
|
+
// Initialize cache and metrics
|
|
203
|
+
this.cache = new LRUCache(this.config.cacheMaxSize, this.config.cacheTtlMs);
|
|
204
|
+
this.metrics = new MetricsCollector();
|
|
205
|
+
}
|
|
206
|
+
// ==========================================================================
|
|
207
|
+
// Initialization
|
|
208
|
+
// ==========================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Initialize the router with V3 MoE integration.
|
|
211
|
+
*
|
|
212
|
+
* Attempts to load V3 MoERouter and SONAOptimizer.
|
|
213
|
+
* Falls back to local scoring if V3 is unavailable.
|
|
214
|
+
*/
|
|
215
|
+
async initialize() {
|
|
216
|
+
if (this.initialized) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Try to load V3 MoE if enabled
|
|
220
|
+
if (this.config.useV3MoE !== false) {
|
|
221
|
+
await this.initializeV3MoE();
|
|
222
|
+
}
|
|
223
|
+
// Start health check loop
|
|
224
|
+
if (this.config.healthCheckIntervalMs > 0) {
|
|
225
|
+
this.startHealthChecks();
|
|
226
|
+
}
|
|
227
|
+
this.initialized = true;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if router is initialized
|
|
231
|
+
*/
|
|
232
|
+
isInitialized() {
|
|
233
|
+
return this.initialized;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if V3 MoE is being used
|
|
237
|
+
*/
|
|
238
|
+
isUsingV3MoE() {
|
|
239
|
+
return this.v3MoE !== null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Shutdown the router and cleanup resources
|
|
243
|
+
*/
|
|
244
|
+
async shutdown() {
|
|
245
|
+
if (this.healthCheckInterval) {
|
|
246
|
+
clearInterval(this.healthCheckInterval);
|
|
247
|
+
this.healthCheckInterval = null;
|
|
248
|
+
}
|
|
249
|
+
this.cache.clear();
|
|
250
|
+
this.initialized = false;
|
|
251
|
+
}
|
|
252
|
+
// ==========================================================================
|
|
253
|
+
// Routing
|
|
254
|
+
// ==========================================================================
|
|
255
|
+
/**
|
|
256
|
+
* Route a tool request to the optimal expert.
|
|
257
|
+
*
|
|
258
|
+
* @param request - Tool request to route
|
|
259
|
+
* @returns Routing decision with selected expert and confidence
|
|
260
|
+
*/
|
|
261
|
+
async route(request) {
|
|
262
|
+
this.ensureInitialized();
|
|
263
|
+
const startTime = Date.now();
|
|
264
|
+
// Check cache first
|
|
265
|
+
if (this.config.enableCache && request.priority !== 'high') {
|
|
266
|
+
const cacheKey = this.generateCacheKey(request);
|
|
267
|
+
const cached = this.cache.get(cacheKey);
|
|
268
|
+
if (cached) {
|
|
269
|
+
const decision = { ...cached, cacheHit: true };
|
|
270
|
+
this.metrics.recordRouting(request.tool, decision.expertId, Date.now() - startTime, true);
|
|
271
|
+
return decision;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Get eligible experts
|
|
275
|
+
const eligible = this.getEligibleExperts(request);
|
|
276
|
+
if (eligible.length === 0) {
|
|
277
|
+
return this.createFallbackDecision(request, 'NO_ELIGIBLE_EXPERTS', startTime);
|
|
278
|
+
}
|
|
279
|
+
// Score and select expert
|
|
280
|
+
const scoredExperts = this.scoreExperts(eligible, request);
|
|
281
|
+
const decision = this.selectBestExpert(scoredExperts, request, startTime);
|
|
282
|
+
// Cache the decision
|
|
283
|
+
if (this.config.enableCache) {
|
|
284
|
+
const cacheKey = this.generateCacheKey(request);
|
|
285
|
+
this.cache.set(cacheKey, decision);
|
|
286
|
+
}
|
|
287
|
+
// Record metrics
|
|
288
|
+
this.metrics.recordRouting(request.tool, decision.expertId, decision.decisionTimeMs, false);
|
|
289
|
+
return decision;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Execute a tool request with SONA routing.
|
|
293
|
+
*
|
|
294
|
+
* Routes the request, executes via the selected expert, and records outcome.
|
|
295
|
+
*
|
|
296
|
+
* @param request - Tool request to execute
|
|
297
|
+
* @param executor - Function to execute the tool with given expert
|
|
298
|
+
* @returns Tool response with execution metadata
|
|
299
|
+
*/
|
|
300
|
+
async executeWithRouting(request, executor) {
|
|
301
|
+
const routingStart = Date.now();
|
|
302
|
+
// Route the request
|
|
303
|
+
const decision = await this.route(request);
|
|
304
|
+
const routingTimeMs = Date.now() - routingStart;
|
|
305
|
+
// Execute with selected expert
|
|
306
|
+
const executionStart = Date.now();
|
|
307
|
+
try {
|
|
308
|
+
const data = await executor(decision.expertId, request);
|
|
309
|
+
const executionTimeMs = Date.now() - executionStart;
|
|
310
|
+
// Record success
|
|
311
|
+
this.recordOutcome(request, decision.expertId, true);
|
|
312
|
+
this.metrics.recordExecution(executionTimeMs);
|
|
313
|
+
return {
|
|
314
|
+
requestId: request.requestId,
|
|
315
|
+
success: true,
|
|
316
|
+
data,
|
|
317
|
+
meta: {
|
|
318
|
+
expertId: decision.expertId,
|
|
319
|
+
totalTimeMs: routingTimeMs + executionTimeMs,
|
|
320
|
+
routingTimeMs,
|
|
321
|
+
executionTimeMs,
|
|
322
|
+
cacheHit: decision.cacheHit ?? false,
|
|
323
|
+
usedFallback: decision.expertId === 'direct-fallback',
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
const executionTimeMs = Date.now() - executionStart;
|
|
329
|
+
// Record failure
|
|
330
|
+
this.recordOutcome(request, decision.expertId, false);
|
|
331
|
+
this.metrics.recordError(error instanceof Error ? error.name : 'UnknownError');
|
|
332
|
+
// Try fallback if enabled
|
|
333
|
+
if (this.config.fallback.enabled && decision.expertId !== 'direct-fallback') {
|
|
334
|
+
try {
|
|
335
|
+
const fallbackData = await executor('direct-fallback', request);
|
|
336
|
+
return {
|
|
337
|
+
requestId: request.requestId,
|
|
338
|
+
success: true,
|
|
339
|
+
data: fallbackData,
|
|
340
|
+
meta: {
|
|
341
|
+
expertId: 'direct-fallback',
|
|
342
|
+
totalTimeMs: Date.now() - routingStart,
|
|
343
|
+
routingTimeMs,
|
|
344
|
+
executionTimeMs: Date.now() - executionStart - executionTimeMs,
|
|
345
|
+
cacheHit: false,
|
|
346
|
+
usedFallback: true,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Fallback also failed
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
requestId: request.requestId,
|
|
356
|
+
success: false,
|
|
357
|
+
error: {
|
|
358
|
+
code: error instanceof Error ? error.name : 'EXECUTION_ERROR',
|
|
359
|
+
message: error instanceof Error ? error.message : String(error),
|
|
360
|
+
},
|
|
361
|
+
meta: {
|
|
362
|
+
expertId: decision.expertId,
|
|
363
|
+
totalTimeMs: routingTimeMs + executionTimeMs,
|
|
364
|
+
routingTimeMs,
|
|
365
|
+
executionTimeMs,
|
|
366
|
+
cacheHit: decision.cacheHit ?? false,
|
|
367
|
+
usedFallback: false,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// ==========================================================================
|
|
373
|
+
// Expert Management
|
|
374
|
+
// ==========================================================================
|
|
375
|
+
/**
|
|
376
|
+
* Get current status of all experts
|
|
377
|
+
*/
|
|
378
|
+
getExpertStatus() {
|
|
379
|
+
return Array.from(this.expertStatus.values());
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get status of a specific expert
|
|
383
|
+
*/
|
|
384
|
+
getExpert(expertId) {
|
|
385
|
+
return this.experts.get(expertId);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Update expert health status
|
|
389
|
+
*/
|
|
390
|
+
updateExpertHealth(expertId, state, load) {
|
|
391
|
+
const status = this.expertStatus.get(expertId);
|
|
392
|
+
if (status) {
|
|
393
|
+
status.state = state;
|
|
394
|
+
if (load !== undefined) {
|
|
395
|
+
status.load = load;
|
|
396
|
+
}
|
|
397
|
+
status.lastHealthCheck = new Date();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// ==========================================================================
|
|
401
|
+
// Metrics
|
|
402
|
+
// ==========================================================================
|
|
403
|
+
/**
|
|
404
|
+
* Get current routing metrics
|
|
405
|
+
*/
|
|
406
|
+
getMetrics() {
|
|
407
|
+
return this.metrics.getMetrics();
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Reset metrics
|
|
411
|
+
*/
|
|
412
|
+
resetMetrics() {
|
|
413
|
+
this.metrics.reset();
|
|
414
|
+
}
|
|
415
|
+
// ==========================================================================
|
|
416
|
+
// Private Methods
|
|
417
|
+
// ==========================================================================
|
|
418
|
+
ensureInitialized() {
|
|
419
|
+
if (!this.initialized) {
|
|
420
|
+
throw new Error('SONARouter not initialized. Call initialize() first.');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
createInitialStatus(expertId) {
|
|
424
|
+
return {
|
|
425
|
+
id: expertId,
|
|
426
|
+
state: 'healthy',
|
|
427
|
+
load: 0,
|
|
428
|
+
activeRequests: 0,
|
|
429
|
+
successRate: 1.0,
|
|
430
|
+
p95LatencyMs: 0,
|
|
431
|
+
lastHealthCheck: new Date(),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async initializeV3MoE() {
|
|
435
|
+
try {
|
|
436
|
+
// Dynamic import of V3 MoE router
|
|
437
|
+
const moeModule = await import('claude-flow/v3/@claude-flow/cli/dist/src/ruvector/moe-router.js');
|
|
438
|
+
this.v3MoE = moeModule.getMoERouter();
|
|
439
|
+
await this.v3MoE.initialize();
|
|
440
|
+
// Dynamic import of V3 SONA optimizer
|
|
441
|
+
const sonaModule = await import('claude-flow/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.js');
|
|
442
|
+
const sonaOptimizer = await sonaModule.getSONAOptimizer();
|
|
443
|
+
await sonaOptimizer.initialize();
|
|
444
|
+
this.v3SONA = sonaOptimizer;
|
|
445
|
+
console.log('[SONARouter] V3 MoE integration initialized');
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// V3 not available, use local routing
|
|
449
|
+
console.log('[SONARouter] V3 MoE not available, using local scoring algorithm');
|
|
450
|
+
this.v3MoE = null;
|
|
451
|
+
this.v3SONA = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
startHealthChecks() {
|
|
455
|
+
this.healthCheckInterval = setInterval(() => {
|
|
456
|
+
this.runHealthChecks();
|
|
457
|
+
}, this.config.healthCheckIntervalMs);
|
|
458
|
+
}
|
|
459
|
+
runHealthChecks() {
|
|
460
|
+
// Simple health check - in production would ping actual expert endpoints
|
|
461
|
+
for (const [_expertId, status] of this.expertStatus) {
|
|
462
|
+
// Simulate health based on load
|
|
463
|
+
if (status.load > 0.9) {
|
|
464
|
+
status.state = 'degraded';
|
|
465
|
+
}
|
|
466
|
+
else if (status.load > 0.95) {
|
|
467
|
+
status.state = 'unhealthy';
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
status.state = 'healthy';
|
|
471
|
+
}
|
|
472
|
+
status.lastHealthCheck = new Date();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
generateCacheKey(request) {
|
|
476
|
+
// Cache key based on tool and argument hash
|
|
477
|
+
const argsHash = this.hashObject(request.arguments);
|
|
478
|
+
return `${request.tool}:${argsHash}`;
|
|
479
|
+
}
|
|
480
|
+
hashObject(obj) {
|
|
481
|
+
const str = JSON.stringify(obj, Object.keys(obj).sort());
|
|
482
|
+
let hash = 0;
|
|
483
|
+
for (let i = 0; i < str.length; i++) {
|
|
484
|
+
const char = str.charCodeAt(i);
|
|
485
|
+
hash = (hash << 5) - hash + char;
|
|
486
|
+
hash = hash & hash;
|
|
487
|
+
}
|
|
488
|
+
return hash.toString(36);
|
|
489
|
+
}
|
|
490
|
+
getEligibleExperts(request) {
|
|
491
|
+
const eligible = [];
|
|
492
|
+
for (const [, expert] of this.experts) {
|
|
493
|
+
// Check if expert supports this tool
|
|
494
|
+
if (!expert.capabilities.supportedTools.includes(request.tool)) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
// Check health
|
|
498
|
+
const status = this.expertStatus.get(expert.id);
|
|
499
|
+
if (!status || status.state === 'unhealthy') {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
// Check load
|
|
503
|
+
if (status.load >= 0.95) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
eligible.push(expert);
|
|
507
|
+
}
|
|
508
|
+
return eligible;
|
|
509
|
+
}
|
|
510
|
+
scoreExperts(experts, request) {
|
|
511
|
+
const toolWeights = TOOL_WEIGHTS[request.tool];
|
|
512
|
+
const scoredExperts = [];
|
|
513
|
+
for (const expert of experts) {
|
|
514
|
+
const status = this.expertStatus.get(expert.id);
|
|
515
|
+
const scores = this.calculateScores(expert, status, toolWeights, request);
|
|
516
|
+
scoredExperts.push({ expert, scores });
|
|
517
|
+
}
|
|
518
|
+
// Sort by total score descending
|
|
519
|
+
scoredExperts.sort((a, b) => b.scores.totalScore - a.scores.totalScore);
|
|
520
|
+
return scoredExperts;
|
|
521
|
+
}
|
|
522
|
+
calculateScores(expert, status, toolWeights, request) {
|
|
523
|
+
// Accuracy score based on historical performance
|
|
524
|
+
const accuracyScore = expert.capabilities.accuracyScore * (1 - status.load * 0.1);
|
|
525
|
+
// Latency score (normalized, lower is better)
|
|
526
|
+
const latencyBaseline = 200; // ms
|
|
527
|
+
let latencyScore = Math.max(0, 1 - expert.capabilities.avgLatencyMs / latencyBaseline);
|
|
528
|
+
// Apply latency constraint if specified
|
|
529
|
+
if (request.maxLatencyMs && expert.capabilities.avgLatencyMs > request.maxLatencyMs) {
|
|
530
|
+
latencyScore = latencyScore * 0.5; // Heavy penalty
|
|
531
|
+
}
|
|
532
|
+
// Reliability score based on success rate
|
|
533
|
+
const reliabilityScore = status.successRate;
|
|
534
|
+
// Efficiency score (inverse of load)
|
|
535
|
+
const efficiencyScore = 1 - status.load;
|
|
536
|
+
// Calculate weighted total
|
|
537
|
+
let totalScore = toolWeights.accuracy * accuracyScore +
|
|
538
|
+
toolWeights.latency * latencyScore +
|
|
539
|
+
toolWeights.reliability * reliabilityScore +
|
|
540
|
+
toolWeights.efficiency * efficiencyScore;
|
|
541
|
+
// Priority boost for specialized experts
|
|
542
|
+
if (expert.type === 'specialized' && expert.capabilities.supportedTools.length === 1) {
|
|
543
|
+
totalScore = totalScore * 1.1;
|
|
544
|
+
}
|
|
545
|
+
// Priority tiebreaker
|
|
546
|
+
totalScore = totalScore + expert.priority / 10000;
|
|
547
|
+
return {
|
|
548
|
+
accuracyScore,
|
|
549
|
+
latencyScore,
|
|
550
|
+
reliabilityScore,
|
|
551
|
+
efficiencyScore,
|
|
552
|
+
totalScore,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
selectBestExpert(scoredExperts, request, startTime) {
|
|
556
|
+
const selected = scoredExperts[0];
|
|
557
|
+
const alternatives = scoredExperts.slice(1, 4); // Top 3 alternatives
|
|
558
|
+
// Calculate confidence based on score margin
|
|
559
|
+
let confidence;
|
|
560
|
+
if (alternatives.length > 0) {
|
|
561
|
+
const scoreMargin = selected.scores.totalScore - alternatives[0].scores.totalScore;
|
|
562
|
+
confidence = Math.min(1.0, 0.5 + scoreMargin * 2);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
confidence = 1.0;
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
requestId: request.requestId,
|
|
569
|
+
expertId: selected.expert.id,
|
|
570
|
+
confidence,
|
|
571
|
+
scores: selected.scores,
|
|
572
|
+
alternatives: alternatives.map((alt) => ({
|
|
573
|
+
expertId: alt.expert.id,
|
|
574
|
+
score: alt.scores.totalScore,
|
|
575
|
+
reason: this.generateAlternativeReason(alt.expert, alt.scores),
|
|
576
|
+
})),
|
|
577
|
+
reason: this.generateDecisionReason(selected.expert, selected.scores, request.tool),
|
|
578
|
+
decidedAt: new Date(),
|
|
579
|
+
decisionTimeMs: Date.now() - startTime,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
generateDecisionReason(expert, scores, tool) {
|
|
583
|
+
const toolWeights = TOOL_WEIGHTS[tool];
|
|
584
|
+
const primaryFactor = toolWeights.accuracy >= toolWeights.latency ? 'accuracy' : 'latency';
|
|
585
|
+
return (`Selected ${expert.name} (${expert.type}) for ${tool}: ` +
|
|
586
|
+
`optimized for ${primaryFactor} with score ${scores.totalScore.toFixed(3)}`);
|
|
587
|
+
}
|
|
588
|
+
generateAlternativeReason(expert, scores) {
|
|
589
|
+
return `${expert.name}: score ${scores.totalScore.toFixed(3)}`;
|
|
590
|
+
}
|
|
591
|
+
createFallbackDecision(request, reason, startTime) {
|
|
592
|
+
return {
|
|
593
|
+
requestId: request.requestId,
|
|
594
|
+
expertId: 'direct-fallback',
|
|
595
|
+
confidence: 1.0,
|
|
596
|
+
scores: {
|
|
597
|
+
accuracyScore: 0,
|
|
598
|
+
latencyScore: 0,
|
|
599
|
+
reliabilityScore: 1.0,
|
|
600
|
+
efficiencyScore: 0,
|
|
601
|
+
totalScore: 0,
|
|
602
|
+
},
|
|
603
|
+
alternatives: [],
|
|
604
|
+
reason: `Fallback: ${reason}`,
|
|
605
|
+
decidedAt: new Date(),
|
|
606
|
+
decisionTimeMs: Date.now() - startTime,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
recordOutcome(request, expertId, success) {
|
|
610
|
+
// Update expert status
|
|
611
|
+
const status = this.expertStatus.get(expertId);
|
|
612
|
+
if (status) {
|
|
613
|
+
// Update success rate (rolling average of last 100)
|
|
614
|
+
status.successRate = status.successRate * 0.99 + (success ? 0.01 : 0);
|
|
615
|
+
}
|
|
616
|
+
// Update V3 SONA if available
|
|
617
|
+
if (this.v3SONA) {
|
|
618
|
+
this.v3SONA.processTrajectoryOutcome({
|
|
619
|
+
trajectoryId: request.requestId,
|
|
620
|
+
task: `${request.tool}:${JSON.stringify(request.arguments)}`,
|
|
621
|
+
agent: expertId,
|
|
622
|
+
success,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
// Update V3 MoE weights if available
|
|
626
|
+
if (this.v3MoE) {
|
|
627
|
+
this.v3MoE.updateExpertWeights(expertId, success ? 1.0 : -0.5);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// ============================================================================
|
|
632
|
+
// Factory Functions
|
|
633
|
+
// ============================================================================
|
|
634
|
+
/**
|
|
635
|
+
* Create and initialize a SONARouter instance
|
|
636
|
+
*/
|
|
637
|
+
export async function createSONARouter(config) {
|
|
638
|
+
const router = new SONARouter(config);
|
|
639
|
+
await router.initialize();
|
|
640
|
+
return router;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Check if SONA routing should be used for a request
|
|
644
|
+
*/
|
|
645
|
+
export function shouldUseSONARouting(tool, featureFlags, userTier) {
|
|
646
|
+
// Master switch
|
|
647
|
+
if (!featureFlags['sona.enabled']) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
// Tool-specific flag
|
|
651
|
+
const toolFlag = `sona.tools.${tool}`;
|
|
652
|
+
if (!featureFlags[toolFlag]) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
// Tier check
|
|
656
|
+
if (userTier) {
|
|
657
|
+
const tierFlag = `sona.tiers.${userTier}`;
|
|
658
|
+
if (!featureFlags[tierFlag]) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
// ============================================================================
|
|
665
|
+
// Type Guards
|
|
666
|
+
// ============================================================================
|
|
667
|
+
/**
|
|
668
|
+
* Check if a routing decision indicates high confidence
|
|
669
|
+
*/
|
|
670
|
+
export function isHighConfidenceDecision(decision) {
|
|
671
|
+
return decision.confidence >= 0.8;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Check if a routing decision used fallback
|
|
675
|
+
*/
|
|
676
|
+
export function usedFallback(decision) {
|
|
677
|
+
return decision.expertId === 'direct-fallback';
|
|
678
|
+
}
|
|
679
|
+
//# sourceMappingURL=SONARouter.js.map
|