lynkr 9.7.3 → 9.9.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/README.md +63 -25
- package/bin/cli.js +16 -1
- package/bin/lynkr-init.js +44 -1
- package/bin/lynkr-usage.js +78 -0
- package/bin/wrap.js +60 -35
- package/config/difficulty-anchors.json +22 -0
- package/package.json +23 -2
- package/scripts/audit-log-reader.js +399 -0
- package/scripts/build-eval-set.js +256 -0
- package/scripts/calibrate-thresholds.js +38 -157
- package/scripts/compact-dictionary.js +204 -0
- package/scripts/mine-difficulty-anchors.js +288 -0
- package/scripts/test-deduplication.js +448 -0
- package/scripts/validate-difficulty-classifier.js +123 -0
- package/scripts/validate-intent-anchors.js +186 -0
- package/scripts/ws7-anchor-replay.js +108 -0
- package/skills/lynkr/SKILL.md +195 -0
- package/src/api/middleware/loop-guard.js +87 -0
- package/src/api/middleware/request-logging.js +5 -64
- package/src/api/middleware/session.js +0 -0
- package/src/api/openai-router.js +120 -101
- package/src/api/providers-handler.js +27 -2
- package/src/api/router.js +467 -125
- package/src/budget/index.js +2 -19
- package/src/cache/semantic.js +9 -0
- package/src/clients/databricks.js +455 -142
- package/src/clients/gpt-utils.js +11 -105
- package/src/clients/openai-format.js +10 -3
- package/src/clients/openrouter-utils.js +49 -24
- package/src/clients/prompt-cache-injection.js +1 -0
- package/src/clients/provider-capabilities.js +1 -1
- package/src/clients/responses-format.js +34 -3
- package/src/clients/routing.js +15 -0
- package/src/config/index.js +36 -2
- package/src/context/gcf.js +275 -0
- package/src/context/tool-result-compressor.js +932 -47
- package/src/dashboard/api.js +1 -0
- package/src/logger/index.js +14 -1
- package/src/memory/search.js +12 -40
- package/src/memory/tools.js +3 -24
- package/src/orchestrator/bypass.js +4 -2
- package/src/orchestrator/index.js +120 -85
- package/src/routing/affinity-store.js +194 -0
- package/src/routing/agentic-detector.js +36 -6
- package/src/routing/bandit.js +25 -6
- package/src/routing/calibration.js +212 -0
- package/src/routing/classifier-setup.js +207 -0
- package/src/routing/client-profiles.js +292 -0
- package/src/routing/complexity-analyzer.js +88 -15
- package/src/routing/deescalator.js +148 -0
- package/src/routing/degradation.js +109 -0
- package/src/routing/difficulty-classifier.js +219 -0
- package/src/routing/feedback.js +157 -0
- package/src/routing/index.js +931 -90
- package/src/routing/intent-score.js +441 -0
- package/src/routing/interaction.js +3 -0
- package/src/routing/knn-router.js +70 -21
- package/src/routing/model-registry.js +28 -7
- package/src/routing/model-tiers.js +25 -2
- package/src/routing/reward-pipeline.js +68 -2
- package/src/routing/risk-analyzer.js +30 -1
- package/src/routing/risk-classifier.js +6 -2
- package/src/routing/session-affinity.js +162 -34
- package/src/routing/telemetry.js +286 -13
- package/src/routing/verifier.js +267 -0
- package/src/server.js +86 -21
- package/src/sessions/cleanup.js +17 -0
- package/src/tools/index.js +1 -15
- package/src/tools/smart-selection.js +10 -0
- package/src/tools/web-client.js +3 -3
- package/.eslintrc.cjs +0 -12
- package/benchmark-configs/litellm_config.yaml +0 -86
- package/benchmark-configs/lynkr.env +0 -48
- package/benchmark-configs/portkey-config.json +0 -60
- package/benchmark-configs/portkey-docker.sh +0 -23
- package/benchmark-tier-routing.js +0 -449
- package/funding.json +0 -110
- package/src/api/middleware/validation.js +0 -261
- package/src/routing/drift-monitor.js +0 -113
- package/src/workers/helpers.js +0 -185
package/src/dashboard/api.js
CHANGED
|
@@ -13,6 +13,7 @@ function providerMeta() {
|
|
|
13
13
|
'azure-anthropic': { type: 'cloud', configured: !!(c.azureAnthropic?.endpoint && c.azureAnthropic?.apiKey) },
|
|
14
14
|
bedrock: { type: 'cloud', configured: !!c.bedrock?.apiKey },
|
|
15
15
|
openrouter: { type: 'cloud', configured: !!c.openrouter?.apiKey },
|
|
16
|
+
edenai: { type: 'cloud', configured: !!c.edenai?.apiKey },
|
|
16
17
|
openai: { type: 'cloud', configured: !!c.openai?.apiKey },
|
|
17
18
|
'azure-openai': { type: 'cloud', configured: !!(c.azureOpenAI?.endpoint && c.azureOpenAI?.apiKey) },
|
|
18
19
|
vertex: { type: 'cloud', configured: !!c.vertex?.projectId },
|
package/src/logger/index.js
CHANGED
|
@@ -95,10 +95,23 @@ if (config.oversizedErrorLogging?.enabled) {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Root level must be the most verbose of any enabled stream, or quieter
|
|
99
|
+
// streams starve louder ones: LOG_LEVEL=silent (the recommended wrap
|
|
100
|
+
// setting) used to make LOG_FILE_ENABLED capture nothing — the exact
|
|
101
|
+
// sessions that most need a log file. Per-stream levels still apply, so
|
|
102
|
+
// the console stays at LOG_LEVEL.
|
|
103
|
+
const LEVEL_ORDER = ["trace", "debug", "info", "warn", "error", "fatal", "silent"];
|
|
104
|
+
const rootLevel = streams
|
|
105
|
+
.map((s) => s.level)
|
|
106
|
+
.reduce(
|
|
107
|
+
(min, l) => (LEVEL_ORDER.indexOf(l) < LEVEL_ORDER.indexOf(min) ? l : min),
|
|
108
|
+
config.logger.level,
|
|
109
|
+
);
|
|
110
|
+
|
|
98
111
|
// Create logger with multistream
|
|
99
112
|
const logger = pino(
|
|
100
113
|
{
|
|
101
|
-
level:
|
|
114
|
+
level: rootLevel,
|
|
102
115
|
name: "claude-backend",
|
|
103
116
|
base: {
|
|
104
117
|
env: config.env,
|
package/src/memory/search.js
CHANGED
|
@@ -10,7 +10,7 @@ const MAX_QUERY_LENGTH = 1000;
|
|
|
10
10
|
const MAX_OR_TERMS = 50;
|
|
11
11
|
|
|
12
12
|
// ============================================================================
|
|
13
|
-
// KEYWORD SANITIZATION
|
|
13
|
+
// KEYWORD SANITIZATION
|
|
14
14
|
// ============================================================================
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -52,13 +52,13 @@ function sanitizeKeywords(keywords) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// ============================================================================
|
|
55
|
-
// FTS5 QUERY PREPARATION
|
|
55
|
+
// FTS5 QUERY PREPARATION
|
|
56
56
|
// ============================================================================
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Prepare FTS5 query - handle special characters and phrases
|
|
60
60
|
*
|
|
61
|
-
*
|
|
61
|
+
* For better-sqlite3 v12+ (SQLite 3.46+):
|
|
62
62
|
* - Commas and periods inside quoted strings cause "fts5: syntax error near ,"
|
|
63
63
|
* - Solution: Extract keywords and search for them individually with OR
|
|
64
64
|
* - This is more robust than attempting to quote complex phrases
|
|
@@ -66,7 +66,6 @@ function sanitizeKeywords(keywords) {
|
|
|
66
66
|
function prepareFTS5Query(query) {
|
|
67
67
|
let cleaned = query.trim();
|
|
68
68
|
|
|
69
|
-
// Length validation
|
|
70
69
|
if (cleaned.length > MAX_QUERY_LENGTH) {
|
|
71
70
|
logger.warn({
|
|
72
71
|
queryLength: cleaned.length,
|
|
@@ -145,7 +144,7 @@ function prepareFTS5Query(query) {
|
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
// ============================================================================
|
|
148
|
-
// SEARCH FUNCTIONS
|
|
147
|
+
// SEARCH FUNCTIONS
|
|
149
148
|
// ============================================================================
|
|
150
149
|
|
|
151
150
|
/**
|
|
@@ -175,7 +174,6 @@ function searchMemories(options) {
|
|
|
175
174
|
ftsQuery: ftsQuery.substring(0, 100)
|
|
176
175
|
}, 'FTS5 query prepared');
|
|
177
176
|
|
|
178
|
-
// Build SQL with filters
|
|
179
177
|
let sql = `
|
|
180
178
|
SELECT m.id, m.session_id, m.content, m.type, m.category,
|
|
181
179
|
m.importance, m.surprise_score, m.access_count, m.decay_factor,
|
|
@@ -188,7 +186,6 @@ function searchMemories(options) {
|
|
|
188
186
|
|
|
189
187
|
const params = [ftsQuery];
|
|
190
188
|
|
|
191
|
-
// Add filters
|
|
192
189
|
if (sessionId) {
|
|
193
190
|
sql += ` AND (m.session_id = ? OR m.session_id IS NULL)`;
|
|
194
191
|
params.push(sessionId);
|
|
@@ -211,7 +208,6 @@ function searchMemories(options) {
|
|
|
211
208
|
params.push(minImportance);
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
// Order by FTS5 rank and importance
|
|
215
211
|
sql += ` ORDER BY memories_fts.rank, m.importance DESC LIMIT ?`;
|
|
216
212
|
params.push(limit);
|
|
217
213
|
|
|
@@ -264,9 +260,8 @@ function searchMemories(options) {
|
|
|
264
260
|
function searchWithExpansion(options) {
|
|
265
261
|
const { query, limit = 10 } = options;
|
|
266
262
|
|
|
267
|
-
// Extract keywords from query
|
|
268
263
|
const keywords = extractKeywords(query);
|
|
269
|
-
const sanitizedKeywords = sanitizeKeywords(keywords);
|
|
264
|
+
const sanitizedKeywords = sanitizeKeywords(keywords);
|
|
270
265
|
|
|
271
266
|
// Search with original query (already sanitized by prepareFTS5Query)
|
|
272
267
|
const results = searchMemories({ ...options, limit: limit * 2 });
|
|
@@ -275,7 +270,7 @@ function searchWithExpansion(options) {
|
|
|
275
270
|
if (results.length < limit && sanitizedKeywords.length > 1) {
|
|
276
271
|
const seen = new Set(results.map((r) => r.id));
|
|
277
272
|
|
|
278
|
-
for (const keyword of sanitizedKeywords) {
|
|
273
|
+
for (const keyword of sanitizedKeywords) {
|
|
279
274
|
if (results.length >= limit) break;
|
|
280
275
|
|
|
281
276
|
const kwResults = searchMemories({
|
|
@@ -315,7 +310,7 @@ function extractKeywords(text) {
|
|
|
315
310
|
}
|
|
316
311
|
|
|
317
312
|
/**
|
|
318
|
-
* Find similar memories by keyword overlap
|
|
313
|
+
* Find similar memories by keyword overlap
|
|
319
314
|
*/
|
|
320
315
|
function findSimilar(memoryId, limit = 5) {
|
|
321
316
|
const memory = store.getMemory(memoryId);
|
|
@@ -324,12 +319,12 @@ function findSimilar(memoryId, limit = 5) {
|
|
|
324
319
|
}
|
|
325
320
|
|
|
326
321
|
const keywords = extractKeywords(memory.content);
|
|
327
|
-
const sanitizedKeywords = sanitizeKeywords(keywords);
|
|
328
|
-
|
|
322
|
+
const sanitizedKeywords = sanitizeKeywords(keywords);
|
|
323
|
+
|
|
329
324
|
if (sanitizedKeywords.length === 0) return [];
|
|
330
325
|
|
|
331
326
|
// Build OR query with SANITIZED keywords
|
|
332
|
-
const query = sanitizedKeywords.join(" OR ");
|
|
327
|
+
const query = sanitizedKeywords.join(" OR ");
|
|
333
328
|
|
|
334
329
|
const results = searchMemories({
|
|
335
330
|
query,
|
|
@@ -339,27 +334,6 @@ function findSimilar(memoryId, limit = 5) {
|
|
|
339
334
|
return results.filter((r) => r.id !== memoryId).slice(0, limit);
|
|
340
335
|
}
|
|
341
336
|
|
|
342
|
-
/**
|
|
343
|
-
* Search by content similarity (UPDATED - sanitized)
|
|
344
|
-
*/
|
|
345
|
-
function searchByContent(content, options = {}) {
|
|
346
|
-
const keywords = extractKeywords(content);
|
|
347
|
-
const sanitizedKeywords = sanitizeKeywords(keywords); // ✅ ADDED
|
|
348
|
-
|
|
349
|
-
if (sanitizedKeywords.length === 0) return [];
|
|
350
|
-
|
|
351
|
-
const query = sanitizedKeywords.slice(0, 5).join(" OR "); // ✅ CHANGED
|
|
352
|
-
return searchMemories({ ...options, query });
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Count search results without fetching them
|
|
357
|
-
*/
|
|
358
|
-
function countSearchResults(options) {
|
|
359
|
-
const results = searchMemories({ ...options, limit: 1000 });
|
|
360
|
-
return results.length;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
337
|
// ============================================================================
|
|
364
338
|
// EXPORTS
|
|
365
339
|
// ============================================================================
|
|
@@ -369,9 +343,7 @@ module.exports = {
|
|
|
369
343
|
searchWithExpansion,
|
|
370
344
|
extractKeywords,
|
|
371
345
|
findSimilar,
|
|
372
|
-
searchByContent,
|
|
373
|
-
countSearchResults,
|
|
374
346
|
prepareFTS5Query,
|
|
375
|
-
sanitizeKeyword, //
|
|
376
|
-
sanitizeKeywords, //
|
|
347
|
+
sanitizeKeyword, // exported for testing
|
|
348
|
+
sanitizeKeywords, // exported for testing
|
|
377
349
|
};
|
package/src/memory/tools.js
CHANGED
|
@@ -13,19 +13,16 @@ const MAX_QUERY_LENGTH = 1000;
|
|
|
13
13
|
const MAX_CONTENT_LENGTH = 5000;
|
|
14
14
|
|
|
15
15
|
// ============================================================================
|
|
16
|
-
// MEMORY TOOLS
|
|
16
|
+
// MEMORY TOOLS
|
|
17
17
|
// ============================================================================
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Tool: memory_search
|
|
21
21
|
* Search long-term memories for relevant facts
|
|
22
|
-
*
|
|
23
|
-
* UPDATED: Added input validation to prevent FTS5 errors and injection
|
|
24
22
|
*/
|
|
25
23
|
async function memory_search(args, context = {}) {
|
|
26
24
|
const { query, limit = 10, type, category } = args;
|
|
27
25
|
|
|
28
|
-
// ✅ Validate query exists and is string
|
|
29
26
|
if (!query || typeof query !== 'string') {
|
|
30
27
|
return {
|
|
31
28
|
ok: false,
|
|
@@ -35,18 +32,16 @@ async function memory_search(args, context = {}) {
|
|
|
35
32
|
};
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
// ✅ Validate query length
|
|
39
35
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
40
36
|
return {
|
|
41
37
|
ok: false,
|
|
42
|
-
content: JSON.stringify({
|
|
38
|
+
content: JSON.stringify({
|
|
43
39
|
error: `Query too long (max ${MAX_QUERY_LENGTH} characters)`,
|
|
44
40
|
provided: query.length
|
|
45
41
|
}),
|
|
46
42
|
};
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
// ✅ Validate type if provided
|
|
50
45
|
if (type && !VALID_TYPES.includes(type)) {
|
|
51
46
|
return {
|
|
52
47
|
ok: false,
|
|
@@ -57,7 +52,6 @@ async function memory_search(args, context = {}) {
|
|
|
57
52
|
};
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
// ✅ Validate category if provided
|
|
61
55
|
if (category && !VALID_CATEGORIES.includes(category)) {
|
|
62
56
|
return {
|
|
63
57
|
ok: false,
|
|
@@ -68,7 +62,6 @@ async function memory_search(args, context = {}) {
|
|
|
68
62
|
};
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
// ✅ Validate limit
|
|
72
65
|
if (typeof limit !== 'number' || limit < 1 || limit > 100) {
|
|
73
66
|
return {
|
|
74
67
|
ok: false,
|
|
@@ -121,8 +114,6 @@ async function memory_search(args, context = {}) {
|
|
|
121
114
|
/**
|
|
122
115
|
* Tool: memory_add
|
|
123
116
|
* Manually add a fact to long-term memory
|
|
124
|
-
*
|
|
125
|
-
* UPDATED: Enhanced validation
|
|
126
117
|
*/
|
|
127
118
|
async function memory_add(args, context = {}) {
|
|
128
119
|
const {
|
|
@@ -132,7 +123,6 @@ async function memory_add(args, context = {}) {
|
|
|
132
123
|
importance = 0.5,
|
|
133
124
|
} = args;
|
|
134
125
|
|
|
135
|
-
// ✅ Validate content
|
|
136
126
|
if (!content || typeof content !== 'string') {
|
|
137
127
|
return {
|
|
138
128
|
ok: false,
|
|
@@ -142,7 +132,6 @@ async function memory_add(args, context = {}) {
|
|
|
142
132
|
};
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
// ✅ Validate content length
|
|
146
135
|
if (content.length > MAX_CONTENT_LENGTH) {
|
|
147
136
|
return {
|
|
148
137
|
ok: false,
|
|
@@ -163,7 +152,6 @@ async function memory_add(args, context = {}) {
|
|
|
163
152
|
};
|
|
164
153
|
}
|
|
165
154
|
|
|
166
|
-
// ✅ Validate type
|
|
167
155
|
if (!VALID_TYPES.includes(type)) {
|
|
168
156
|
return {
|
|
169
157
|
ok: false,
|
|
@@ -174,7 +162,6 @@ async function memory_add(args, context = {}) {
|
|
|
174
162
|
};
|
|
175
163
|
}
|
|
176
164
|
|
|
177
|
-
// ✅ Validate category
|
|
178
165
|
if (!VALID_CATEGORIES.includes(category)) {
|
|
179
166
|
return {
|
|
180
167
|
ok: false,
|
|
@@ -185,7 +172,6 @@ async function memory_add(args, context = {}) {
|
|
|
185
172
|
};
|
|
186
173
|
}
|
|
187
174
|
|
|
188
|
-
// ✅ Validate importance
|
|
189
175
|
if (typeof importance !== 'number' || importance < 0 || importance > 1) {
|
|
190
176
|
return {
|
|
191
177
|
ok: false,
|
|
@@ -241,13 +227,10 @@ async function memory_add(args, context = {}) {
|
|
|
241
227
|
/**
|
|
242
228
|
* Tool: memory_forget
|
|
243
229
|
* Remove memories matching a query
|
|
244
|
-
*
|
|
245
|
-
* UPDATED: Enhanced validation
|
|
246
230
|
*/
|
|
247
231
|
async function memory_forget(args, context = {}) {
|
|
248
232
|
const { query, confirm = false } = args;
|
|
249
233
|
|
|
250
|
-
// ✅ Validate query
|
|
251
234
|
if (!query || typeof query !== 'string') {
|
|
252
235
|
return {
|
|
253
236
|
ok: false,
|
|
@@ -257,7 +240,6 @@ async function memory_forget(args, context = {}) {
|
|
|
257
240
|
};
|
|
258
241
|
}
|
|
259
242
|
|
|
260
|
-
// ✅ Validate query length
|
|
261
243
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
262
244
|
return {
|
|
263
245
|
ok: false,
|
|
@@ -268,7 +250,6 @@ async function memory_forget(args, context = {}) {
|
|
|
268
250
|
};
|
|
269
251
|
}
|
|
270
252
|
|
|
271
|
-
// ✅ Validate confirm is boolean
|
|
272
253
|
if (typeof confirm !== 'boolean') {
|
|
273
254
|
return {
|
|
274
255
|
ok: false,
|
|
@@ -280,7 +261,6 @@ async function memory_forget(args, context = {}) {
|
|
|
280
261
|
}
|
|
281
262
|
|
|
282
263
|
try {
|
|
283
|
-
// Search for matching memories
|
|
284
264
|
const matches = search.searchMemories({
|
|
285
265
|
query,
|
|
286
266
|
limit: 50, // Check up to 50 matches
|
|
@@ -318,7 +298,6 @@ async function memory_forget(args, context = {}) {
|
|
|
318
298
|
};
|
|
319
299
|
}
|
|
320
300
|
|
|
321
|
-
// Delete all matching memories
|
|
322
301
|
let deletedCount = 0;
|
|
323
302
|
for (const memory of matches) {
|
|
324
303
|
const deleted = store.deleteMemory(memory.id);
|
|
@@ -386,7 +365,7 @@ async function memory_stats(args, context = {}) {
|
|
|
386
365
|
}
|
|
387
366
|
|
|
388
367
|
// ============================================================================
|
|
389
|
-
// TOOL DEFINITIONS
|
|
368
|
+
// TOOL DEFINITIONS
|
|
390
369
|
// ============================================================================
|
|
391
370
|
|
|
392
371
|
const MEMORY_TOOLS = {
|
|
@@ -90,10 +90,12 @@ function detectBypass({ payload, headers = {} }) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// Pattern 3: Warmup / count probes —
|
|
93
|
+
// Pattern 3: Warmup / count / quota probes — single short user messages.
|
|
94
|
+
// Unbypassed probes reach a real model, whose confused replies get
|
|
95
|
+
// memory-extracted and re-injected into real turns.
|
|
94
96
|
if (messages.length === 1 && messages[0]?.role === "user") {
|
|
95
97
|
const firstText = getText(messages[0].content).trim();
|
|
96
|
-
if (firstText === "Warmup" || firstText === "count") {
|
|
98
|
+
if (firstText === "Warmup" || firstText === "count" || firstText === "quota") {
|
|
97
99
|
return { kind: firstText.toLowerCase(), text: "OK" };
|
|
98
100
|
}
|
|
99
101
|
}
|