lynkr 9.9.1 → 9.10.0

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 (67) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +4 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +2 -2
  7. package/scripts/validate-difficulty-classifier.js +27 -6
  8. package/src/api/providers-handler.js +0 -1
  9. package/src/api/router.js +275 -160
  10. package/src/clients/databricks.js +95 -6
  11. package/src/config/index.js +3 -43
  12. package/src/orchestrator/index.js +117 -1235
  13. package/src/orchestrator/passthrough-stream.js +382 -0
  14. package/src/orchestrator/sse-transformer.js +408 -0
  15. package/src/routing/affinity-store.js +17 -3
  16. package/src/routing/difficulty-classifier.js +52 -10
  17. package/src/routing/index.js +35 -3
  18. package/src/routing/intent-score.js +20 -2
  19. package/src/routing/session-affinity.js +8 -1
  20. package/src/routing/side-channel-detector.js +103 -0
  21. package/src/server.js +0 -46
  22. package/src/agents/context-manager.js +0 -236
  23. package/src/agents/decomposition/dispatcher.js +0 -185
  24. package/src/agents/decomposition/gate.js +0 -136
  25. package/src/agents/decomposition/index.js +0 -183
  26. package/src/agents/decomposition/model-call.js +0 -75
  27. package/src/agents/decomposition/planner.js +0 -223
  28. package/src/agents/decomposition/synthesizer.js +0 -89
  29. package/src/agents/decomposition/telemetry.js +0 -55
  30. package/src/agents/definitions/loader.js +0 -653
  31. package/src/agents/executor.js +0 -457
  32. package/src/agents/index.js +0 -165
  33. package/src/agents/parallel-coordinator.js +0 -68
  34. package/src/agents/reflector.js +0 -331
  35. package/src/agents/skillbook.js +0 -331
  36. package/src/agents/store.js +0 -259
  37. package/src/edits/index.js +0 -171
  38. package/src/indexer/babel-parser.js +0 -213
  39. package/src/indexer/index.js +0 -1629
  40. package/src/indexer/navigation/index.js +0 -32
  41. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  42. package/src/indexer/parser.js +0 -443
  43. package/src/tasks/store.js +0 -349
  44. package/src/tests/coverage.js +0 -173
  45. package/src/tests/index.js +0 -171
  46. package/src/tests/store.js +0 -213
  47. package/src/tools/agent-task.js +0 -145
  48. package/src/tools/code-mode.js +0 -304
  49. package/src/tools/decompose.js +0 -91
  50. package/src/tools/edits.js +0 -94
  51. package/src/tools/execution.js +0 -171
  52. package/src/tools/git.js +0 -1346
  53. package/src/tools/index.js +0 -306
  54. package/src/tools/indexer.js +0 -360
  55. package/src/tools/lazy-loader.js +0 -366
  56. package/src/tools/mcp-remote.js +0 -88
  57. package/src/tools/mcp.js +0 -116
  58. package/src/tools/process.js +0 -167
  59. package/src/tools/smart-selection.js +0 -180
  60. package/src/tools/stubs.js +0 -55
  61. package/src/tools/tasks.js +0 -260
  62. package/src/tools/tests.js +0 -132
  63. package/src/tools/tinyfish.js +0 -358
  64. package/src/tools/truncate.js +0 -106
  65. package/src/tools/web-client.js +0 -71
  66. package/src/tools/web.js +0 -415
  67. package/src/tools/workspace.js +0 -204
package/README.md CHANGED
@@ -68,7 +68,7 @@ The fastest path is the interactive wizard:
68
68
  lynkr init
