lynkr 7.2.5 → 8.0.0

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 (64) hide show
  1. package/README.md +2 -2
  2. package/config/model-tiers.json +89 -0
  3. package/docs/docs.html +1 -0
  4. package/docs/index.md +7 -0
  5. package/docs/toon-integration-spec.md +130 -0
  6. package/documentation/README.md +3 -2
  7. package/documentation/claude-code-cli.md +23 -16
  8. package/documentation/cursor-integration.md +17 -14
  9. package/documentation/docker.md +11 -4
  10. package/documentation/embeddings.md +7 -5
  11. package/documentation/faq.md +66 -12
  12. package/documentation/features.md +22 -15
  13. package/documentation/installation.md +66 -14
  14. package/documentation/production.md +43 -8
  15. package/documentation/providers.md +145 -42
  16. package/documentation/routing.md +476 -0
  17. package/documentation/token-optimization.md +7 -5
  18. package/documentation/troubleshooting.md +81 -5
  19. package/install.sh +6 -1
  20. package/package.json +4 -2
  21. package/scripts/setup.js +0 -1
  22. package/src/agents/executor.js +14 -6
  23. package/src/api/middleware/session.js +15 -2
  24. package/src/api/openai-router.js +130 -37
  25. package/src/api/providers-handler.js +15 -1
  26. package/src/api/router.js +107 -2
  27. package/src/budget/index.js +4 -3
  28. package/src/clients/databricks.js +431 -234
  29. package/src/clients/gpt-utils.js +181 -0
  30. package/src/clients/ollama-utils.js +66 -140
  31. package/src/clients/routing.js +0 -1
  32. package/src/clients/standard-tools.js +76 -3
  33. package/src/config/index.js +113 -35
  34. package/src/context/toon.js +173 -0
  35. package/src/logger/index.js +23 -0
  36. package/src/orchestrator/index.js +686 -211
  37. package/src/routing/agentic-detector.js +320 -0
  38. package/src/routing/complexity-analyzer.js +202 -2
  39. package/src/routing/cost-optimizer.js +305 -0
  40. package/src/routing/index.js +168 -159
  41. package/src/routing/model-tiers.js +365 -0
  42. package/src/server.js +2 -2
  43. package/src/sessions/cleanup.js +3 -3
  44. package/src/sessions/record.js +10 -1
  45. package/src/sessions/store.js +7 -2
  46. package/src/tools/agent-task.js +48 -1
  47. package/src/tools/index.js +15 -2
  48. package/te +11622 -0
  49. package/test/README.md +1 -1
  50. package/test/azure-openai-config.test.js +17 -8
  51. package/test/azure-openai-integration.test.js +7 -1
  52. package/test/azure-openai-routing.test.js +41 -43
  53. package/test/bedrock-integration.test.js +18 -32
  54. package/test/hybrid-routing-integration.test.js +35 -20
  55. package/test/hybrid-routing-performance.test.js +74 -64
  56. package/test/llamacpp-integration.test.js +28 -9
  57. package/test/lmstudio-integration.test.js +20 -8
  58. package/test/openai-integration.test.js +17 -20
  59. package/test/performance-tests.js +1 -1
  60. package/test/routing.test.js +65 -59
  61. package/test/toon-compression.test.js +131 -0
  62. package/CLAWROUTER_ROUTING_PLAN.md +0 -910
  63. package/ROUTER_COMPARISON.md +0 -173
  64. package/TIER_ROUTING_PLAN.md +0 -771
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Agentic Workflow Detector
3
+ * Detects multi-step tool chains and autonomous agent patterns
4
+ * Used to boost complexity tier for agentic workloads
5
+ */
6
+
7
+ const logger = require('../logger');
8
+
9
+ // Agent type classification with tier requirements
10
+ const AGENT_TYPES = {
11
+ SINGLE_SHOT: {
12
+ minTier: 'SIMPLE',
13
+ scoreBoost: 0,
14
+ description: 'Simple request-response, no tool chains',
15
+ },
16
+ TOOL_CHAIN: {
17
+ minTier: 'MEDIUM',
18
+ scoreBoost: 15,
19
+ requiresToolUse: true,
20
+ description: 'Sequential tool usage (read -> edit -> test)',
21
+ },
22
+ ITERATIVE: {
23
+ minTier: 'COMPLEX',
24
+ scoreBoost: 25,
25
+ requiresToolUse: true,
26
+ description: 'Retry loops, debugging cycles, iterative refinement',
27
+ },
28
+ AUTONOMOUS: {
29
+ minTier: 'REASONING',
30
+ scoreBoost: 35,
31
+ requiresToolUse: true,
32
+ description: 'Open-ended tasks, full autonomy, complex decision making',
33
+ },
34
+ };
35
+
36
+ // Detection patterns
37
+ const PATTERNS = {
38
+ // Tool chain indicators
39
+ toolChain: /\b(then\s+use|after\s+that|next\s+step|finally|first.*then|step\s*\d+)\b/i,
40
+
41
+ // Iterative work indicators
42
+ iterative: /\b(keep\s+trying|until|repeat|loop|retry|iterate|fix.*again|try.*different|debug)\b/i,
43
+
44
+ // Autonomous work indicators
45
+ autonomous: /\b(figure\s+out|solve|complete\s+the\s+task|do\s+whatever|make\s+it\s+work|find\s+a\s+way|whatever\s+it\s+takes)\b/i,
46
+
47
+ // Multi-file work
48
+ multiFile: /\b(multiple\s+files?|across\s+(the\s+)?codebase|all\s+files?|refactor\s+entire|whole\s+project|everywhere)\b/i,
49
+
50
+ // Planning indicators
51
+ planning: /\b(plan|design|architect|strategy|roadmap|approach|how\s+would\s+you)\b/i,
52
+
53
+ // Implementation indicators
54
+ implementation: /\b(implement|build|create|develop|write|code|add\s+feature)\b/i,
55
+
56
+ // Analysis indicators
57
+ analysis: /\b(analyze|investigate|understand|explain|why\s+is|what\s+causes|root\s+cause)\b/i,
58
+
59
+ // Testing indicators
60
+ testing: /\b(test|verify|validate|check|ensure|confirm|make\s+sure)\b/i,
61
+ };
62
+
63
+ // High-complexity tools that indicate agentic work
64
+ const AGENTIC_TOOLS = new Set([
65
+ // Execution tools
66
+ 'Bash', 'bash', 'shell', 'execute', 'run_command',
67
+ // Write tools
68
+ 'Write', 'write_file', 'fs_write', 'create_file',
69
+ // Edit tools
70
+ 'Edit', 'edit_file', 'fs_edit', 'edit_patch', 'str_replace_editor',
71
+ // Agent tools
72
+ 'Task', 'agent_task', 'spawn_agent', 'delegate',
73
+ // Git tools
74
+ 'Git', 'git_commit', 'git_push', 'git_create_branch',
75
+ // Test tools
76
+ 'Test', 'run_tests', 'pytest', 'jest',
77
+ // Notebook tools
78
+ 'NotebookEdit', 'notebook_edit',
79
+ ]);
80
+
81
+ // Read-only tools (lower complexity)
82
+ const READ_ONLY_TOOLS = new Set([
83
+ 'Read', 'read_file', 'fs_read',
84
+ 'Glob', 'glob', 'find_files',
85
+ 'Grep', 'grep', 'search', 'ripgrep',
86
+ 'WebFetch', 'web_fetch', 'fetch_url',
87
+ 'WebSearch', 'web_search',
88
+ ]);
89
+
90
+ class AgenticDetector {
91
+ /**
92
+ * Detect agentic workflow patterns
93
+ * @param {Object} payload - Request payload with messages and tools
94
+ * @returns {Object} Detection result
95
+ */
96
+ detect(payload) {
97
+ const messages = payload?.messages || [];
98
+ const tools = payload?.tools || [];
99
+ const content = this._extractContent(messages);
100
+
101
+ let score = 0;
102
+ const signals = [];
103
+
104
+ // Signal 1: Tool count (many tools = likely multi-step)
105
+ const toolCount = tools.length;
106
+ if (toolCount > 10) {
107
+ score += 25;
108
+ signals.push({ signal: 'very_high_tool_count', value: toolCount, weight: 25 });
109
+ } else if (toolCount > 5) {
110
+ score += 15;
111
+ signals.push({ signal: 'high_tool_count', value: toolCount, weight: 15 });
112
+ } else if (toolCount > 3) {
113
+ score += 8;
114
+ signals.push({ signal: 'moderate_tool_count', value: toolCount, weight: 8 });
115
+ }
116
+
117
+ // Signal 2: Agentic tools present (Bash, Write, Edit, Task)
118
+ const agenticToolCount = tools.filter(t => {
119
+ const name = t.name || t.function?.name || '';
120
+ return AGENTIC_TOOLS.has(name);
121
+ }).length;
122
+
123
+ if (agenticToolCount > 3) {
124
+ score += 25;
125
+ signals.push({ signal: 'many_agentic_tools', value: agenticToolCount, weight: 25 });
126
+ } else if (agenticToolCount > 1) {
127
+ score += 15;
128
+ signals.push({ signal: 'has_agentic_tools', value: agenticToolCount, weight: 15 });
129
+ } else if (agenticToolCount === 1) {
130
+ score += 8;
131
+ signals.push({ signal: 'single_agentic_tool', value: agenticToolCount, weight: 8 });
132
+ }
133
+
134
+ // Signal 3: Prior tool results (already in agentic loop)
135
+ const toolResultCount = this._countToolResults(messages);
136
+ if (toolResultCount > 5) {
137
+ score += 30;
138
+ signals.push({ signal: 'deep_tool_loop', value: toolResultCount, weight: 30 });
139
+ } else if (toolResultCount > 2) {
140
+ score += 20;
141
+ signals.push({ signal: 'active_tool_loop', value: toolResultCount, weight: 20 });
142
+ } else if (toolResultCount > 0) {
143
+ score += 10;
144
+ signals.push({ signal: 'has_tool_results', value: toolResultCount, weight: 10 });
145
+ }
146
+
147
+ // Signal 4: Pattern matching on content
148
+ if (PATTERNS.autonomous.test(content)) {
149
+ score += 25;
150
+ signals.push({ signal: 'autonomous_pattern', weight: 25 });
151
+ }
152
+
153
+ if (PATTERNS.iterative.test(content)) {
154
+ score += 20;
155
+ signals.push({ signal: 'iterative_pattern', weight: 20 });
156
+ }
157
+
158
+ if (PATTERNS.toolChain.test(content)) {
159
+ score += 15;
160
+ signals.push({ signal: 'tool_chain_pattern', weight: 15 });
161
+ }
162
+
163
+ if (PATTERNS.multiFile.test(content)) {
164
+ score += 15;
165
+ signals.push({ signal: 'multi_file_work', weight: 15 });
166
+ }
167
+
168
+ if (PATTERNS.planning.test(content)) {
169
+ score += 10;
170
+ signals.push({ signal: 'planning_required', weight: 10 });
171
+ }
172
+
173
+ if (PATTERNS.implementation.test(content) && PATTERNS.testing.test(content)) {
174
+ score += 15;
175
+ signals.push({ signal: 'implementation_with_testing', weight: 15 });
176
+ }
177
+
178
+ // Signal 5: Conversation depth
179
+ const messageCount = messages.length;
180
+ if (messageCount > 15) {
181
+ score += 20;
182
+ signals.push({ signal: 'very_deep_conversation', value: messageCount, weight: 20 });
183
+ } else if (messageCount > 8) {
184
+ score += 12;
185
+ signals.push({ signal: 'deep_conversation', value: messageCount, weight: 12 });
186
+ } else if (messageCount > 4) {
187
+ score += 6;
188
+ signals.push({ signal: 'ongoing_conversation', value: messageCount, weight: 6 });
189
+ }
190
+
191
+ // Signal 6: Content length (longer prompts often = more complex tasks)
192
+ if (content.length > 2000) {
193
+ score += 10;
194
+ signals.push({ signal: 'long_prompt', value: content.length, weight: 10 });
195
+ }
196
+
197
+ // Determine agent type
198
+ const agentType = this._classifyAgentType(score, signals);
199
+ const isAgentic = score >= 25;
200
+
201
+ const result = {
202
+ isAgentic,
203
+ agentType,
204
+ confidence: Math.min(score / 100, 1),
205
+ score,
206
+ signals,
207
+ minTier: AGENT_TYPES[agentType].minTier,
208
+ scoreBoost: AGENT_TYPES[agentType].scoreBoost,
209
+ description: AGENT_TYPES[agentType].description,
210
+ };
211
+
212
+ if (isAgentic) {
213
+ logger.debug({
214
+ agentType,
215
+ score,
216
+ signalCount: signals.length,
217
+ toolCount,
218
+ toolResultCount,
219
+ }, '[AgenticDetector] Agentic workflow detected');
220
+ }
221
+
222
+ return result;
223
+ }
224
+
225
+ /**
226
+ * Classify agent type based on score and signals
227
+ */
228
+ _classifyAgentType(score, signals) {
229
+ // Check for specific signal combinations
230
+ const hasAutonomousPattern = signals.some(s => s.signal === 'autonomous_pattern');
231
+ const hasDeepToolLoop = signals.some(s => s.signal === 'deep_tool_loop');
232
+ const hasManyAgenticTools = signals.some(s => s.signal === 'many_agentic_tools');
233
+
234
+ // Autonomous: high score + autonomous pattern or very deep tool usage
235
+ if (score >= 60 || (hasAutonomousPattern && score >= 40)) {
236
+ return 'AUTONOMOUS';
237
+ }
238
+
239
+ // Iterative: moderate-high score with tool loops
240
+ if (score >= 40 || (hasDeepToolLoop && score >= 30)) {
241
+ return 'ITERATIVE';
242
+ }
243
+
244
+ // Tool chain: some tool usage indicated
245
+ if (score >= 20 || hasManyAgenticTools) {
246
+ return 'TOOL_CHAIN';
247
+ }
248
+
249
+ return 'SINGLE_SHOT';
250
+ }
251
+
252
+ /**
253
+ * Extract user content from messages
254
+ */
255
+ _extractContent(messages) {
256
+ const userMsgs = messages.filter(m => m?.role === 'user');
257
+ if (userMsgs.length === 0) return '';
258
+
259
+ // Get last user message
260
+ const last = userMsgs[userMsgs.length - 1];
261
+
262
+ if (typeof last.content === 'string') {
263
+ return last.content;
264
+ }
265
+
266
+ if (Array.isArray(last.content)) {
267
+ return last.content
268
+ .filter(block => block?.type === 'text')
269
+ .map(block => block.text || '')
270
+ .join(' ');
271
+ }
272
+
273
+ return '';
274
+ }
275
+
276
+ /**
277
+ * Count tool results in conversation
278
+ */
279
+ _countToolResults(messages) {
280
+ let count = 0;
281
+
282
+ for (const msg of messages) {
283
+ if (msg?.role === 'user' && Array.isArray(msg.content)) {
284
+ count += msg.content.filter(c => c?.type === 'tool_result').length;
285
+ }
286
+ }
287
+
288
+ return count;
289
+ }
290
+
291
+ /**
292
+ * Get detection stats for debugging
293
+ */
294
+ getPatternStats(content) {
295
+ const stats = {};
296
+ for (const [name, pattern] of Object.entries(PATTERNS)) {
297
+ stats[name] = pattern.test(content);
298
+ }
299
+ return stats;
300
+ }
301
+ }
302
+
303
+ // Singleton instance
304
+ let instance = null;
305
+
306
+ function getAgenticDetector() {
307
+ if (!instance) {
308
+ instance = new AgenticDetector();
309
+ }
310
+ return instance;
311
+ }
312
+
313
+ module.exports = {
314
+ AgenticDetector,
315
+ getAgenticDetector,
316
+ AGENT_TYPES,
317
+ PATTERNS,
318
+ AGENTIC_TOOLS,
319
+ READ_ONLY_TOOLS,
320
+ };
@@ -88,6 +88,58 @@ const FORCE_LOCAL_PATTERNS = [
88
88
  /^(help|menu|commands?|options?)[\s\.\!\?]*$/i,
89
89
  ];
