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.
- package/README.md +50 -9
- package/bin/cli.js +2 -0
- package/bin/lynkr-init.js +4 -12
- package/bin/lynkr-reset.js +71 -0
- package/config/model-tiers.json +199 -52
- package/package.json +2 -2
- package/scripts/validate-difficulty-classifier.js +27 -6
- package/src/api/providers-handler.js +0 -1
- package/src/api/router.js +275 -160
- package/src/clients/databricks.js +95 -6
- package/src/config/index.js +3 -43
- package/src/orchestrator/index.js +117 -1235
- package/src/orchestrator/passthrough-stream.js +382 -0
- package/src/orchestrator/sse-transformer.js +408 -0
- package/src/routing/affinity-store.js +17 -3
- package/src/routing/difficulty-classifier.js +52 -10
- package/src/routing/index.js +35 -3
- package/src/routing/intent-score.js +20 -2
- package/src/routing/session-affinity.js +8 -1
- package/src/routing/side-channel-detector.js +103 -0
- package/src/server.js +0 -46
- package/src/agents/context-manager.js +0 -236
- package/src/agents/decomposition/dispatcher.js +0 -185
- package/src/agents/decomposition/gate.js +0 -136
- package/src/agents/decomposition/index.js +0 -183
- package/src/agents/decomposition/model-call.js +0 -75
- package/src/agents/decomposition/planner.js +0 -223
- package/src/agents/decomposition/synthesizer.js +0 -89
- package/src/agents/decomposition/telemetry.js +0 -55
- package/src/agents/definitions/loader.js +0 -653
- package/src/agents/executor.js +0 -457
- package/src/agents/index.js +0 -165
- package/src/agents/parallel-coordinator.js +0 -68
- package/src/agents/reflector.js +0 -331
- package/src/agents/skillbook.js +0 -331
- package/src/agents/store.js +0 -259
- package/src/edits/index.js +0 -171
- package/src/indexer/babel-parser.js +0 -213
- package/src/indexer/index.js +0 -1629
- package/src/indexer/navigation/index.js +0 -32
- package/src/indexer/navigation/providers/treeSitter.js +0 -36
- package/src/indexer/parser.js +0 -443
- package/src/tasks/store.js +0 -349
- package/src/tests/coverage.js +0 -173
- package/src/tests/index.js +0 -171
- package/src/tests/store.js +0 -213
- package/src/tools/agent-task.js +0 -145
- package/src/tools/code-mode.js +0 -304
- package/src/tools/decompose.js +0 -91
- package/src/tools/edits.js +0 -94
- package/src/tools/execution.js +0 -171
- package/src/tools/git.js +0 -1346
- package/src/tools/index.js +0 -306
- package/src/tools/indexer.js +0 -360
- package/src/tools/lazy-loader.js +0 -366
- package/src/tools/mcp-remote.js +0 -88
- package/src/tools/mcp.js +0 -116
- package/src/tools/process.js +0 -167
- package/src/tools/smart-selection.js +0 -180
- package/src/tools/stubs.js +0 -55
- package/src/tools/tasks.js +0 -260
- package/src/tools/tests.js +0 -132
- package/src/tools/tinyfish.js +0 -358
- package/src/tools/truncate.js +0 -106
- package/src/tools/web-client.js +0 -71
- package/src/tools/web.js +0 -415
- 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,
|
|
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): **
|
|
284
|
-
|
|
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
|
|
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
|
-
| **
|
|
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** | ✅
|
|
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
|
|
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(['
|
|
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'
|
|
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);
|
package/config/model-tiers.json
CHANGED
|
@@ -2,80 +2,226 @@
|
|
|
2
2
|
"tiers": {
|
|
3
3
|
"SIMPLE": {
|
|
4
4
|
"description": "Greetings, simple Q&A, confirmations, basic lookups",
|
|
5
|
-
"range": [
|
|
5
|
+
"range": [
|
|
6
|
+
0,
|
|
7
|
+
25
|
|
8
|
+
],
|
|
6
9
|
"priority": 1,
|
|
7
10
|
"preferred": {
|
|
8
|
-
"ollama": [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
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": [
|
|
62
|
+
"range": [
|
|
63
|
+
26,
|
|
64
|
+
50
|
|
65
|
+
],
|
|
25
66
|
"priority": 2,
|
|
26
67
|
"preferred": {
|
|
27
|
-
"ollama": [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
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": [
|
|
117
|
+
"range": [
|
|
118
|
+
51,
|
|
119
|
+
75
|
|
120
|
+
],
|
|
44
121
|
"priority": 3,
|
|
45
122
|
"preferred": {
|
|
46
|
-
"ollama": [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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": [
|
|
166
|
+
"range": [
|
|
167
|
+
76,
|
|
168
|
+
100
|
|
169
|
+
],
|
|
61
170
|
"priority": 4,
|
|
62
171
|
"preferred": {
|
|
63
|
-
"openai": [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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": {
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
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/
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 ${
|
|
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
|