lynkr 9.14.17 → 9.14.19

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 CHANGED
@@ -22,6 +22,8 @@
22
22
 
23
23
  > Numbers from the bundled benchmark against LiteLLM on identical free local backends — run it yourself: `node benchmark-tier-routing.js`. It doubles as a 19-scenario routing regression harness (currently 12/12 correctness checks), and `MODE=routing` runs a routing-only head-to-head that judges **both** proxies on the same acceptable-tier sets — including LiteLLM's Auto Router v2. [How it works →](docs/benchmarking.md)
24
24
 
25
+ > **Agentic benchmark (Terminal-Bench core, 80 tasks, Oct 2026):** Lynkr **42/80 for $1.54** vs openrouter/auto **44/80 for $4.72** on the same VM and evening — tied on accuracy within single-run noise (Lynkr's three runs: 39, 39, 42) at ~30% of the cost; vLLM Semantic Router on the same models/hosts: 25/80 (34 agent timeouts from unbounded upstream stalls and per-turn CPU classification). [Full comparison, fairness notes and what it changed in Lynkr →](docs/terminal-bench-router-comparison.md)
26
+
25
27
  > **Third-party benchmark:** on [RouterArena](https://github.com/RouteWorks/RouterArena) (ICLR 2026, 8,400 queries) Lynkr's routing scores **67.65 arena / 68.41% accuracy at $0.29 per 1K queries with 92.38 robustness** — above GPT-5's built-in router and NotDiamond at a fraction of their cost. [Methodology & caveats →](docs/routerarena-benchmark.md)
26
28
 
27
29
  ---
package/bin/cli.js CHANGED
@@ -16,6 +16,8 @@ const SUBCOMMANDS = {
16
16
  restart: path.join(__dirname, "lynkr-restart.js"),
17
17
  run: path.join(__dirname, "run.js"),
18
18
  connect: path.join(__dirname, "lynkr-connect-orcarouter.js"),
19
+ route: path.join(__dirname, "lynkr-route.js"),
20
+ audit: path.join(__dirname, "lynkr-audit.js"),
19
21
  };
20
22
 
21
23
  const sub = process.argv[2];
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lynkr audit <session_id|--last N> Per-session routing ledger from telemetry.
4
+ *
5
+ * Prints, per turn: time, tier, model, decision, effort, latency, status,
6
+ * cost, judge verdict, previous-turn outcome. Totals at the bottom.
7
+ * Reads <cwd>/.lynkr/telemetry.db (same path the server writes).
8
+ */
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ function main() {
15
+ const argv = process.argv.slice(2);
16
+ const json = argv.includes('--json');
17
+ const lastIdx = argv.indexOf('--last');
18
+ const lastVal = lastIdx >= 0 ? argv[lastIdx + 1] : null;
19
+ const sessionId = argv.find((a) => !a.startsWith('--') && a !== lastVal);
20
+ const dbPath = path.resolve(process.cwd(), '.lynkr', 'telemetry.db');
21
+ if (!fs.existsSync(dbPath)) { console.error(`no telemetry db at ${dbPath}`); process.exit(2); }
22
+ const Database = require('better-sqlite3');
23
+ const db = new Database(dbPath, { readonly: true });
24
+
25
+ if (!sessionId) {
26
+ const n = lastIdx >= 0 ? Number(argv[lastIdx + 1]) || 10 : 10;
27
+ const rows = db.prepare(`SELECT session_id, COUNT(*) turns, MIN(timestamp) t0, MAX(timestamp) t1, SUM(cost_usd) cost,
28
+ SUM(status_code != 200) errors, GROUP_CONCAT(DISTINCT tier) tiers
29
+ FROM routing_telemetry WHERE session_id IS NOT NULL GROUP BY session_id ORDER BY t1 DESC LIMIT ?`).all(n);
30
+ if (json) { console.log(JSON.stringify(rows, null, 2)); return; }
31
+ console.log(`\nlast ${rows.length} sessions`);
32
+ for (const r of rows) console.log(` ${r.session_id} turns=${String(r.turns).padStart(3)} tiers=${r.tiers} cost=$${(r.cost || 0).toFixed(4)} errors=${r.errors} ${new Date(r.t1).toISOString()}`);
33
+ console.log(`\nlynkr audit <session_id> for the per-turn ledger\n`);
34
+ return;
35
+ }
36
+ const rows = db.prepare(`SELECT id, timestamp, tier, model, provider, routing_method, decision_name, engine_tier, engine_mode, effort,
37
+ latency_ms, status_code, error_type, cost_usd, input_tokens, output_tokens, cache_read_tokens,
38
+ jev_verdict, jev_confidence, prev_turn_outcome, prev_turn_attributable, escalation_source
39
+ FROM routing_telemetry WHERE session_id = ? ORDER BY id`).all(sessionId);
40
+ if (!rows.length) { console.error(`no rows for session ${sessionId}`); process.exit(1); }
41
+ if (json) { console.log(JSON.stringify(rows, null, 2)); return; }
42
+ const pad = (s, n) => String(s ?? '-').padEnd(n);
43
+ console.log(`\nsession ${sessionId} (${rows.length} turns)\n`);
44
+ console.log(` ${pad('#', 3)} ${pad('time', 8)} ${pad('tier', 9)} ${pad('model', 30)} ${pad('decision', 22)} ${pad('eff', 6)} ${pad('ms', 7)} ${pad('st', 4)} ${pad('$', 9)} ${pad('judge', 14)} ${pad('prev-outcome', 16)}`);
45
+ let cost = 0, inTok = 0, outTok = 0, cached = 0, errs = 0;
46
+ rows.forEach((r, i) => {
47
+ cost += r.cost_usd || 0; inTok += r.input_tokens || 0; outTok += r.output_tokens || 0; cached += r.cache_read_tokens || 0; if (r.status_code !== 200) errs++;
48
+ const t = new Date(r.timestamp).toISOString().slice(11, 19);
49
+ const dec = r.decision_name ? `${r.decision_name}${r.engine_tier && r.engine_tier !== r.tier ? '→' + r.engine_tier : ''}` : '-';
50
+ const judge = r.jev_verdict ? `${r.jev_verdict}@${Number(r.jev_confidence || 0).toFixed(2)}` : '-';
51
+ const po = r.prev_turn_outcome ? `${r.prev_turn_outcome}${r.prev_turn_attributable ? '' : '(env)'}` : '-';
52
+ console.log(` ${pad(i + 1, 3)} ${pad(t, 8)} ${pad(r.tier, 9)} ${pad(String(r.model || '').split('/').pop().slice(0, 30), 30)} ${pad(dec.slice(0, 22), 22)} ${pad(r.effort, 6)} ${pad(r.latency_ms, 7)} ${pad(r.status_code, 4)} ${pad((r.cost_usd || 0).toFixed(5), 9)} ${pad(judge, 14)} ${pad(po, 16)}${r.escalation_source ? ' esc:' + r.escalation_source : ''}`);
53
+ });
54
+ console.log(`\n cost $${cost.toFixed(4)} in ${inTok} (cached ${cached}, ${inTok ? Math.round(100 * cached / (inTok + cached)) : 0}%) out ${outTok} errors ${errs}\n`);
55
+ }
56
+
57
+ if (require.main === module || process.env._LYNKR_SUBCMD === 'audit') main();
58
+ module.exports = { main };
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lynkr route --preview <request.json|-> Explain how a request would route.
4
+ *
5
+ * Runs the full routing decision (signals, legacy chain, decision engine)
6
+ * without calling any provider or writing telemetry. Prints every signal's
7
+ * value, the legacy tier, the matched decision and its tier/effort, and
8
+ * whether observe/enforce mode would change the served tier.
9
+ *
10
+ * Options: --json machine-readable output
11
+ * --config <path> use an alternative config/routing.json
12
+ */
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ function main() {
19
+ const argv = process.argv.slice(2);
20
+ const json = argv.includes('--json');
21
+ const ci = argv.indexOf('--config');
22
+ if (ci >= 0) process.env.LYNKR_ROUTING_CONFIG = path.resolve(argv[ci + 1]);
23
+ const pi = argv.indexOf('--preview');
24
+ const file = pi >= 0 ? argv[pi + 1] : argv.find((a) => !a.startsWith('--'));
25
+ if (!file) {
26
+ console.error('usage: lynkr route --preview <request.json|-> [--json] [--config routing.json]');
27
+ process.exit(2);
28
+ }
29
+ const raw = file === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(file, 'utf8');
30
+ let payload;
31
+ try { payload = JSON.parse(raw); } catch (e) { console.error(`not JSON: ${e.message}`); process.exit(2); }
32
+ if (typeof payload === 'string' || (payload && !payload.messages)) {
33
+ // Bare text → a single user message.
34
+ const text = typeof payload === 'string' ? payload : raw;
35
+ payload = { messages: [{ role: 'user', content: text }], tools: [] };
36
+ }
37
+
38
+ // Load the operator .env like the server does; silence logs.
39
+ const envPath = fs.existsSync(path.join(process.cwd(), '.env')) ? path.join(process.cwd(), '.env') : path.join(require('os').homedir(), '.env');
40
+ require('dotenv').config({ path: envPath });
41
+ process.env.LOG_LEVEL = 'silent'; process.env.LOG_FILE_ENABLED = 'false';
42
+
43
+ const { determineProviderSmart } = require('../src/routing');
44
+ const rc = require('../src/routing/routing-config');
45
+
46
+ (async () => {
47
+ const d = await determineProviderSmart(payload, {});
48
+ const e = d.engine || null;
49
+ const out = {
50
+ config: { path: rc.configPath(), mode: rc.mode() },
51
+ legacy: { tier: d.tier, provider: d.provider, model: d.model, method: d.method, score: d.score ?? null, anchorScore: d.analysis?.anchorScore ?? null, escalations: d.escalations || [] },
52
+ judge: d.analysis?.jev ? { tier: d.analysis.jev.tier, confidence: d.analysis.jev.confidence, probabilities: d.analysis.jev.probabilities } : null,
53
+ shortfall: d.shortfall ? { selected: d.shortfall.selected, lift: d.shortfall.lift, req: d.shortfall.req } : null,
54
+ engine: e ? { decision: e.decision, tier: e.tier, effort: e.effort, hosts: e.hosts, mode: e.mode, agreesWithLegacy: e.agreesWithLegacy, considered: e.trace?.considered, signals: e.signals } : null,
55
+ served: e && e.mode === 'enforce' && e.tier ? e.tier : d.tier,
56
+ };
57
+ if (json) { console.log(JSON.stringify(out, null, 2)); return; }
58
+ const pad = (s, n) => String(s ?? '').padEnd(n);
59
+ console.log(`\nconfig ${out.config.path} (mode: ${out.config.mode})`);
60
+ console.log(`\nSIGNALS`);
61
+ for (const [k, v] of Object.entries(out.engine?.signals || {})) {
62
+ const val = v.band || v.tier || (v.value && typeof v.value === 'object' ? JSON.stringify(v.value) : v.value);
63
+ console.log(` ${pad(k, 12)} ${v.matched ? 'matched ' : '- '} ${pad(val, 22)} ${v.confidence != null ? 'conf ' + Number(v.confidence).toFixed(2) : ''}`);
64
+ }
65
+ console.log(`\nLEGACY tier=${out.legacy.tier} model=${out.legacy.model} method=${out.legacy.method} anchor=${out.legacy.anchorScore}` + (out.judge ? ` judge=${out.judge.tier}@${out.judge.confidence}` : ''));
66
+ if (out.legacy.escalations.length) console.log(` escalations: ${out.legacy.escalations.map((x) => `${x.source}:${x.fromTier}→${x.toTier}`).join(', ')}`);
67
+ if (out.shortfall) console.log(`SHORTFALL wants ${out.shortfall.selected?.tier}:${out.shortfall.selected?.model} lift=${(out.shortfall.lift || []).join('+') || '-'}`);
68
+ console.log(`\nDECISIONS`);
69
+ for (const c of (out.engine?.considered || [])) console.log(` ${c.matched ? '✔' : '·'} ${pad(c.name, 32)} priority ${c.priority}`);
70
+ console.log(`\nENGINE decision=${out.engine?.decision} tier=${out.engine?.tier} effort=${out.engine?.effort ?? '-'} hosts=${out.engine?.hosts ? out.engine.hosts.join(',') : '-'}`);
71
+ console.log(`SERVED ${out.served}${out.engine && !out.engine.agreesWithLegacy ? (out.engine.mode === 'enforce' ? ' (engine overrode legacy)' : ' (observe: engine would pick ' + out.engine.tier + ')') : ''}\n`);
72
+ })().catch((err) => { console.error(err); process.exit(1); });
73
+ }
74
+
75
+ if (require.main === module || process.env._LYNKR_SUBCMD === 'route') main();
76
+ module.exports = { main };