90
90
 
91
+ // Weighted Scoring (15 Dimensions)
92
+ const DIMENSION_WEIGHTS = {
93
+ // Content Analysis (35%)
94
+ tokenCount: 0.08,
95
+ promptComplexity: 0.10,
96
+ technicalDepth: 0.10,
97
+ domainSpecificity: 0.07,
98
+ // Tool Analysis (25%)
99
+ toolCount: 0.08,
100
+ toolComplexity: 0.10,
101
+ toolChainPotential: 0.07,
102
+ // Reasoning Requirements (25%)
103
+ multiStepReasoning: 0.10,
104
+ codeGeneration: 0.08,
105
+ analysisDepth: 0.07,
106
+ // Context Factors (15%)
107
+ conversationDepth: 0.05,
108
+ priorToolUsage: 0.05,
109
+ ambiguity: 0.05,
110
+ };
111
+
112
+ // Tool complexity weights (higher = more complex)
113
+ const TOOL_COMPLEXITY_WEIGHTS = {
114
+ Bash: 0.9,
115
+ bash: 0.9,
116
+ shell: 0.9,
117
+ Write: 0.8,
118
+ write_file: 0.8,
119
+ Edit: 0.7,
120
+ edit_file: 0.7,
121
+ NotebookEdit: 0.7,
122
+ Task: 0.9,
123
+ agent_task: 0.9,
124
+ WebSearch: 0.5,
125
+ WebFetch: 0.4,
126
+ Read: 0.3,
127
+ read_file: 0.3,
128
+ Glob: 0.2,
129
+ Grep: 0.2,
130
+ default: 0.5,
131
+ };
132
+
133
+ // Domain-specific keywords for complexity
134
+ const DOMAIN_KEYWORDS = {
135
+ security: /\b(auth|encrypt|vulnerability|injection|xss|csrf|jwt|oauth|password|credential|secret)\b/i,
136
+ ml: /\b(model|train|inference|tensor|embedding|neural|llm|gpt|transformer|pytorch|tensorflow)\b/i,
137
+ distributed: /\b(microservice|kafka|redis|queue|scale|cluster|replicate|kubernetes|docker|container)\b/i,
138
+ database: /\b(sql|nosql|migration|index|query|transaction|orm|postgres|mongodb|mysql)\b/i,
139
+ frontend: /\b(react|vue|angular|svelte|css|html|component|state|redux|hooks)\b/i,
140
+ devops: /\b(ci\/cd|pipeline|deploy|terraform|ansible|github\s*actions|jenkins)\b/i,
141
+ };
142
+
91
143
  // ============================================================================
