lynkr 3.2.0 → 3.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Self-hosted Claude Code proxy with Databricks,Azure adapters, openrouter, Ollama,llamacpp, workspace tooling, and MCP integration.",
5
5
  "main": "index.js",
6
6
  "bin": {
File without changes
@@ -10,6 +10,7 @@ const tokens = require("../utils/tokens");
10
10
  const systemPrompt = require("../prompts/system");
11
11
  const historyCompression = require("../context/compression");
12
12
  const tokenBudget = require("../context/budget");
13
+ const { classifyRequestType, selectToolsSmartly } = require("../tools/smart-selection");
13
14
 
14
15
  const DROP_KEYS = new Set([
15
16
  "provider",
@@ -1001,39 +1002,22 @@ function sanitizePayload(payload) {
1001
1002
 
1002
1003
  // Smart tool selection (universal, applies to all providers)
1003
1004
  if (config.smartToolSelection?.enabled && Array.isArray(clean.tools) && clean.tools.length > 0) {
1004
- const { classifyRequestType, selectToolsSmartly } = require('../tools/smart-selection');
1005
-
1006
1005
  const classification = classifyRequestType(clean);
1007
-
1008
- logger.debug({
1009
- originalToolCount: clean.tools.length,
1010
- classificationType: classification.type,
1011
- confidence: classification.confidence,
1012
- keywords: classification.keywords
1013
- }, "Smart tool selection: classified request");
1014
-
1015
1006
  const selectedTools = selectToolsSmartly(clean.tools, classification, {
1016
1007
  provider: providerType,
1017
1008
  tokenBudget: config.smartToolSelection.tokenBudget,
1018
1009
  config: config.smartToolSelection
1019
1010
  });
1020
1011
 
1021
- const removedTools = clean.tools
1022
- .filter(t => !selectedTools.find(s => s.name === t.name))
1023
- .map(t => t.name);
1024
-
1025
- const keptTools = selectedTools.map(t => t.name);
1026
-
1027
- logger.info({
1028
- feature: 'smart_tool_selection',
1029
- requestType: classification.type,
1030
- confidence: classification.confidence,
1031
- originalToolCount: clean.tools.length,
1032
- selectedToolCount: selectedTools.length,
1033
- removedTools,
1034
- keptTools,
1035
- provider: providerType
1036
- }, "Smart tool selection applied");
1012
+ // Only log if tools were actually filtered (avoid logging overhead)
1013
+ if (selectedTools.length !== clean.tools.length) {
1014
+ logger.info({
1015
+ requestType: classification.type,
1016
+ originalCount: clean.tools.length,
1017
+ selectedCount: selectedTools.length,
1018
+ provider: providerType
1019
+ }, "Smart tool selection applied");
1020
+ }
1037
1021
 
1038
1022
  clean.tools = selectedTools.length > 0 ? selectedTools : undefined;
1039
1023
  }
@@ -9,6 +9,18 @@
9
9
 
10
10
  const logger = require('../logger');
11
11
 
12
+ // Pre-compiled regex patterns for performance (avoid recompiling on every request)
13
+ const GREETING_PATTERN = /^(hi|hello|hey|good morning|good afternoon|good evening|howdy|greetings|sup|yo)[\s\.\!\?]*$/i;
14
+ const QUESTION_PATTERN = /^(what is|what's|how does|when|where|why|explain|define|tell me about|can you explain)/i;
15
+ const TECHNICAL_KEYWORDS = /code|function|class|file|module|import|export|async|await|promise|callback|api|database|server|client|component|method|variable|array|object|string|number/i;
16
+ const EXPLANATION_PATTERN = /explain|describe|summarize|what does|how does|tell me about|give me an overview|clarify|elaborate/i;
17
+ const WEB_PATTERN = /search|lookup|find info|google|documentation|docs|website|url|link|online|internet|browse/i;
18
+ const READ_PATTERN = /read|show|display|view|cat|check|inspect|look at|see|examine|review|print|output/i;
19
+ const WRITE_PATTERN = /write|create|add|update|modify|change|fix|delete|remove|insert|append|replace|save/i;
20
+ const EDIT_PATTERN = /edit|refactor|rename|move|reorganize|restructure|rewrite/i;
21
+ const EXECUTION_PATTERN = /run|execute|test|compile|build|deploy|start|install|launch|boot|fire up|npm|git|python|node|docker|bash|sh|cmd/i;
22
+ const COMPLEX_PATTERN = /implement|build|create|develop|design|architect|plan|strategy|approach|help with|work on|improve|optimize|enhance|refactor|migrate/i;
23
+
12
24
  /**
13
25
  * Tool selection map: request type → relevant tools
14
26
  */
@@ -91,8 +103,7 @@ function extractContent(message) {
91
103
  * Check if content matches greeting patterns
92
104
  */
93
105
  function isGreeting(content) {
94
- const greetingPattern = /^(hi|hello|hey|good morning|good afternoon|good evening|howdy|greetings|sup|yo)[\s\.\!\?]*$/i;
95
- return greetingPattern.test(content.trim());
106
+ return GREETING_PATTERN.test(content.trim());
96
107
  }
97
108
 
98
109
  /**
@@ -100,85 +111,70 @@ function isGreeting(content) {
100
111
  */
101
112
  function isShortNonTechnical(content) {
102
113
  const trimmed = content.trim();
103
-
104
- // Very short messages (< 20 chars)
105
- if (trimmed.length >= 20) return false;
106
-
107
- // Check for technical keywords
108
- const technicalKeywords = /code|file|function|error|bug|fix|write|read|create|implement|class|module|import|export|async|await/i;
109
- return !technicalKeywords.test(trimmed);
114
+ return trimmed.length < 20 && !TECHNICAL_KEYWORDS.test(trimmed);
110
115
  }
111
116
 
112
117
  /**
113
118
  * Check if content is a simple question
114
119
  */
115
120
  function isSimpleQuestion(content) {
116
- const questionPattern = /^(what is|what's|how does|when|where|why|explain|define|tell me about|can you explain)/i;
117
- return questionPattern.test(content.trim());
121
+ return QUESTION_PATTERN.test(content.trim());
118
122
  }
119
123
 
120
124
  /**
121
125
  * Check for technical keywords
122
126
  */
123
127
  function hasTechnicalKeywords(content) {
124
- const technicalPattern = /code|function|class|file|module|import|export|async|await|promise|callback|api|database|server|client|component|method|variable|array|object|string|number/i;
125
- return technicalPattern.test(content);
128
+ return TECHNICAL_KEYWORDS.test(content);
126
129
  }
127
130
 
128
131
  /**
129
132
  * Check for explanation/research keywords
130
133
  */
131
134
  function hasExplanationKeywords(content) {
132
- const explanationPattern = /explain|describe|summarize|what does|how does|tell me about|give me an overview|clarify|elaborate/i;
133
- return explanationPattern.test(content);
135
+ return EXPLANATION_PATTERN.test(content);
134
136
  }
135
137
 
136
138
  /**
137
139
  * Check for web/search keywords
138
140
  */
139
141
  function hasWebKeywords(content) {
140
- const webPattern = /search|lookup|find info|google|documentation|docs|website|url|link|online|internet|browse/i;
141
- return webPattern.test(content);
142
+ return WEB_PATTERN.test(content);
142
143
  }
143
144
 
144
145
  /**
145
146
  * Check for file reading keywords
146
147
  */
147
148
  function hasReadKeywords(content) {
148
- const readPattern = /read|show|display|view|cat|check|inspect|look at|see|examine|review|print|output/i;
149
- return readPattern.test(content);
149
+ return READ_PATTERN.test(content);
150
150
  }
151
151
 
152
152
  /**
153
153
  * Check for file writing/modification keywords
154
154
  */
155
155
  function hasWriteKeywords(content) {
156
- const writePattern = /write|create|add|update|modify|change|fix|delete|remove|insert|append|replace|save/i;
157
- return writePattern.test(content);
156
+ return WRITE_PATTERN.test(content);
158
157
  }
159
158
 
160
159
  /**
161
160
  * Check for edit/refactor keywords
162
161
  */
163
162
  function hasEditKeywords(content) {
164
- const editPattern = /edit|refactor|rename|move|reorganize|restructure|rewrite/i;
165
- return editPattern.test(content);
163
+ return EDIT_PATTERN.test(content);
166
164
  }
167
165
 
168
166
  /**
169
167
  * Check for execution/testing keywords
170
168
  */
171
169
  function hasExecutionKeywords(content) {
172
- const executionPattern = /run|execute|test|compile|build|deploy|start|install|launch|boot|fire up|npm|git|python|node|docker|bash|sh|cmd/i;
173
- return executionPattern.test(content);
170
+ return EXECUTION_PATTERN.test(content);
174
171
  }
175
172
 
176
173
  /**
177
174
  * Check for complex task keywords
178
175
  */
179
176
  function hasComplexKeywords(content) {
180
- const complexPattern = /implement|build|create|develop|design|architect|plan|strategy|approach|help with|work on|improve|optimize|enhance|refactor|migrate/i;
181
- return complexPattern.test(content);
177
+ return COMPLEX_PATTERN.test(content);
182
178
  }
183
179
 
184
180
  /**
@@ -191,7 +187,6 @@ function classifyRequestType(payload) {
191
187
  const lastMessage = getLastUserMessage(payload);
192
188
 
193
189
  if (!lastMessage) {
194
- logger.debug('No user message found for classification');
195
190
  return { type: 'coding', confidence: 0.5, keywords: [] };
196
191
  }
197
192
 
@@ -199,12 +194,6 @@ function classifyRequestType(payload) {
199
194
  const contentLower = content.toLowerCase();
200
195
  const messageCount = payload.messages?.length ?? 0;
201
196
 
202
- logger.debug({
203
- contentLength: content.length,
204
- messageCount,
205
- contentPreview: content.substring(0, 100)
206
- }, 'Classifying request type');
207
-
208
197
  // 1. Conversational (no tools)
209
198
  if (isGreeting(contentLower)) {
210
199
  return { type: 'conversational', confidence: 1.0, keywords: ['greeting'] };
@@ -286,13 +275,6 @@ function selectToolsSmartly(tools, classification, options = {}) {
286
275
  // Get relevant tool names for this request type
287
276
  const relevantToolNames = TOOL_SELECTION_MAP[requestType] || TOOL_SELECTION_MAP.coding;
288
277
 
289
- logger.debug({
290
- requestType,
291
- confidence: classification.confidence,
292
- relevantToolNames,
293
- mode: config.mode
294
- }, 'Selecting tools for request type');
295
-
296
278
  // Filter to relevant tools only
297
279
  let selectedTools = tools.filter(tool => relevantToolNames.includes(tool.name));
298
280
 
@@ -312,10 +294,6 @@ function selectToolsSmartly(tools, classification, options = {}) {
312
294
 
313
295
  // Provider-specific limits
314
296
  if (provider === 'ollama' && selectedTools.length > 8) {
315
- logger.debug({
316
- originalCount: selectedTools.length,
317
- limit: 8
318
- }, 'Limiting tools for Ollama provider');
319
297
  selectedTools = selectedTools.slice(0, 8);
320
298
  }
321
299
 
@@ -323,11 +301,6 @@ function selectToolsSmartly(tools, classification, options = {}) {
323
301
  const estimatedTokens = estimateToolTokens(selectedTools);
324
302
  if (estimatedTokens > tokenBudget) {
325
303
  const targetCount = Math.floor(tokenBudget / 175);
326
- logger.debug({
327
- estimatedTokens,
328
- tokenBudget,
329
- targetCount
330
- }, 'Enforcing token budget on tools');
331
304
  selectedTools = selectedTools.slice(0, Math.max(targetCount, 0));
332
305
  }
333
306
 
@@ -337,14 +310,6 @@ function selectToolsSmartly(tools, classification, options = {}) {
337
310
  selectedTools = selectedTools.filter(t => minimalTools.includes(t.name));
338
311
  }
339
312
 
340
- logger.info({
341
- requestType,
342
- originalToolCount: tools.length,
343
- selectedToolCount: selectedTools.length,
344
- removedToolCount: tools.length - selectedTools.length,
345
- estimatedTokenSavings: estimateToolTokens(tools) - estimateToolTokens(selectedTools)
346
- }, 'Smart tool selection completed');
347
-
348
313
  return selectedTools;
349
314
  }
350
315