lynkr 7.2.0 → 7.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/agents/store.js +28 -13
- package/src/budget/index.js +13 -7
- package/src/cache/prompt.js +9 -2
- package/src/cache/semantic.js +114 -5
- package/src/db/index.js +370 -333
- package/src/clients/databricks.js.backup +0 -1036
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lynkr",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.2",
|
|
4
4
|
"description": "Self-hosted Claude Code & Cursor proxy with Databricks,AWS BedRock,Azure adapters, openrouter, Ollama,llamacpp,LM Studio, workspace tooling, and MCP integration.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"@azure/openai": "^2.0.0",
|
|
48
48
|
"@babel/parser": "^7.29.0",
|
|
49
49
|
"@babel/traverse": "^7.29.0",
|
|
50
|
-
"better-sqlite3": "^12.6.2",
|
|
51
50
|
"compression": "^1.7.4",
|
|
52
51
|
"diff": "^5.2.0",
|
|
53
52
|
"dockerode": "^4.0.2",
|
|
@@ -62,6 +61,7 @@
|
|
|
62
61
|
"undici": "^6.22.0"
|
|
63
62
|
},
|
|
64
63
|
"optionalDependencies": {
|
|
64
|
+
"better-sqlite3": "^12.6.2",
|
|
65
65
|
"tree-sitter": "^0.21.1",
|
|
66
66
|
"tree-sitter-javascript": "^0.21.0",
|
|
67
67
|
"tree-sitter-python": "^0.21.0",
|
package/src/agents/store.js
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
let Database;
|
|
2
|
+
try {
|
|
3
|
+
Database = require("better-sqlite3");
|
|
4
|
+
} catch {
|
|
5
|
+
Database = null;
|
|
6
|
+
}
|
|
2
7
|
const path = require("path");
|
|
3
8
|
const fs = require("fs");
|
|
4
9
|
const logger = require("../logger");
|
|
5
10
|
|
|
6
11
|
class AgentStore {
|
|
7
12
|
constructor() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
13
|
+
if (!Database) {
|
|
14
|
+
this.db = null;
|
|
15
|
+
return;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
try {
|
|
19
|
+
// Use same database location as main app
|
|
20
|
+
const dbDir = path.join(process.cwd(), "data");
|
|
21
|
+
if (!fs.existsSync(dbDir)) {
|
|
22
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dbPath = path.join(dbDir, "lynkr.db");
|
|
26
|
+
this.db = new Database(dbPath, {
|
|
27
|
+
verbose: process.env.DEBUG_SQL ? console.log : null,
|
|
28
|
+
fileMustExist: false
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.initTables();
|
|
32
|
+
this.prepareStatements();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.warn({ err: err.message }, "AgentStore: better-sqlite3 not available");
|
|
35
|
+
this.db = null;
|
|
36
|
+
}
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
initTables() {
|
package/src/budget/index.js
CHANGED
|
@@ -16,15 +16,21 @@ class BudgetManager {
|
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
try {
|
|
20
|
+
const dbPath = path.join(process.cwd(), 'data', 'budgets.db');
|
|
21
|
+
const dbDir = path.dirname(dbPath);
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (!fs.existsSync(dbDir)) {
|
|
24
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
25
|
+
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
this.db = new Database(dbPath);
|
|
28
|
+
this.initDatabase();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
logger.warn({ err: err.message }, "BudgetManager: better-sqlite3 not available");
|
|
31
|
+
this.enabled = false;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
logger.info({ dbPath }, 'Budget manager initialized');
|
|
30
36
|
}
|
package/src/cache/prompt.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const crypto = require("crypto");
|
|
2
|
-
|
|
2
|
+
let Database;
|
|
3
|
+
try {
|
|
4
|
+
Database = require("better-sqlite3");
|
|
5
|
+
} catch {
|
|
6
|
+
Database = null;
|
|
7
|
+
}
|
|
3
8
|
const path = require("path");
|
|
4
9
|
const fs = require("fs");
|
|
5
10
|
const config = require("../config");
|
|
@@ -49,9 +54,11 @@ class PromptCache {
|
|
|
49
54
|
this.isClosed = false; // Track if database has been closed
|
|
50
55
|
|
|
51
56
|
// Initialize persistent cache database
|
|
52
|
-
if (this.enabled) {
|
|
57
|
+
if (this.enabled && Database) {
|
|
53
58
|
this.initDatabase();
|
|
54
59
|
this.startPruning();
|
|
60
|
+
} else if (!Database) {
|
|
61
|
+
this.enabled = false;
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
|
package/src/cache/semantic.js
CHANGED
|
@@ -142,9 +142,9 @@ class SemanticCache {
|
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Extract cacheable text from messages
|
|
145
|
-
* IMPORTANT: Only extracts user
|
|
146
|
-
*
|
|
147
|
-
*
|
|
145
|
+
* IMPORTANT: Only extracts the ACTUAL user query, NOT system-like content.
|
|
146
|
+
* When messages are merged (e.g., Codex sends AGENTS.md as user role),
|
|
147
|
+
* we need to extract only the real user query from the end.
|
|
148
148
|
*
|
|
149
149
|
* @param {Array} messages - Chat messages
|
|
150
150
|
* @returns {string|null} - Extracted user prompt or null
|
|
@@ -169,12 +169,121 @@ class SemanticCache {
|
|
|
169
169
|
.join('\n');
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
172
|
+
// Extract ONLY the actual user query when content contains merged system-like prefixes
|
|
173
|
+
// Codex and other clients send AGENTS.md, environment_context, etc. as user role messages
|
|
174
|
+
// which get merged into one large user message. We need to extract just the real query.
|
|
175
|
+
const originalLength = content.length;
|
|
176
|
+
content = this._extractActualUserQuery(content);
|
|
177
|
+
|
|
178
|
+
// Log extraction for debugging
|
|
179
|
+
if (originalLength !== content.length) {
|
|
180
|
+
logger.info({
|
|
181
|
+
originalLength,
|
|
182
|
+
extractedLength: content.length,
|
|
183
|
+
extracted: content.substring(0, 100),
|
|
184
|
+
}, '[SemanticCache] Extracted user query from merged content');
|
|
185
|
+
}
|
|
174
186
|
|
|
175
187
|
return content.trim() || null;
|
|
176
188
|
}
|
|
177
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Extract the actual user query from potentially merged content.
|
|
192
|
+
* Codex and other clients merge system instructions with user queries.
|
|
193
|
+
* We need to find the ACTUAL user query, which is usually short and at the end.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} content - Potentially merged user content
|
|
196
|
+
* @returns {string} - The actual user query
|
|
197
|
+
*/
|
|
198
|
+
_extractActualUserQuery(content) {
|
|
199
|
+
if (!content) return content;
|
|
200
|
+
|
|
201
|
+
// Short content is likely the actual query - no extraction needed
|
|
202
|
+
if (content.length < 100) {
|
|
203
|
+
return content;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Patterns that indicate SYSTEM/INSTRUCTION content (NOT user queries)
|
|
207
|
+
const systemPatterns = [
|
|
208
|
+
/^#\s*(AGENTS|CLAUDE|README)/i, // Markdown doc headers
|
|
209
|
+
/^<[a-z_-]+[\s>]/i, // XML-like tags
|
|
210
|
+
/^```/, // Code blocks
|
|
211
|
+
/^---\s*$/m, // YAML/markdown separators
|
|
212
|
+
/^IMPORTANT:/i, // Instruction markers
|
|
213
|
+
/^(permissions|environment|collaboration|context|instructions|Focus on)/i,
|
|
214
|
+
/sandboxing|workspace|cwd|shell/i, // Environment info
|
|
215
|
+
/Do not summarize|respond ONLY/i, // Instruction text
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Split content by double newlines or single newlines
|
|
219
|
+
const segments = content.split(/\n\n+|\n(?=[A-Z#<])/);
|
|
220
|
+
|
|
221
|
+
// Strategy 1: Find the LAST SHORT segment that looks like a real query
|
|
222
|
+
// Real user queries are usually short (< 200 chars) and don't match system patterns
|
|
223
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
224
|
+
const segment = segments[i].trim();
|
|
225
|
+
|
|
226
|
+
// Skip empty or very short segments (< 2 chars)
|
|
227
|
+
if (!segment || segment.length < 2) continue;
|
|
228
|
+
|
|
229
|
+
// Skip if too long (system content tends to be verbose)
|
|
230
|
+
if (segment.length > 300) continue;
|
|
231
|
+
|
|
232
|
+
// Check if this looks like system content
|
|
233
|
+
const isSystemContent = systemPatterns.some(pattern => pattern.test(segment));
|
|
234
|
+
|
|
235
|
+
if (!isSystemContent) {
|
|
236
|
+
// Found a non-system segment - likely the real query
|
|
237
|
+
logger.debug({
|
|
238
|
+
originalLength: content.length,
|
|
239
|
+
extractedLength: segment.length,
|
|
240
|
+
extracted: segment.substring(0, 100),
|
|
241
|
+
}, '[SemanticCache] Extracted actual user query');
|
|
242
|
+
return segment;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Strategy 2: Look for content after the last XML closing tag
|
|
247
|
+
const afterXmlMatch = content.match(/<\/[^>]+>\s*\n*([^<\n]{2,200})$/);
|
|
248
|
+
if (afterXmlMatch) {
|
|
249
|
+
const extracted = afterXmlMatch[1].trim();
|
|
250
|
+
if (extracted.length >= 2) {
|
|
251
|
+
logger.debug({
|
|
252
|
+
extractedLength: extracted.length,
|
|
253
|
+
extracted: extracted.substring(0, 100),
|
|
254
|
+
}, '[SemanticCache] Extracted query after XML tag');
|
|
255
|
+
return extracted;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Strategy 3: Take the very last line if it's short
|
|
260
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
261
|
+
const lastLine = lines[lines.length - 1]?.trim();
|
|
262
|
+
if (lastLine && lastLine.length >= 2 && lastLine.length <= 200) {
|
|
263
|
+
const isSystem = systemPatterns.some(p => p.test(lastLine));
|
|
264
|
+
if (!isSystem) {
|
|
265
|
+
logger.debug({
|
|
266
|
+
extractedLength: lastLine.length,
|
|
267
|
+
extracted: lastLine.substring(0, 100),
|
|
268
|
+
}, '[SemanticCache] Extracted last line as query');
|
|
269
|
+
return lastLine;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Strategy 4: If all else fails, return last 150 chars
|
|
274
|
+
// This ensures we don't cache based on system prompt prefix
|
|
275
|
+
if (content.length > 500) {
|
|
276
|
+
const tail = content.slice(-150).trim();
|
|
277
|
+
logger.debug({
|
|
278
|
+
originalLength: content.length,
|
|
279
|
+
extractedLength: tail.length,
|
|
280
|
+
}, '[SemanticCache] Using tail extraction fallback');
|
|
281
|
+
return tail;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return content;
|
|
285
|
+
}
|
|
286
|
+
|
|
178
287
|
/**
|
|
179
288
|
* Find the most similar cached response
|
|
180
289
|
* IMPORTANT: Only matches entries with the same system prompt hash.
|