69
69
  ```
70
70
 
71
- It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 12 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, agents, rate limiting).
71
+ It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 12 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, rate limiting).
72
72
 
73
73
  Useful flags:
74
74
 
@@ -271,17 +271,39 @@ Tier configuration is strictly authoritative — bandit exploration is constrain
271
271
 
272
272
  ## Advanced Features
273
273
 
274
+ ### Real-time SSE streaming (all tiers)
275
+
276
+ Responses stream token-by-token instead of arriving all at once — including
277
+ through the tier router. Two mechanisms, both on by default:
278
+
279
+ - **Native passthrough**: when the upstream already speaks Anthropic SSE
280
+ (Anthropic endpoints, Z.AI, Ollama v0.14+), Lynkr pipes the bytes straight
281
+ through with backpressure. Kill switch: `LYNKR_NATIVE_PASSTHROUGH=false`.
282
+ - **Cross-format transform**: OpenAI-format upstreams (OpenAI, Azure OpenAI,
283
+ OpenRouter, Databricks, llama.cpp, LM Studio) are reshaped into Anthropic
284
+ events in flight — including reassembling tool-call argument fragments into
285
+ clean `tool_use` blocks. Kill switch: `LYNKR_STREAM_TRANSFORM=false`.
286
+
287
+ The `LYNKR_VISIBLE_ROUTING` badge streams too (injected as the first content
288
+ block), and telemetry is recorded on stream close with real token counts.
289
+ Fallback safety: if an upstream fails before the first byte, the request
290
+ falls back to the buffered path automatically.
291
+
292
+ ```bash
293
+ # Ollama thinking models (MiniMax): streaming skips the <think>-leak repair.
294
+ # If you see raw <think> text in responses, buffer that provider instead:
295
+ LYNKR_OLLAMA_BUFFER_RESPONSES=true # default true; false = stream Ollama
296
+ ```
297
+
274
298
  ### Token Optimization (60-80% savings)
275
299
  ```bash
276
300
  # Enable all optimizations
277
301
  PROMPT_CACHE_ENABLED=true
278
302
  SEMANTIC_CACHE_ENABLED=true
279
- TOOL_INJECTION_ENABLED=false
280
- CODE_MODE_ENABLED=true
281
303
  ```
282
304
 
283
- Always-on (no config): **smart tool selection** (server mode), **RTK tool-result
284
- compression** (test/git/grep/lint/build/JSON output), **MCP tool dedup** (drops
305
+ Always-on (no config): **RTK tool-result compression**
306
+ (test/git/grep/lint/build/JSON output), **MCP tool dedup** (drops
285
307
  built-in WebSearch/WebFetch when an Exa/Tavily MCP tool is present), and
286
308
  **request bypass** (Claude CLI Warmup / title-extraction calls are answered
287
309
  locally, never hitting a provider).
@@ -292,6 +314,25 @@ CAVEMAN_ENABLED=true # off by default — nudges the model to be concise
292
314
  CAVEMAN_LEVEL=lite # lite | full | ultra
293
315
  ```
294
316
 
317
+ ### Built-in dashboard
318
+
319
+ Open `http://localhost:8081/dashboard` while Lynkr is running. No setup, reads
320
+ the local telemetry store:
321
+
322
+ - **Spend & savings** — actual cost vs what the same traffic would have cost
323
+ on the flagship model (the counterfactual no pass-through gateway can show,
324
+ since they neither pick your model nor see local-model traffic)
325
+ - **Tier mix** — daily request breakdown across SIMPLE/MEDIUM/COMPLEX/REASONING
326
+ - **Routing accuracy** — over-/under-provisioned request counts, a self-audit
327
+ of the tier router's decisions
328
+ - **Request logs** — filterable by provider, tier, and errors, with latency,
329
+ tokens, and cost per request
330
+ - **Provider health** — configured providers, credential warnings, circuit
331
+ breaker states
332
+
333
+ JSON APIs behind it (`/dashboard/api/overview|usage|routing|logs`) if you want
334
+ the raw numbers.
335
+
295
336
  ### Cost tracking & model pricing
296
337
  Per-request cost is computed from a model-pricing registry (LiteLLM → models.dev,
297
338
  cached 24h) and recorded in telemetry. Models the registry doesn't know record
