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.
Files changed (80) hide show
  1. package/README.md +63 -25
  2. package/bin/cli.js +16 -1
  3. package/bin/lynkr-init.js +44 -1
  4. package/bin/lynkr-usage.js +78 -0
  5. package/bin/wrap.js +60 -35
  6. package/config/difficulty-anchors.json +22 -0
  7. package/package.json +23 -2
  8. package/scripts/audit-log-reader.js +399 -0
  9. package/scripts/build-eval-set.js +256 -0
  10. package/scripts/calibrate-thresholds.js +38 -157
  11. package/scripts/compact-dictionary.js +204 -0
  12. package/scripts/mine-difficulty-anchors.js +288 -0
  13. package/scripts/test-deduplication.js +448 -0
  14. package/scripts/validate-difficulty-classifier.js +123 -0
  15. package/scripts/validate-intent-anchors.js +186 -0
  16. package/scripts/ws7-anchor-replay.js +108 -0
  17. package/skills/lynkr/SKILL.md +195 -0
  18. package/src/api/middleware/loop-guard.js +87 -0
  19. package/src/api/middleware/request-logging.js +5 -64
  20. package/src/api/middleware/session.js +0 -0
  21. package/src/api/openai-router.js +120 -101
  22. package/src/api/providers-handler.js +27 -2
  23. package/src/api/router.js +467 -125
  24. package/src/budget/index.js +2 -19
  25. package/src/cache/semantic.js +9 -0
  26. package/src/clients/databricks.js +455 -142
  27. package/src/clients/gpt-utils.js +11 -105
  28. package/src/clients/openai-format.js +10 -3
  29. package/src/clients/openrouter-utils.js +49 -24
  30. package/src/clients/prompt-cache-injection.js +1 -0
  31. package/src/clients/provider-capabilities.js +1 -1
  32. package/src/clients/responses-format.js +34 -3
  33. package/src/clients/routing.js +15 -0
  34. package/src/config/index.js +36 -2
  35. package/src/context/gcf.js +275 -0
  36. package/src/context/tool-result-compressor.js +932 -47
  37. package/src/dashboard/api.js +1 -0
  38. package/src/logger/index.js +14 -1
  39. package/src/memory/search.js +12 -40
  40. package/src/memory/tools.js +3 -24
  41. package/src/orchestrator/bypass.js +4 -2
  42. package/src/orchestrator/index.js +120 -85
  43. package/src/routing/affinity-store.js +194 -0
  44. package/src/routing/agentic-detector.js +36 -6
  45. package/src/routing/bandit.js +25 -6
  46. package/src/routing/calibration.js +212 -0
  47. package/src/routing/classifier-setup.js +207 -0
  48. package/src/routing/client-profiles.js +292 -0
  49. package/src/routing/complexity-analyzer.js +88 -15
  50. package/src/routing/deescalator.js +148 -0
  51. package/src/routing/degradation.js +109 -0
  52. package/src/routing/difficulty-classifier.js +219 -0
  53. package/src/routing/feedback.js +157 -0
  54. package/src/routing/index.js +931 -90
  55. package/src/routing/intent-score.js +441 -0
  56. package/src/routing/interaction.js +3 -0
  57. package/src/routing/knn-router.js +70 -21
  58. package/src/routing/model-registry.js +28 -7
  59. package/src/routing/model-tiers.js +25 -2
  60. package/src/routing/reward-pipeline.js +68 -2
  61. package/src/routing/risk-analyzer.js +30 -1
  62. package/src/routing/risk-classifier.js +6 -2
  63. package/src/routing/session-affinity.js +162 -34
  64. package/src/routing/telemetry.js +286 -13
  65. package/src/routing/verifier.js +267 -0
  66. package/src/server.js +86 -21
  67. package/src/sessions/cleanup.js +17 -0
  68. package/src/tools/index.js +1 -15
  69. package/src/tools/smart-selection.js +10 -0
  70. package/src/tools/web-client.js +3 -3
  71. package/.eslintrc.cjs +0 -12
  72. package/benchmark-configs/litellm_config.yaml +0 -86
  73. package/benchmark-configs/lynkr.env +0 -48
  74. package/benchmark-configs/portkey-config.json +0 -60
  75. package/benchmark-configs/portkey-docker.sh +0 -23
  76. package/benchmark-tier-routing.js +0 -449
  77. package/funding.json +0 -110
  78. package/src/api/middleware/validation.js +0 -261
  79. package/src/routing/drift-monitor.js +0 -113
  80. package/src/workers/helpers.js +0 -185
@@ -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 },
@@ -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: config.logger.level,
114
+ level: rootLevel,
102
115
  name: "claude-backend",
103
116
  base: {
104
117
  env: config.env,
@@ -10,7 +10,7 @@ const MAX_QUERY_LENGTH = 1000;
10
10
  const MAX_OR_TERMS = 50;
11
11
 
12
12
  // ============================================================================
13
- // KEYWORD SANITIZATION (NEW - Critical for FTS5 Safety)
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 (UPDATED - Hardened)
55
+ // FTS5 QUERY PREPARATION
56
56
  // ============================================================================
57
57
 
58
58
  /**
59
59
  * Prepare FTS5 query - handle special characters and phrases
60
60
  *
61
- * CRITICAL FIX for better-sqlite3 v12+ (SQLite 3.46+):
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 (UPDATED)
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); // ✅ ADDED
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) { // ✅ CHANGED - use sanitized
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 (UPDATED - sanitized)
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); // ✅ ADDED
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 "); // ✅ CHANGED - use sanitized
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, // ✅ NEW - exported for testing
376
- sanitizeKeywords, // ✅ NEW - exported for testing
347
+ sanitizeKeyword, // exported for testing
348
+ sanitizeKeywords, // exported for testing
377
349
  };
@@ -13,19 +13,16 @@ const MAX_QUERY_LENGTH = 1000;
13
13
  const MAX_CONTENT_LENGTH = 5000;
14
14
 
15
15
  // ============================================================================
16
- // MEMORY TOOLS (UPDATED WITH VALIDATION)
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 (UPDATED)
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 — a single short user message.
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
  }