lynkr 7.2.5 → 8.0.1

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 (124) hide show
  1. package/README.md +3 -3
  2. package/config/model-tiers.json +89 -0
  3. package/install.sh +6 -1
  4. package/package.json +4 -2
  5. package/scripts/setup.js +0 -1
  6. package/src/agents/executor.js +14 -6
  7. package/src/api/middleware/session.js +15 -2
  8. package/src/api/openai-router.js +162 -37
  9. package/src/api/providers-handler.js +15 -1
  10. package/src/api/router.js +107 -2
  11. package/src/budget/index.js +4 -3
  12. package/src/clients/databricks.js +431 -234
  13. package/src/clients/gpt-utils.js +181 -0
  14. package/src/clients/ollama-utils.js +66 -140
  15. package/src/clients/routing.js +0 -1
  16. package/src/clients/standard-tools.js +99 -3
  17. package/src/config/index.js +133 -35
  18. package/src/context/toon.js +173 -0
  19. package/src/logger/index.js +23 -0
  20. package/src/orchestrator/index.js +688 -213
  21. package/src/routing/agentic-detector.js +320 -0
  22. package/src/routing/complexity-analyzer.js +202 -2
  23. package/src/routing/cost-optimizer.js +305 -0
  24. package/src/routing/index.js +168 -159
  25. package/src/routing/model-tiers.js +365 -0
  26. package/src/server.js +4 -14
  27. package/src/sessions/cleanup.js +3 -3
  28. package/src/sessions/record.js +10 -1
  29. package/src/sessions/store.js +7 -2
  30. package/src/tools/agent-task.js +48 -1
  31. package/src/tools/index.js +19 -2
  32. package/src/tools/lazy-loader.js +7 -0
  33. package/src/tools/tinyfish.js +358 -0
  34. package/src/tools/truncate.js +1 -0
  35. package/.github/FUNDING.yml +0 -15
  36. package/.github/workflows/README.md +0 -215
  37. package/.github/workflows/ci.yml +0 -69
  38. package/.github/workflows/index.yml +0 -62
  39. package/.github/workflows/web-tools-tests.yml +0 -56
  40. package/CITATIONS.bib +0 -6
  41. package/CLAWROUTER_ROUTING_PLAN.md +0 -910
  42. package/DEPLOYMENT.md +0 -1001
  43. package/LYNKR-TUI-PLAN.md +0 -984
  44. package/PERFORMANCE-REPORT.md +0 -866
  45. package/PLAN-per-client-model-routing.md +0 -252
  46. package/ROUTER_COMPARISON.md +0 -173
  47. package/TIER_ROUTING_PLAN.md +0 -771
  48. package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
  49. package/docs/BingSiteAuth.xml +0 -4
  50. package/docs/docs-style.css +0 -478
  51. package/docs/docs.html +0 -197
  52. package/docs/google5be250e608e6da39.html +0 -1
  53. package/docs/index.html +0 -577
  54. package/docs/index.md +0 -577
  55. package/docs/robots.txt +0 -4
  56. package/docs/sitemap.xml +0 -44
  57. package/docs/style.css +0 -1223
  58. package/documentation/README.md +0 -100
  59. package/documentation/api.md +0 -806
  60. package/documentation/claude-code-cli.md +0 -672
  61. package/documentation/codex-cli.md +0 -397
  62. package/documentation/contributing.md +0 -571
  63. package/documentation/cursor-integration.md +0 -731
  64. package/documentation/docker.md +0 -867
  65. package/documentation/embeddings.md +0 -760
  66. package/documentation/faq.md +0 -659
  67. package/documentation/features.md +0 -396
  68. package/documentation/headroom.md +0 -519
  69. package/documentation/installation.md +0 -706
  70. package/documentation/memory-system.md +0 -476
  71. package/documentation/production.md +0 -601
  72. package/documentation/providers.md +0 -906
  73. package/documentation/testing.md +0 -629
  74. package/documentation/token-optimization.md +0 -323
  75. package/documentation/tools.md +0 -697
  76. package/documentation/troubleshooting.md +0 -893
  77. package/final-test.js +0 -33
  78. package/headroom-sidecar/config.py +0 -93
  79. package/headroom-sidecar/requirements.txt +0 -14
  80. package/headroom-sidecar/server.py +0 -451
  81. package/monitor-agents.sh +0 -31
  82. package/scripts/audit-log-reader.js +0 -399
  83. package/scripts/compact-dictionary.js +0 -204
  84. package/scripts/test-deduplication.js +0 -448
  85. package/src/db/database.sqlite +0 -0
  86. package/test/README.md +0 -212
  87. package/test/azure-openai-config.test.js +0 -204
  88. package/test/azure-openai-error-resilience.test.js +0 -238
  89. package/test/azure-openai-format-conversion.test.js +0 -354
  90. package/test/azure-openai-integration.test.js +0 -281
  91. package/test/azure-openai-routing.test.js +0 -177
  92. package/test/azure-openai-streaming.test.js +0 -171
  93. package/test/bedrock-integration.test.js +0 -471
  94. package/test/comprehensive-test-suite.js +0 -928
  95. package/test/config-validation.test.js +0 -207
  96. package/test/cursor-integration.test.js +0 -484
  97. package/test/format-conversion.test.js +0 -578
  98. package/test/hybrid-routing-integration.test.js +0 -254
  99. package/test/hybrid-routing-performance.test.js +0 -418
  100. package/test/llamacpp-integration.test.js +0 -863
  101. package/test/lmstudio-integration.test.js +0 -335
  102. package/test/memory/extractor.test.js +0 -398
  103. package/test/memory/retriever.test.js +0 -613
  104. package/test/memory/retriever.test.js.bak +0 -585
  105. package/test/memory/search.test.js +0 -537
  106. package/test/memory/search.test.js.bak +0 -389
  107. package/test/memory/store.test.js +0 -344
  108. package/test/memory/store.test.js.bak +0 -312
  109. package/test/memory/surprise.test.js +0 -300
  110. package/test/memory-performance.test.js +0 -472
  111. package/test/openai-integration.test.js +0 -686
  112. package/test/openrouter-error-resilience.test.js +0 -418
  113. package/test/passthrough-mode.test.js +0 -385
  114. package/test/performance-benchmark.js +0 -351
  115. package/test/performance-tests.js +0 -528
  116. package/test/routing.test.js +0 -219
  117. package/test/web-tools.test.js +0 -329
  118. package/test-agents-simple.js +0 -43
  119. package/test-cli-connection.sh +0 -33
  120. package/test-learning-unit.js +0 -126
  121. package/test-learning.js +0 -112
  122. package/test-parallel-agents.sh +0 -124
  123. package/test-parallel-direct.js +0 -155
  124. package/test-subagents.sh +0 -117
@@ -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
  };