92
144
  // PHASE 3: Metrics Tracking
93
145
  // ============================================================================
@@ -360,6 +412,116 @@ function scoreReasoning(content) {
360
412
  return { score: Math.min(score, 15), reasons };
361
413
  }
362
414
 
415
+ // ============================================================================
416
+ // WEIGHTED SCORING FUNCTION (15 Dimensions)
417
+ // ============================================================================
418
+
419
+ /**
420
+ * Calculate weighted complexity score (0-100)
421
+ * Uses 15 dimensions with configurable weights
422
+ * @param {Object} payload - Request payload
423
+ * @param {string} content - Extracted content
424
+ * @returns {Object} Weighted score result
425
+ */
426
+ function calculateWeightedScore(payload, content) {
427
+ const dimensions = {};
428
+
429
+ // 1. Token count (0-100)
430
+ const tokens = estimateTokens(payload);
431
+ dimensions.tokenCount = tokens < 500 ? 10 : tokens < 2000 ? 30 : tokens < 5000 ? 50 : tokens < 10000 ? 70 : 90;
432
+
433
+ // 2. Prompt complexity (sentence structure, avg length)
434
+ const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
435
+ const avgLength = content.length / Math.max(sentences.length, 1);
436
+ dimensions.promptComplexity = Math.min(avgLength / 2, 100);
437
+
438
+ // 3. Technical depth (keyword density)
439
+ const techMatches = (content.match(PATTERNS.technical) || []).length;
440
+ dimensions.technicalDepth = Math.min(techMatches * 15, 100);
441
+
442
+ // 4. Domain specificity (how many domains are touched)
443
+ let domainScore = 0;
444
+ const domainsMatched = [];
445
+ for (const [domain, regex] of Object.entries(DOMAIN_KEYWORDS)) {
446
+ if (regex.test(content)) {
447
+ domainScore += 20;
448
+ domainsMatched.push(domain);
449
+ }
450
+ }
451
+ dimensions.domainSpecificity = Math.min(domainScore, 100);
452
+
453
+ // 5. Tool count
454
+ const toolCount = payload?.tools?.length ?? 0;
455
+ dimensions.toolCount = toolCount === 0 ? 0 :
456
+ toolCount <= 3 ? 20 :
457
+ toolCount <= 6 ? 40 :
458
+ toolCount <= 10 ? 60 :
459
+ toolCount <= 15 ? 80 : 100;
460
+
461
+ // 6. Tool complexity (weighted by tool types)
462
+ if (payload?.tools?.length > 0) {
463
+ const totalWeight = payload.tools.reduce((sum, t) => {
464
+ const name = t.name || t.function?.name || '';
465
+ return sum + (TOOL_COMPLEXITY_WEIGHTS[name] || TOOL_COMPLEXITY_WEIGHTS.default);
466
+ }, 0);
467
+ const avgWeight = totalWeight / payload.tools.length;
468
+ dimensions.toolComplexity = avgWeight * 100;
469
+ } else {
470
+ dimensions.toolComplexity = 0;
471
+ }
472
+
473
+ // 7. Tool chain potential (sequential operations)
474
+ dimensions.toolChainPotential = /\b(then|after|next|finally|first.*then|step\s*\d+)\b/i.test(content) ? 70 : 20;
475
+
476
+ // 8. Multi-step reasoning
477
+ dimensions.multiStepReasoning = ADVANCED_PATTERNS.reasoning.stepByStep.test(content) ? 80 :
478
+ ADVANCED_PATTERNS.reasoning.planning.test(content) ? 60 : 20;
479
+
480
+ // 9. Code generation requirement
481
+ dimensions.codeGeneration = /\b(write|create|implement|build|generate)\s+(a\s+)?(new\s+)?(function|class|module|api|endpoint|service|component)/i.test(content) ? 80 : 20;
482
+
483
+ // 10. Analysis depth
484
+ dimensions.analysisDepth = ADVANCED_PATTERNS.reasoning.tradeoffs.test(content) ? 80 :
485
+ ADVANCED_PATTERNS.reasoning.analysis.test(content) ? 60 : 20;
486
+
487
+ // 11. Conversation depth
488
+ const messageCount = payload?.messages?.length ?? 0;
489
+ dimensions.conversationDepth = messageCount < 3 ? 10 :
490
+ messageCount < 6 ? 30 :
491
+ messageCount < 10 ? 50 : 70;
492
+
493
+ // 12. Prior tool usage (tool results in conversation)
494
+ const toolResults = (payload?.messages || []).filter(m =>
495
+ m.role === 'user' && Array.isArray(m.content) && m.content.some(c => c.type === 'tool_result')
496
+ ).length;
497
+ dimensions.priorToolUsage = toolResults === 0 ? 10 :
498
+ toolResults < 3 ? 40 :
499
+ toolResults < 6 ? 60 : 80;
500
+
501
+ // 13. Ambiguity (inverse of specificity)
502
+ const hasSpecifics = /\b(file|function|line\s*\d+|error|bug|at\s+[\w.]+:\d+|\/[\w/]+\.\w+)\b/i.test(content);
503
+ dimensions.ambiguity = hasSpecifics ? 20 : content.length < 50 ? 70 : 40;
504
+
505
+ // Calculate weighted total
506
+ let weightedTotal = 0;
507
+ for (const [dimension, weight] of Object.entries(DIMENSION_WEIGHTS)) {
508
+ weightedTotal += (dimensions[dimension] || 0) * weight;
509
+ }
510
+
511
+ return {
512
+ score: Math.round(weightedTotal),
513
+ dimensions,
514
+ weights: DIMENSION_WEIGHTS,
515
+ meta: {
516
+ tokens,
517
+ toolCount,
518
+ messageCount,
519
+ toolResults,
520
+ domainsMatched,
521
+ },
522
+ };
523
+ }
524
+
363
525
  /**
364
526
  * Get threshold based on SMART_TOOL_SELECTION_MODE
365
527
  */