@@ -419,10 +460,9 @@ Head-to-head against **LiteLLM** on the **same backends** (Ollama `minimax-m2.5`
419
460
 
420
461
  | Mechanism | Lynkr | LiteLLM | Result |
421
462
  |---|---|---|---|
422
- | Smart tool selection (14 tools) | **959** tokens · $0.0044 | 2,085 tokens · $0.0091 | **53% fewer tokens, 52% cheaper** |
423
463
  | TOON compression (60-item grep JSON) | **427** tokens · $0.009 | 3,458 tokens · $0.018 | **87.6% fewer tokens, 50% cheaper** |
424
464
 
425
- Lynkr strips irrelevant tool schemas (smart tool selection) and binary-compresses large JSON tool results (TOON) — both in-process, no added latency.
465
+ Lynkr binary-compresses large JSON tool results (TOON) in-process, with no added latency.
426
466
 
427
467
  ### Semantic cache
428
468
 
@@ -509,10 +549,11 @@ With tier routing + token optimization: **additional 50-87% savings** on cloud p
509
549
  | **Local models** | Ollama, llama.cpp, LM Studio | Ollama only | ❌ | ❌ |
510
550
  | **Automatic tier routing** | ✅ embedding intent + 13-dimension scorer, verified cascade | ⚠️ Auto Router v2 (v1.94): heuristic under-routes, LLM classifier billed per request | ❌ | ❌ Manual metadata |
511
551
  | **TOON JSON compression** | ✅ up to 87.6% | ❌ | ❌ | ❌ |
512
- | **Smart tool selection** | ✅ up to 60% token reduction | | | |
552
+ | **Upstream SSE streaming** | ✅ native passthrough + cross-format transform | ⚠️ passthrough only | | ⚠️ |
513
553
  | **Semantic cache** | ✅ 171ms hits, 0 tokens | ❌ | ❌ | ✅ Prompt cache only |
554
+ | **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
514
555
  | **Long-term memory** | ✅ SQLite, per-session | ❌ | ❌ | ❌ |
515
- | **MCP integration** | ✅ + Code Mode (96% reduction) | ❌ | ❌ | ❌ |
556
+ | **MCP integration** | ✅ | ❌ | ❌ | ❌ |
516
557
  | **Self-hosted** | ✅ Node.js only | ✅ Python stack | ❌ SaaS | ✅ Docker |
517
558
  | **Dependencies** | Node.js 20+ | Python, Prisma, PostgreSQL | None | Docker, Python |
518
559
 
package/bin/cli.js CHANGED
@@ -11,6 +11,7 @@ const SUBCOMMANDS = {
11
11
  trajectory: path.join(__dirname, "lynkr-trajectory.js"),
12
12
  wrap: path.join(__dirname, "wrap.js"),
13
13
  init: path.join(__dirname, "lynkr-init.js"),
14
+ reset: path.join(__dirname, "lynkr-reset.js"),
14
15
  };
15
16
 
16
17
  const sub = process.argv[2];
@@ -53,6 +54,7 @@ Usage:
53
54
  lynkr usage [options] Show AI spend report and tier-routing savings
54
55
  lynkr stats [options] Shareable savings-receipt card (also: lynkr usage --card)
55
56
  lynkr trajectory [options] Export agent trajectories as JSONL training data
57
+ lynkr reset <resource> Clear a stored resource (e.g. session_pins, all)
56
58
 
57
59
  Options:
58
60
  -h, --help Show this help message
package/bin/lynkr-init.js CHANGED
@@ -197,12 +197,9 @@ const BASELINE_ENV = {
197
197
  LYNKR_KNN_CONFIDENCE_HIGH: '0.55',
198
198
  LYNKR_KNN_CONFIDENCE_LOW: '0.30',
199
199
 
200
- // ── Tool execution ────────────────────────────────────────────────────
201
- TOOL_EXECUTION_MODE: 'client',
202
- TOOL_INJECTION_ENABLED: 'false',
200
+ // ── Tool selection (routing signals) ──────────────────────────────────
203
201
  SMART_TOOL_SELECTION_MODE: 'disabled',
204
202
  SMART_TOOL_SELECTION_TOKEN_BUDGET: '2500',
205
- CODE_MODE_ENABLED: 'true',
206
203
 
207
204
  // ── Caching ───────────────────────────────────────────────────────────
208
205
  PROMPT_CACHE_ENABLED: 'true',
@@ -253,7 +250,6 @@ const BASELINE_ENV = {
253
250
  MEMORY_DEDUP_LOOKBACK: '5',
254
251
  MEMORY_TTL: '3600000',
255
252
  TOKEN_TRACKING_ENABLED: 'true',
256
- TOOL_TRUNCATION_ENABLED: 'true',
257
253
 
258
254
  // ── Prompt/output shaping ─────────────────────────────────────────────
259
255
  SYSTEM_PROMPT_MODE: 'dynamic',
@@ -280,12 +276,8 @@ const BASELINE_ENV = {
280
276
  POLICY_FILE_BLOCKED_PATHS: '/.env,.env,/etc/passwd,/etc/shadow',
281
277
  POLICY_SAFE_COMMANDS_ENABLED: 'true',
282
278
 
283
- // ── Agents ────────────────────────────────────────────────────────────
279
+ // ── Agents (delegation prompt injection; execution is client-side) ────
284
280
  AGENTS_ENABLED: 'true',
285
- AGENTS_MAX_CONCURRENT: '10',
286
- AGENTS_DEFAULT_MODEL: 'haiku',
287
- AGENTS_MAX_STEPS: '15',
288
- AGENTS_TIMEOUT: '300000',
289
281
 
290
282
  // ── Rate limiting ─────────────────────────────────────────────────────
291
283
  RATE_LIMIT_ENABLED: 'true',
@@ -606,12 +598,12 @@ function buildEnvContent(env, isWrap, tierConfig) {
606
598
  // Group output by section in the order it appears in the generated file.
607
599
  // Mirrors the layout of the .env.example reference doc.
608
600
  const SERVER_KEYS = new Set(['PORT', 'NODE_ENV', 'REQUEST_JSON_LIMIT', 'SESSION_DB_PATH', 'WORKSPACE_ROOT', 'ENABLE_TOOL_SEARCH']);
609
- const TOOL_EXEC_KEYS = new Set(['TOOL_EXECUTION_MODE', 'TOOL_INJECTION_ENABLED', 'SMART_TOOL_SELECTION_MODE', 'SMART_TOOL_SELECTION_TOKEN_BUDGET', 'CODE_MODE_ENABLED']);
601
+ const TOOL_EXEC_KEYS = new Set(['SMART_TOOL_SELECTION_MODE', 'SMART_TOOL_SELECTION_TOKEN_BUDGET']);
610
602
  const CACHE_KEYS = new Set([
611
603
  'PROMPT_CACHE_ENABLED', 'PROMPT_CACHE_MAX_ENTRIES', 'PROMPT_CACHE_TTL_MS',
612
604
  'SEMANTIC_CACHE_ENABLED', 'SEMANTIC_CACHE_THRESHOLD', 'SEMANTIC_CACHE_MAX_ENTRIES', 'SEMANTIC_CACHE_TTL_MS',
613
605
  ]);
614
- const MEMORY_KEYS = new Set(Object.keys(merged).filter((k) => k.startsWith('MEMORY_') || k === 'TOKEN_TRACKING_ENABLED' || k === 'TOOL_TRUNCATION_ENABLED'));
606
+ const MEMORY_KEYS = new Set(Object.keys(merged).filter((k) => k.startsWith('MEMORY_') || k === 'TOKEN_TRACKING_ENABLED'));
615
607
  const SHAPING_KEYS = new Set([
616
608
  'SYSTEM_PROMPT_MODE', 'TOOL_DESCRIPTIONS',
617
609
  'HISTORY_COMPRESSION_ENABLED', 'HISTORY_KEEP_RECENT_TURNS', 'HISTORY_SUMMARIZE_OLDER',
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+
6
+ const DB_PATH =
7
+ process.env.LYNKR_TELEMETRY_DB ||
8
+ path.join(__dirname, "..", ".lynkr", "telemetry.db");
9
+
10
+ const RESOURCES = {
11
+ session_pins: {
12
+ describe: "Session → provider affinity pins",
13
+ table: "session_pins",
14
+ },
15
+ };
16
+
17
+ function usage(code = 0) {
18
+ const lines = Object.entries(RESOURCES).map(
19
+ ([k, v]) => ` ${k.padEnd(16)} ${v.describe}`
20
+ );
21
+ console.log(
22
+ `Usage: lynkr reset <resource>
23
+
24
+ Resources:
25
+ ${lines.join("\n")}
26
+ all Clear every resource above
27
+
28
+ DB: ${DB_PATH}
29
+ `
30
+ );
31
+ process.exit(code);
32
+ }
33
+
34
+ const target = process.argv[2];
35
+ if (!target || target === "-h" || target === "--help") usage(0);
36
+
37
+ if (target !== "all" && !RESOURCES[target]) {
38
+ console.error(`Error: unknown resource '${target}'.`);
39
+ usage(1);
40
+ }
41
+
42
+ if (!fs.existsSync(DB_PATH)) {
43
+ console.error(`Error: telemetry DB not found at ${DB_PATH}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ let Database;
48
+ try {
49
+ Database = require("better-sqlite3");
50
+ } catch (err) {
51
+ console.error("Error: better-sqlite3 not installed. Run `npm install` in ~/claude-code.");
52
+ process.exit(1);
53
+ }
54
+
55
+ const db = new Database(DB_PATH);
56
+ const targets = target === "all" ? Object.keys(RESOURCES) : [target];
57
+
58
+ let hadError = false;
59
+ for (const t of targets) {
60
+ const { table } = RESOURCES[t];
61
+ try {
62
+ const before = db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get().n;
63
+ db.prepare(`DELETE FROM ${table}`).run();
64
+ console.log(`✓ cleared ${t} (${before} rows)`);
65
+ } catch (err) {
66
+ hadError = true;
67
+ console.error(`✗ ${t}: ${err.message}`);
68
+ }
69
+ }
70
+ db.close();
71
+ process.exit(hadError ? 1 : 0);
@@ -2,80 +2,226 @@
2
2
  "tiers": {
3
3
  "SIMPLE": {
4
4
  "description": "Greetings, simple Q&A, confirmations, basic lookups",
5
- "range": [0, 25],
5
+ "range": [
6
+ 0,
7
+ 25
8
+ ],
6
9
  "priority": 1,
7
10
  "preferred": {
8
- "ollama": ["llama3.2", "gemma2", "phi3", "qwen2.5:7b", "mistral"],
9
- "llamacpp": ["default"],
10
- "lmstudio": ["default"],
11
- "openai": ["gpt-4o-mini", "gpt-3.5-turbo"],
12
- "azure-openai": ["gpt-4o-mini", "gpt-35-turbo"],
13
- "anthropic": ["claude-3-haiku-20240307", "claude-3-5-haiku-20241022"],
14
- "bedrock": ["anthropic.claude-3-haiku-20240307-v1:0", "amazon.nova-lite-v1:0"],
15
- "databricks": ["databricks-claude-haiku-4-5", "databricks-gpt-5-nano"],
16
- "google": ["gemini-2.0-flash", "gemini-1.5-flash"],
17
- "openrouter": ["google/gemini-flash-1.5", "deepseek/deepseek-chat"],
18
- "zai": ["GLM-4-Flash"],
19
- "moonshot": ["kimi-k2-turbo-preview"]
11
+ "ollama": [
12
+ "llama3.2",
13
+ "gemma2",
14
+ "phi3",
15
+ "qwen2.5:7b",
16
+ "mistral"
17
+ ],
18
+ "llamacpp": [
19
+ "default"
20
+ ],
21
+ "lmstudio": [
22
+ "default"
23
+ ],
24
+ "openai": [
25
+ "gpt-4o-mini",
26
+ "gpt-3.5-turbo"
27
+ ],
28
+ "azure-openai": [
29
+ "gpt-4o-mini",
30
+ "gpt-35-turbo"
31
+ ],
32
+ "anthropic": [
33
+ "claude-3-haiku-20240307",
34
+ "claude-3-5-haiku-20241022"
35
+ ],
36
+ "bedrock": [
37
+ "anthropic.claude-3-haiku-20240307-v1:0",
38
+ "amazon.nova-lite-v1:0"
39
+ ],
40
+ "databricks": [
41
+ "databricks-claude-haiku-4-5",
42
+ "databricks-gpt-5-nano"
43
+ ],
44
+ "google": [
45
+ "gemini-2.0-flash",
46
+ "gemini-1.5-flash"
47
+ ],
48
+ "openrouter": [
49
+ "google/gemini-flash-1.5",
50
+ "deepseek/deepseek-chat"
51
+ ],
52
+ "zai": [
53
+ "GLM-4-Flash"
54
+ ],
55
+ "moonshot": [
56
+ "kimi-k2-turbo-preview"
57
+ ]
20
58
  }
21
59
  },
22
60
  "MEDIUM": {
23
61
  "description": "Code reading, simple edits, research, documentation",
24
- "range": [26, 50],
62
+ "range": [
63
+ 26,
64
+ 50
65
+ ],
25
66
  "priority": 2,
26
67
  "preferred": {
27
- "ollama": ["qwen2.5:32b", "deepseek-coder:33b", "codellama:34b"],
28
- "llamacpp": ["default"],
29
- "lmstudio": ["default"],
30
- "openai": ["gpt-4o", "gpt-4-turbo"],
31
- "azure-openai": ["gpt-4o", "gpt-4"],
32
- "anthropic": ["claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022"],
33
- "bedrock": ["anthropic.claude-3-5-sonnet-20241022-v2:0", "amazon.nova-pro-v1:0"],
34
- "databricks": ["databricks-claude-sonnet-4-5", "databricks-gpt-5-1"],
35
- "google": ["gemini-1.5-pro", "gemini-2.0-pro"],
36
- "openrouter": ["anthropic/claude-3.5-sonnet", "openai/gpt-4o"],
37
- "zai": ["GLM-4.7"],
38
- "moonshot": ["kimi-k2-turbo-preview"]
68
+ "ollama": [
69
+ "qwen2.5:32b",
70
+ "deepseek-coder:33b",
71
+ "codellama:34b"
72
+ ],
73
+ "llamacpp": [
74
+ "default"
75
+ ],
76
+ "lmstudio": [
77
+ "default"
78
+ ],
79
+ "openai": [
80
+ "gpt-4o",
81
+ "gpt-4-turbo"
82
+ ],
83
+ "azure-openai": [
84
+ "gpt-4o",
85
+ "gpt-4"
86
+ ],
87
+ "anthropic": [
88
+ "claude-sonnet-4-20250514",
89
+ "claude-3-5-sonnet-20241022"
90
+ ],
91
+ "bedrock": [
92
+ "anthropic.claude-3-5-sonnet-20241022-v2:0",
93
+ "amazon.nova-pro-v1:0"
94
+ ],
95
+ "databricks": [
96
+ "databricks-claude-sonnet-4-5",
97
+ "databricks-gpt-5-1"
98
+ ],
99
+ "google": [
100
+ "gemini-1.5-pro",
101
+ "gemini-2.0-pro"
102
+ ],
103
+ "openrouter": [
104
+ "anthropic/claude-3.5-sonnet",
105
+ "openai/gpt-4o"
106
+ ],
107
+ "zai": [
108
+ "GLM-4.7"
109
+ ],
110
+ "moonshot": [
111
+ "kimi-k2-turbo-preview"
112
+ ]
39
113
  }
40
114
  },
41
115
  "COMPLEX": {
42
116
  "description": "Multi-file changes, debugging, architecture, refactoring",
43
- "range": [51, 75],
117
+ "range": [
118
+ 51,
119
+ 75
120
+ ],
44
121
  "priority": 3,
45
122
  "preferred": {
46
- "ollama": ["qwen2.5:72b", "llama3.1:70b", "deepseek-coder-v2:236b"],
47
- "openai": ["o1-mini", "o3-mini", "gpt-4o"],
48
- "azure-openai": ["o1-mini", "gpt-4o"],
49
- "anthropic": ["claude-sonnet-4-20250514", "claude-3-5-sonnet-20241022"],
50
- "bedrock": ["anthropic.claude-3-5-sonnet-20241022-v2:0"],
51
- "databricks": ["databricks-claude-sonnet-4-5", "databricks-gpt-5-1-codex-max"],
52
- "google": ["gemini-2.5-pro", "gemini-1.5-pro"],
53
- "openrouter": ["anthropic/claude-3.5-sonnet", "meta-llama/llama-3.1-405b"],
54
- "zai": ["GLM-4.7"],
55
- "moonshot": ["kimi-k2-turbo-preview"]
123
+ "ollama": [
124
+ "qwen2.5:72b",
125
+ "llama3.1:70b",
126
+ "deepseek-coder-v2:236b"
127
+ ],
128
+ "openai": [
129
+ "o1-mini",
130
+ "o3-mini",
131
+ "gpt-4o"
132
+ ],
133
+ "azure-openai": [
134
+ "o1-mini",
135
+ "gpt-4o"
136
+ ],
137
+ "anthropic": [
138
+ "claude-sonnet-4-20250514",
139
+ "claude-3-5-sonnet-20241022"
140
+ ],
141
+ "bedrock": [
142
+ "anthropic.claude-3-5-sonnet-20241022-v2:0"
143
+ ],
144
+ "databricks": [
145
+ "databricks-claude-sonnet-4-5",
146
+ "databricks-gpt-5-1-codex-max"
147
+ ],
148
+ "google": [
149
+ "gemini-2.5-pro",
150
+ "gemini-1.5-pro"
151
+ ],
152
+ "openrouter": [
153
+ "anthropic/claude-3.5-sonnet",
154
+ "meta-llama/llama-3.1-405b"
155
+ ],
156
+ "zai": [
157
+ "GLM-4.7"
158
+ ],
159
+ "moonshot": [
160
+ "kimi-k2-turbo-preview"
161
+ ]
56
162
  }
57
163
  },
58
164
  "REASONING": {
59
165
  "description": "Complex analysis, security audits, novel problems, deep thinking",
60
- "range": [76, 100],
166
+ "range": [
167
+ 76,
168
+ 100
169
+ ],
61
170
  "priority": 4,
62
171
  "preferred": {
63
- "openai": ["o1", "o1-pro", "o3"],
64
- "azure-openai": ["o1", "o1-pro"],
65
- "anthropic": ["claude-opus-4-20250514", "claude-3-opus-20240229"],
66
- "bedrock": ["anthropic.claude-3-opus-20240229-v1:0"],
67
- "databricks": ["databricks-claude-opus-4-6", "databricks-claude-opus-4-5", "databricks-gpt-5-2"],
68
- "google": ["gemini-2.5-pro"],
69
- "openrouter": ["anthropic/claude-3-opus", "deepseek/deepseek-reasoner", "openai/o1"],
70
- "deepseek": ["deepseek-reasoner", "deepseek-r1"],
71
- "moonshot": ["kimi-k2-thinking", "kimi-k2-turbo-preview"]
172
+ "openai": [
173
+ "o1",
174
+ "o1-pro",
175
+ "o3"
176
+ ],
177
+ "azure-openai": [
178
+ "o1",
179
+ "o1-pro"
180
+ ],
181
+ "anthropic": [
182
+ "claude-opus-4-20250514",
183
+ "claude-3-opus-20240229"
184
+ ],
185
+ "bedrock": [
186
+ "anthropic.claude-3-opus-20240229-v1:0"
187
+ ],
188
+ "databricks": [
189
+ "databricks-claude-opus-4-6",
190
+ "databricks-claude-opus-4-5",
191
+ "databricks-gpt-5-2"
192
+ ],
193
+ "google": [
194
+ "gemini-2.5-pro"
195
+ ],
196
+ "openrouter": [
197
+ "anthropic/claude-3-opus",
198
+ "deepseek/deepseek-reasoner",
199
+ "openai/o1"
200
+ ],
201
+ "deepseek": [
202
+ "deepseek-reasoner",
203
+ "deepseek-r1"
204
+ ],
205
+ "moonshot": [
206
+ "kimi-k2-thinking",
207
+ "kimi-k2-turbo-preview"
208
+ ]
72
209
  }
73
210
  }
74
211
  },
75
212
  "localProviders": {
76
- "ollama": { "free": true, "defaultTier": "SIMPLE" },
77
- "llamacpp": { "free": true, "defaultTier": "SIMPLE" },
78
- "lmstudio": { "free": true, "defaultTier": "SIMPLE" }
213
+ "ollama": {
214
+ "free": true,
215
+ "defaultTier": "SIMPLE"
216
+ },
217
+ "llamacpp": {
218
+ "free": true,
219
+ "defaultTier": "SIMPLE"
220
+ },
221
+ "lmstudio": {
222
+ "free": true,
223
+ "defaultTier": "SIMPLE"
224
+ }
79
225
  },
80
226
  "providerAliases": {
81
227
  "azure": "azure-openai",
@@ -84,6 +230,7 @@
84
230
  "claude": "anthropic",
85
231
  "gemini": "google",
86
232
  "vertex": "google",
87
- "kimi": "moonshot"
233
+ "kimi": "moonshot",
234
+ "z-ai": "zai"
88
235
  }
89
- }
236
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.9.1",
3
+ "version": "9.10.0",
4
4
  "files": [
5
5
  "index.js",
6
6
  "install.sh",
@@ -36,7 +36,7 @@
36
36
  "dev": "nodemon index.js",
37
37
  "lint": "eslint src index.js",
38
38
  "test": "npm run test:unit && npm run test:performance",
39
- "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/web-tools.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/distill.test.js test/large-payload.test.js test/code-mode.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/model-registry-cost.test.js test/task-decomposition.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/agent-learning.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js",
39
+ "test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js",
40
40
  "test:memory": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js",
41
41
  "test:new-features": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
42
42
  "test:performance": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
@@ -17,22 +17,29 @@ const path = require("path");
17
17
  const { classifyDifficulty, _clearCacheForTests } = require("../src/routing/difficulty-classifier");
18
18
 
19
19
  const EVAL_FILE = path.join(__dirname, "../data/difficulty-eval.jsonl");
20
- const RESULTS_FILE = path.join(__dirname, "../data/difficulty-eval-results.jsonl");
21
20
  const TIERS = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
22
21
 
23
22
  async function main() {
23
+ // --file=data/xyz.jsonl runs an alternate eval set (e.g. the follow-up
24
+ // slice with conversation context). Results land next to the input file.
25
+ const fileArg = process.argv.find((a) => a.startsWith("--file="));
26
+ const evalFile = fileArg ? path.resolve(fileArg.slice(7)) : EVAL_FILE;
27
+ const resultsFile = evalFile.replace(/\.jsonl$/, "-results.jsonl");
28
+
24
29
  _clearCacheForTests();
25
- const lines = fs.readFileSync(EVAL_FILE, "utf8").split("\n").filter(Boolean);
30
+ const lines = fs.readFileSync(evalFile, "utf8").split("\n").filter(Boolean);
26
31
  const rows = lines.map(l => JSON.parse(l));
27
- console.log(`Loaded ${rows.length} eval rows`);
32
+ console.log(`Loaded ${rows.length} eval rows from ${path.basename(evalFile)}`);
28
33
 
29
34
  // Persist incrementally so a crash mid-run preserves partial data.
30
- const resultsFd = fs.openSync(RESULTS_FILE, "w");
35
+ const resultsFd = fs.openSync(resultsFile, "w");
31
36
  const results = [];
32
37
  const t0 = Date.now();
33
38
  let done = 0;
34
39
  for (const row of rows) {
35
- const r = await classifyDifficulty(row.text);
40
+ // context is threaded the same way production does (router window loop
41
+ // → scoreIntent → classifyDifficulty); absent for standalone prompts.
42
+ const r = await classifyDifficulty(row.text, { context: row.context });
36
43
  const record = {
37
44
  ...row,
38
45
  predicted: r?.tier ?? null,
@@ -47,7 +54,7 @@ async function main() {
47
54
  }
48
55
  }
49
56
  fs.closeSync(resultsFd);
50
- console.log(`\nDone in ${((Date.now() - t0) / 1000).toFixed(0)}s (results saved to ${RESULTS_FILE})`);
57
+ console.log(`\nDone in ${((Date.now() - t0) / 1000).toFixed(0)}s (results saved to ${resultsFile})`);
51
58
 
52
59
  // Overall + per-tier accuracy
53
60
  const perTier = {};
@@ -108,6 +115,20 @@ async function main() {
108
115
  console.log(`SIMPLE→REASONING: ${simpleToReasoning}`);
109
116
  console.log(`MEDIUM→COMPLEX: ${confusion.MEDIUM?.COMPLEX || 0}`);
110
117
 
118
+ // Confidence histogram — validates whether the confidence gate in
119
+ // _reconcile carries signal. A degenerate distribution (everything ≥0.9)
120
+ // means confidence is decorative and the band cap is the only real guard.
121
+ const buckets = { "<0.6": 0, "0.6-0.8": 0, "0.8-0.9": 0, "0.9-1.0": 0 };
122
+ for (const r of results) {
123
+ if (r.confidence == null) continue;
124
+ if (r.confidence < 0.6) buckets["<0.6"]++;
125
+ else if (r.confidence < 0.8) buckets["0.6-0.8"]++;
126
+ else if (r.confidence < 0.9) buckets["0.8-0.9"]++;
127
+ else buckets["0.9-1.0"]++;
128
+ }
129
+ console.log(`\n=== Confidence histogram ===`);
130
+ for (const [b, n] of Object.entries(buckets)) console.log(` ${b}: ${n}`);
131
+
111
132
  // List misclassifications (cap at 20 per tier)
112
133
  console.log(`\n=== Misclassifications (up to 5 per tier) ===`);
113
134
  for (const t of TIERS) {
@@ -401,7 +401,6 @@ router.get("/config", (req, res) => {
401
401
  fallback_provider: config.modelProvider?.fallbackProvider || null,
402
402
  fallback_enabled: config.modelProvider?.fallbackEnabled || false,
403
403
  tier_routing_enabled: config.modelTiers?.enabled || false,
404
- tool_execution_mode: config.toolExecutionMode || "server",
405
404
  configured_providers: providers.map(p => p.name),
406
405
  memory_enabled: config.memory?.enabled || false,
407
406
  smart_tool_selection: config.smartToolSelection?.enabled || false