@@ -381,13 +543,45 @@ function getThreshold() {
381
543
  * Analyze request complexity and return full analysis
382
544
  *
383
545
  * @param {Object} payload - Request payload
546
+ * @param {Object} options - Analysis options
384
547
  * @returns {Object} Complexity analysis result
385
548
  */
386
- function analyzeComplexity(payload) {
549
+ function analyzeComplexity(payload, options = {}) {
387
550
  const content = extractContent(payload);
388
551
  const messageCount = payload?.messages?.length ?? 0;
552
+ const useWeighted = options.weighted ?? config.routing?.weightedScoring ?? false;
553
+
554
+ // Use weighted scoring if enabled
555
+ if (useWeighted) {
556
+ const weighted = calculateWeightedScore(payload, content);
557
+ const threshold = getThreshold();
558
+ const mode = config.smartToolSelection?.mode ?? 'heuristic';
559
+
560
+ // Check force patterns
561
+ const taskTypeResult = scoreTaskType(content);
562
+ let recommendation;
563
+ if (taskTypeResult.reason === 'force_local') {
564
+ recommendation = 'local';
565
+ } else if (taskTypeResult.reason === 'force_cloud') {
566
+ recommendation = 'cloud';
567
+ } else {
568
+ recommendation = weighted.score >= threshold ? 'cloud' : 'local';
569
+ }
389
570
 
390
- // Calculate individual scores
571
+ return {
572
+ score: weighted.score,
573
+ threshold,
574
+ mode: 'weighted',
575
+ recommendation,
576
+ breakdown: weighted.dimensions,
577
+ weights: weighted.weights,
578
+ meta: weighted.meta,
579
+ forceReason: taskTypeResult.reason?.startsWith('force_') ? taskTypeResult.reason : null,
580
+ content: content.slice(0, 100) + (content.length > 100 ? '...' : ''),
581
+ };
582
+ }
583
+
584
+ // Standard scoring (original logic)
391
585
  const tokenScore = scoreTokens(payload);
392
586
  const toolScore = scoreTools(payload);
393
587
  const taskTypeResult = scoreTaskType(content);
@@ -577,6 +771,9 @@ module.exports = {
577
771
  scoreCodeComplexity,
578
772
  scoreReasoning,
579
773
 
774
+ // Weighted scoring
775
+ calculateWeightedScore,
776
+
580
777
  // Configuration
581
778
  getThreshold,
582
779
 
@@ -592,4 +789,7 @@ module.exports = {
592
789
  ADVANCED_PATTERNS,
593
790
  FORCE_CLOUD_PATTERNS,
594
791
  FORCE_LOCAL_PATTERNS,
792
+ DIMENSION_WEIGHTS,
793
+ TOOL_COMPLEXITY_WEIGHTS,
794
+ DOMAIN_KEYWORDS,
595
795
  };