lynkr 9.0.2 → 9.1.3
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 +21 -10
- package/bin/cli.js +18 -1
- package/bin/lynkr-trajectory.js +136 -0
- package/bin/lynkr-usage.js +219 -0
- package/funding.json +110 -0
- package/package.json +4 -2
- package/public/dashboard.html +665 -0
- package/scripts/build-knn-index.js +130 -0
- package/scripts/calibrate-thresholds.js +197 -0
- package/scripts/compare-policies.js +67 -0
- package/scripts/learn-output-ratios.js +162 -0
- package/scripts/refresh-pricing.js +122 -0
- package/scripts/run-routerarena.js +26 -0
- package/scripts/sample-regret.js +84 -0
- package/scripts/train-risk-classifier.js +191 -0
- package/src/api/files-router.js +6 -6
- package/src/api/middleware/budget-enforcer.js +60 -0
- package/src/api/middleware/budget.js +19 -1
- package/src/api/middleware/load-shedding.js +17 -0
- package/src/api/middleware/tenant.js +21 -0
- package/src/api/openai-router.js +1 -1
- package/src/api/router.js +204 -87
- package/src/budget/hierarchical-budget.js +159 -0
- package/src/cache/semantic.js +28 -2
- package/src/clients/databricks.js +68 -10
- package/src/clients/openai-format.js +31 -5
- package/src/config/index.js +246 -43
- package/src/context/toon.js +5 -4
- package/src/dashboard/api.js +170 -0
- package/src/dashboard/router.js +13 -0
- package/src/headroom/client.js +3 -109
- package/src/headroom/index.js +0 -14
- package/src/memory/search.js +0 -50
- package/src/orchestrator/index.js +106 -11
- package/src/orchestrator/preflight.js +188 -0
- package/src/prompts/system.js +34 -6
- package/src/routing/bandit.js +246 -0
- package/src/routing/cascade.js +106 -0
- package/src/routing/complexity-analyzer.js +7 -15
- package/src/routing/confidence-scorer.js +121 -0
- package/src/routing/context-validator.js +71 -0
- package/src/routing/cost-optimizer.js +5 -2
- package/src/routing/deadline.js +52 -0
- package/src/routing/drift-monitor.js +113 -0
- package/src/routing/embedding-cache.js +77 -0
- package/src/routing/index.js +374 -4
- package/src/routing/interaction.js +183 -0
- package/src/routing/knn-router.js +206 -0
- package/src/routing/latency-tracker.js +113 -71
- package/src/routing/model-tiers.js +156 -6
- package/src/routing/output-ratios.js +57 -0
- package/src/routing/regret-estimator.js +91 -0
- package/src/routing/reward-pipeline.js +62 -0
- package/src/routing/risk-analyzer.js +194 -0
- package/src/routing/risk-classifier.js +130 -0
- package/src/routing/shadow-mode.js +77 -0
- package/src/routing/telemetry.js +7 -0
- package/src/routing/tenant-policy.js +96 -0
- package/src/routing/tokenizer.js +162 -0
- package/src/server.js +12 -0
- package/src/stores/file-store.js +42 -7
- package/src/tools/smart-selection.js +11 -2
- package/src/training/trajectory-compressor.js +266 -0
- package/src/usage/aggregator.js +206 -0
- package/src/utils/markdown-ansi.js +146 -0
package/README.md
CHANGED
|
@@ -225,14 +225,15 @@ Routes requests to the right model based on 5-phase complexity analysis. Simple
|
|
|
225
225
|
- **Graphify integration** — AST-based knowledge graph detects god nodes, community cohesion, blast radius across 19 languages
|
|
226
226
|
- **Routing telemetry** — every decision recorded with quality scoring (0-100) and latency tracking (P50/P95/P99)
|
|
227
227
|
|
|
228
|
-
### Token Optimization (
|
|
229
|
-
- **
|
|
230
|
-
- **
|
|
231
|
-
- **
|
|
232
|
-
- **
|
|
233
|
-
- **
|
|
234
|
-
- **
|
|
235
|
-
- **
|
|
228
|
+
### Token Optimization (8 Phases)
|
|
229
|
+
- **MCP Code Mode** — replaces 100+ MCP tool schemas with 4 meta-tools (~96% reduction, lazy tool discovery)
|
|
230
|
+
- **Smart tool selection** — only sends tools relevant to the current task (50-70% reduction)
|
|
231
|
+
- **Prompt caching** — SHA-256 keyed LRU cache (30-45% reduction on repeated prompts)
|
|
232
|
+
- **Memory deduplication** — eliminates repeated information across turns (20-30% reduction)
|
|
233
|
+
- **Tool response truncation** — intelligent truncation of long outputs (15-25% reduction)
|
|
234
|
+
- **Dynamic system prompts** — adapt complexity to request type (10-20% reduction)
|
|
235
|
+
- **Distill compression** — structural similarity, delta rendering, smart dedup of repetitive tool outputs (20-40% reduction)
|
|
236
|
+
- **Headroom sidecar** — optional ML-based compression: Smart Crusher, CCR, LLMLingua (47-92% reduction)
|
|
236
237
|
|
|
237
238
|
### Enterprise Resilience
|
|
238
239
|
- **Circuit breakers** — automatic failover with half-open probe recovery
|
|
@@ -254,12 +255,22 @@ SEMANTIC_CACHE_THRESHOLD=0.95
|
|
|
254
255
|
```
|
|
255
256
|
|
|
256
257
|
### MCP Integration + Code Mode
|
|
257
|
-
Automatic Model Context Protocol server discovery and orchestration. Your MCP tools work through Lynkr without configuration.
|
|
258
|
+
Automatic Model Context Protocol server discovery and orchestration. Your MCP tools work through Lynkr without configuration.
|
|
259
|
+
|
|
260
|
+
**MCP Code Mode** — Token optimization for heavy MCP setups:
|
|
261
|
+
- Replaces 100+ individual MCP tool schemas with 4 meta-tools
|
|
262
|
+
- Reduces tool catalog from ~17,500 tokens to ~700 tokens (**96% reduction**)
|
|
263
|
+
- Enables lazy tool discovery: model queries `mcp_list_tools`, then `mcp_tool_info`, then `mcp_execute`
|
|
264
|
+
- Best for: 50+ MCP tools, long conversations, context-constrained setups
|
|
265
|
+
- Trade-off: 3 sequential calls instead of 1 (adds ~2-3s latency)
|
|
258
266
|
|
|
259
267
|
```bash
|
|
260
|
-
CODE_MODE_ENABLED=true
|
|
268
|
+
CODE_MODE_ENABLED=true # Enable Code Mode
|
|
269
|
+
CODE_MODE_CACHE_TTL=60000 # Tool list cache TTL (ms)
|
|
261
270
|
```
|
|
262
271
|
|
|
272
|
+
See [Token Optimization Guide](documentation/token-optimization.md#phase-0-mcp-code-mode-96-reduction-for-mcp-tools) and [Tools Documentation](documentation/tools.md#mcp-code-mode-token-optimization) for details.
|
|
273
|
+
|
|
263
274
|
---
|
|
264
275
|
|
|
265
276
|
## Deployment Options
|
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const path = require("path");
|
|
3
4
|
const pkg = require('../package.json');
|
|
4
5
|
|
|
6
|
+
// Subcommands. Dispatched before server boot so `lynkr usage` / `lynkr trajectory`
|
|
7
|
+
// don't start the proxy. Add new subcommands here, not in scattered binaries.
|
|
8
|
+
const SUBCOMMANDS = {
|
|
9
|
+
usage: path.join(__dirname, "lynkr-usage.js"),
|
|
10
|
+
trajectory: path.join(__dirname, "lynkr-trajectory.js"),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const sub = process.argv[2];
|
|
14
|
+
if (sub && Object.prototype.hasOwnProperty.call(SUBCOMMANDS, sub)) {
|
|
15
|
+
process.argv.splice(2, 1); // drop the subcommand token so the script's own arg parser is happy
|
|
16
|
+
require(SUBCOMMANDS[sub]);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
6
21
|
console.log(pkg.version);
|
|
7
22
|
process.exit(0);
|
|
@@ -14,7 +29,9 @@ ${pkg.name} v${pkg.version}
|
|
|
14
29
|
${pkg.description}
|
|
15
30
|
|
|
16
31
|
Usage:
|
|
17
|
-
lynkr [options]
|
|
32
|
+
lynkr [options] Start the proxy server (default)
|
|
33
|
+
lynkr usage [options] Show AI spend report and tier-routing savings
|
|
34
|
+
lynkr trajectory [options] Export agent trajectories as JSONL training data
|
|
18
35
|
|
|
19
36
|
Options:
|
|
20
37
|
-h, --help Show this help message
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
/**
|
|
4
|
+
* lynkr trajectory — export agent trajectories from the session DB
|
|
5
|
+
* as JSONL training data.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* lynkr trajectory # stdout, last 30 days
|
|
9
|
+
* lynkr trajectory --since 7d # last 7 days
|
|
10
|
+
* lynkr trajectory --output trajectories.jsonl # write to file
|
|
11
|
+
* lynkr trajectory --tier COMPLEX # only complex sessions
|
|
12
|
+
* lynkr trajectory --anonymize # strip PII / paths / secrets
|
|
13
|
+
* lynkr trajectory --count # just print the row count
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
process.env.WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || path.resolve(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
const compressor = require("../src/training/trajectory-compressor");
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const opts = { since: "30d", anonymize: false, output: "-", count: false };
|
|
24
|
+
for (let i = 2; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
const next = argv[i + 1];
|
|
27
|
+
if (a === "--since" && next) {
|
|
28
|
+
opts.since = next;
|
|
29
|
+
i++;
|
|
30
|
+
} else if (a === "--days" && next) {
|
|
31
|
+
opts.since = `${parseInt(next, 10)}d`;
|
|
32
|
+
i++;
|
|
33
|
+
} else if (a === "--tier" && next) {
|
|
34
|
+
opts.tier = next.toUpperCase();
|
|
35
|
+
i++;
|
|
36
|
+
} else if (a === "--output" && next) {
|
|
37
|
+
opts.output = next;
|
|
38
|
+
i++;
|
|
39
|
+
} else if (a === "-o" && next) {
|
|
40
|
+
opts.output = next;
|
|
41
|
+
i++;
|
|
42
|
+
} else if (a === "--anonymize" || a === "--anonymise") {
|
|
43
|
+
opts.anonymize = true;
|
|
44
|
+
} else if (a === "--count") {
|
|
45
|
+
opts.count = true;
|
|
46
|
+
} else if (a === "--help" || a === "-h") {
|
|
47
|
+
printHelp();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} else if (a === "--format" && next) {
|
|
50
|
+
// Reserved for future formats. Only "jsonl" is supported today.
|
|
51
|
+
if (next !== "jsonl") {
|
|
52
|
+
console.error(`Unsupported --format: ${next}. Only 'jsonl' is supported.`);
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return opts;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printHelp() {
|
|
62
|
+
console.log(`Lynkr trajectory exporter — emit JSONL training samples from session history.
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
lynkr trajectory [options]
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
--since <window> "7d", "30d", ISO date, or epoch ms (default: 30d)
|
|
69
|
+
--days N Shorthand for --since Nd
|
|
70
|
+
--tier <tier> Filter to one tier: SIMPLE, MEDIUM, COMPLEX, REASONING
|
|
71
|
+
--output, -o <path> Output file (default: stdout, "-")
|
|
72
|
+
--anonymize Strip PII, file paths, API keys, hostnames
|
|
73
|
+
--count Print only the row count, no output
|
|
74
|
+
--format jsonl Output format (only jsonl supported)
|
|
75
|
+
-h, --help Show this help
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
lynkr trajectory --days 7 --output last-week.jsonl
|
|
79
|
+
lynkr trajectory --tier COMPLEX --anonymize -o complex-anon.jsonl
|
|
80
|
+
lynkr trajectory --count
|
|
81
|
+
|
|
82
|
+
Output format (one JSON object per line):
|
|
83
|
+
{
|
|
84
|
+
"session_id": "...",
|
|
85
|
+
"messages": [{"role": "user", "content": "..."}, ...],
|
|
86
|
+
"tool_calls": [...],
|
|
87
|
+
"outcome": "success" | "error",
|
|
88
|
+
"tier": "MEDIUM",
|
|
89
|
+
"complexity_score": 38,
|
|
90
|
+
"model_used": "gpt-4o",
|
|
91
|
+
"provider_used": "azure-openai",
|
|
92
|
+
"tokens_in": 1234,
|
|
93
|
+
"tokens_out": 456,
|
|
94
|
+
"latency_ms": 2400,
|
|
95
|
+
"started_at": "...",
|
|
96
|
+
"ended_at": "..."
|
|
97
|
+
}
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fmtInt(n) {
|
|
102
|
+
return new Intl.NumberFormat("en-US").format(n || 0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function main() {
|
|
106
|
+
const opts = parseArgs(process.argv);
|
|
107
|
+
|
|
108
|
+
if (opts.count) {
|
|
109
|
+
// Quick path — stream-walk the sessions and just count valid trajectories.
|
|
110
|
+
let count = 0;
|
|
111
|
+
compressor.exportJsonl({
|
|
112
|
+
...opts,
|
|
113
|
+
output: { write: () => count++, end: () => {} },
|
|
114
|
+
});
|
|
115
|
+
console.log(`${fmtInt(count)} trajectories`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const isStdout = opts.output === "-";
|
|
120
|
+
const start = Date.now();
|
|
121
|
+
const result = compressor.exportJsonl({
|
|
122
|
+
since: opts.since,
|
|
123
|
+
tier: opts.tier,
|
|
124
|
+
anonymize: opts.anonymize,
|
|
125
|
+
output: opts.output,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!isStdout) {
|
|
129
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`Exported ${fmtInt(result.count)} trajectories to ${result.output} in ${elapsed}s\n`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main();
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
/**
|
|
4
|
+
* lynkr usage — print AI spend report from routing telemetry.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* lynkr-usage # last 30 days
|
|
8
|
+
* lynkr-usage --days 7
|
|
9
|
+
* lynkr-usage --window 1d
|
|
10
|
+
* lynkr-usage --window all
|
|
11
|
+
* lynkr-usage --json # machine-readable
|
|
12
|
+
* lynkr-usage --flagship gpt-5 # alternative comparison model
|
|
13
|
+
* lynkr-usage --provider moonshot # filter to one provider
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
// Make sure config/logger pick up the workspace root
|
|
19
|
+
process.env.WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || path.resolve(__dirname, "..");
|
|
20
|
+
|
|
21
|
+
const aggregator = require("../src/usage/aggregator");
|
|
22
|
+
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const opts = { window: "30d", json: false };
|
|
25
|
+
for (let i = 2; i < argv.length; i++) {
|
|
26
|
+
const a = argv[i];
|
|
27
|
+
const next = argv[i + 1];
|
|
28
|
+
if (a === "--json") opts.json = true;
|
|
29
|
+
else if (a === "--days" && next) {
|
|
30
|
+
opts.window = `${parseInt(next, 10)}d`;
|
|
31
|
+
i++;
|
|
32
|
+
} else if (a === "--window" && next) {
|
|
33
|
+
opts.window = next;
|
|
34
|
+
i++;
|
|
35
|
+
} else if (a === "--since" && next) {
|
|
36
|
+
opts.window = next;
|
|
37
|
+
i++;
|
|
38
|
+
} else if (a === "--flagship" && next) {
|
|
39
|
+
opts.flagship = next;
|
|
40
|
+
i++;
|
|
41
|
+
} else if (a === "--provider" && next) {
|
|
42
|
+
opts.provider = next;
|
|
43
|
+
i++;
|
|
44
|
+
} else if (a === "--model" && next) {
|
|
45
|
+
opts.model = next;
|
|
46
|
+
i++;
|
|
47
|
+
} else if (a === "--help" || a === "-h") {
|
|
48
|
+
printHelp();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return opts;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printHelp() {
|
|
56
|
+
console.log(`Lynkr usage report — show AI spend and tier-routing savings.
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
lynkr usage [options]
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
--days N Window in days (e.g. --days 7)
|
|
63
|
+
--window <preset> Window preset: 1d, 7d, 30d, all (default: 30d)
|
|
64
|
+
--since <iso> Custom start time (ISO 8601 or epoch ms)
|
|
65
|
+
--flagship <model> Comparison model for "savings" math (default: claude-sonnet-4-5-20250929)
|
|
66
|
+
--provider <name> Filter to a single provider
|
|
67
|
+
--model <id> Filter to a single model
|
|
68
|
+
--json Print as JSON instead of a formatted table
|
|
69
|
+
-h, --help Show this help
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
lynkr usage
|
|
73
|
+
lynkr usage --days 7
|
|
74
|
+
lynkr usage --window all --json
|
|
75
|
+
`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const C = {
|
|
79
|
+
reset: "\x1b[0m",
|
|
80
|
+
dim: "\x1b[2m",
|
|
81
|
+
bold: "\x1b[1m",
|
|
82
|
+
green: "\x1b[32m",
|
|
83
|
+
yellow: "\x1b[33m",
|
|
84
|
+
cyan: "\x1b[36m",
|
|
85
|
+
red: "\x1b[31m",
|
|
86
|
+
gray: "\x1b[90m",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function colour(text, code) {
|
|
90
|
+
if (!process.stdout.isTTY) return text;
|
|
91
|
+
return `${code}${text}${C.reset}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function fmtUSD(n) {
|
|
95
|
+
if (!n) return "$0.00";
|
|
96
|
+
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
97
|
+
return `$${n.toFixed(2)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function fmtTokens(n) {
|
|
101
|
+
if (!n) return "0";
|
|
102
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
|
|
103
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
104
|
+
return String(n);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fmtInt(n) {
|
|
108
|
+
return new Intl.NumberFormat("en-US").format(n || 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function pad(s, width, align = "left") {
|
|
112
|
+
s = String(s);
|
|
113
|
+
if (s.length >= width) return s;
|
|
114
|
+
const filler = " ".repeat(width - visibleLength(s));
|
|
115
|
+
return align === "right" ? filler + s : s + filler;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function visibleLength(s) {
|
|
119
|
+
// strip ANSI for column-width math
|
|
120
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function tableRow(cells, widths, aligns) {
|
|
124
|
+
return cells
|
|
125
|
+
.map((c, i) => pad(c, widths[i], aligns[i] || "left"))
|
|
126
|
+
.join(" ");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function printTable(rows, header, widths, aligns) {
|
|
130
|
+
console.log(colour(tableRow(header, widths, aligns), C.bold));
|
|
131
|
+
console.log(colour(widths.map((w) => "─".repeat(w)).join(" "), C.dim));
|
|
132
|
+
for (const row of rows) {
|
|
133
|
+
console.log(tableRow(row, widths, aligns));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function bucketRows(bucket, widths) {
|
|
138
|
+
return Object.entries(bucket)
|
|
139
|
+
.sort((a, b) => b[1].actualCost - a[1].actualCost)
|
|
140
|
+
.map(([key, b]) => [
|
|
141
|
+
key,
|
|
142
|
+
fmtInt(b.requests),
|
|
143
|
+
fmtTokens(b.totalTokens),
|
|
144
|
+
colour(fmtUSD(b.actualCost), C.cyan),
|
|
145
|
+
colour(fmtUSD(b.flagshipCost), C.gray),
|
|
146
|
+
colour(fmtUSD(b.saved), C.green),
|
|
147
|
+
colour(`${b.savedPercent.toFixed(1)}%`, C.green),
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function printReport(usage) {
|
|
152
|
+
const { window, since, flagship, totals, byTier, byProvider, byModel } = usage;
|
|
153
|
+
|
|
154
|
+
const banner = `Lynkr — Usage Report`;
|
|
155
|
+
console.log("");
|
|
156
|
+
console.log(colour(banner, C.bold));
|
|
157
|
+
console.log(
|
|
158
|
+
colour(
|
|
159
|
+
`window: ${window}${since ? ` since: ${since}` : ""} flagship-comparison: ${flagship}`,
|
|
160
|
+
C.dim
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
console.log("");
|
|
164
|
+
|
|
165
|
+
// Summary line
|
|
166
|
+
const headline =
|
|
167
|
+
`${fmtInt(totals.requests)} requests ` +
|
|
168
|
+
`${fmtTokens(totals.totalTokens)} tokens ` +
|
|
169
|
+
`actual ${colour(fmtUSD(totals.actualCost), C.cyan)} ` +
|
|
170
|
+
`flagship-only ${colour(fmtUSD(totals.flagshipCost), C.gray)} ` +
|
|
171
|
+
`saved ${colour(fmtUSD(totals.saved), C.green)} ` +
|
|
172
|
+
colour(`(${totals.savedPercent.toFixed(1)}%)`, C.green);
|
|
173
|
+
console.log(headline);
|
|
174
|
+
if (totals.fallbacks || totals.errors) {
|
|
175
|
+
console.log(
|
|
176
|
+
colour(
|
|
177
|
+
` ${totals.fallbacks} fallback${totals.fallbacks !== 1 ? "s" : ""}, ` +
|
|
178
|
+
`${totals.errors} error${totals.errors !== 1 ? "s" : ""}`,
|
|
179
|
+
C.yellow
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
console.log("");
|
|
184
|
+
|
|
185
|
+
if (totals.requests === 0) {
|
|
186
|
+
console.log(colour("No telemetry yet for this window. Send some requests through Lynkr first.", C.yellow));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const headers = ["", "REQUESTS", "TOKENS", "ACTUAL", "FLAGSHIP", "SAVED", "PCT"];
|
|
191
|
+
const widths = [22, 9, 9, 10, 10, 10, 7];
|
|
192
|
+
const aligns = ["left", "right", "right", "right", "right", "right", "right"];
|
|
193
|
+
|
|
194
|
+
console.log(colour("BY TIER", C.bold));
|
|
195
|
+
printTable(bucketRows(byTier, widths), ["TIER", ...headers.slice(1)], widths, aligns);
|
|
196
|
+
console.log("");
|
|
197
|
+
|
|
198
|
+
console.log(colour("BY PROVIDER", C.bold));
|
|
199
|
+
printTable(bucketRows(byProvider, widths), ["PROVIDER", ...headers.slice(1)], widths, aligns);
|
|
200
|
+
console.log("");
|
|
201
|
+
|
|
202
|
+
console.log(colour("BY MODEL", C.bold));
|
|
203
|
+
printTable(bucketRows(byModel, widths), ["MODEL", ...headers.slice(1)], widths, aligns);
|
|
204
|
+
console.log("");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function main() {
|
|
208
|
+
const opts = parseArgs(process.argv);
|
|
209
|
+
const usage = aggregator.getUsage(opts);
|
|
210
|
+
|
|
211
|
+
if (opts.json) {
|
|
212
|
+
process.stdout.write(JSON.stringify(usage, null, 2) + "\n");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
printReport(usage);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
main();
|
package/funding.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://fundingjson.org/schema/v1.1.0.json",
|
|
3
|
+
"version": "v1.1.0",
|
|
4
|
+
|
|
5
|
+
"entity": {
|
|
6
|
+
"type": "individual",
|
|
7
|
+
"role": "maintainer",
|
|
8
|
+
"name": "Vishal Veera Reddy",
|
|
9
|
+
"email": "veerareddyvishal56@gmail.com",
|
|
10
|
+
"description": "Indian software engineer building open-source AI infrastructure. Sole maintainer of Lynkr, a self-hosted AI gateway that lets developers run any AI coding tool on any LLM provider.",
|
|
11
|
+
"webpageUrl": {
|
|
12
|
+
"url": "https://github.com/vishalveerareddy123"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"projects": [
|
|
17
|
+
{
|
|
18
|
+
"guid": "lynkr",
|
|
19
|
+
"name": "Lynkr",
|
|
20
|
+
"description": "A self-hosted AI gateway that decouples AI coding tools (Claude Code, Cursor, Codex, Cline, jcode, Pi) from their default LLM providers. Lynkr auto-detects the connecting tool, translates between Anthropic and OpenAI request formats, and routes to any of 12+ backends (Ollama, AWS Bedrock, Azure OpenAI, OpenRouter, Databricks, Moonshot, Google Vertex, llama.cpp, LM Studio, and more). A request-complexity classifier sends simple turns to free local models and complex ones to flagship cloud models, cutting per-developer AI bills 60-80% while removing vendor lock-in. Includes tool-result compression, MCP Code Mode (96% token reduction on tool definitions), persistent memory, and tier-based routing — all configured through a single .env file.",
|
|
21
|
+
"webpageUrl": {
|
|
22
|
+
"url": "https://fast-editor.github.io/Lynkr/"
|
|
23
|
+
},
|
|
24
|
+
"repositoryUrl": {
|
|
25
|
+
"url": "https://github.com/Fast-Editor/Lynkr"
|
|
26
|
+
},
|
|
27
|
+
"licenses": ["spdx:Apache-2.0"],
|
|
28
|
+
"tags": [
|
|
29
|
+
"ai",
|
|
30
|
+
"ai-gateway",
|
|
31
|
+
"llm",
|
|
32
|
+
"llm-router",
|
|
33
|
+
"developer-tools",
|
|
34
|
+
"proxy",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"ollama",
|
|
37
|
+
"anthropic",
|
|
38
|
+
"openai"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
|
|
43
|
+
"funding": {
|
|
44
|
+
"channels": [
|
|
45
|
+
{
|
|
46
|
+
"guid": "github-sponsors",
|
|
47
|
+
"type": "payment-provider",
|
|
48
|
+
"address": "https://github.com/sponsors/vishalveerareddy123",
|
|
49
|
+
"description": "Support Lynkr development via GitHub Sponsors."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"guid": "fossunited-grant",
|
|
53
|
+
"type": "other",
|
|
54
|
+
"address": "grants@fossunited.org",
|
|
55
|
+
"description": "FOSS United Foundation grant channel for institutional FOSS funding."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"guid": "bank-transfer",
|
|
59
|
+
"type": "bank",
|
|
60
|
+
"address": "Available on request via the project email.",
|
|
61
|
+
"description": "Direct bank transfer for organisations or grant disbursements."
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
"plans": [
|
|
66
|
+
{
|
|
67
|
+
"guid": "core-maintenance-2026",
|
|
68
|
+
"status": "active",
|
|
69
|
+
"name": "Core maintenance + roadmap (12 months)",
|
|
70
|
+
"description": "Funds full-time work on Lynkr's core gateway: provider-format conversions, tier routing, tool-call translation across 10+ model formats (Minimax, Qwen, GLM, Llama, DeepSeek, Mistral), tool-result compression, persistent memory, MCP Code Mode, observability, tests, and docs. Estimated cost reflects one Indian maintainer working full-time for a year.",
|
|
71
|
+
"amount": 500000,
|
|
72
|
+
"currency": "INR",
|
|
73
|
+
"frequency": "yearly",
|
|
74
|
+
"channels": ["fossunited-grant", "bank-transfer"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"guid": "infra-2026",
|
|
78
|
+
"status": "active",
|
|
79
|
+
"name": "Infrastructure + benchmarks",
|
|
80
|
+
"description": "Funds CI runners, benchmark harness for cost/quality/latency comparisons across providers, public dashboard at lynkr.dev, and self-hosted SearXNG + telemetry mirrors used by Lynkr's web search and routing layers.",
|
|
81
|
+
"amount": 150000,
|
|
82
|
+
"currency": "INR",
|
|
83
|
+
"frequency": "yearly",
|
|
84
|
+
"channels": ["fossunited-grant", "bank-transfer"]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"guid": "community-sponsor",
|
|
88
|
+
"status": "active",
|
|
89
|
+
"name": "Community sponsorship",
|
|
90
|
+
"description": "Recurring small-amount sponsorship from individual developers and small teams who use Lynkr.",
|
|
91
|
+
"amount": 0,
|
|
92
|
+
"currency": "USD",
|
|
93
|
+
"frequency": "monthly",
|
|
94
|
+
"channels": ["github-sponsors"]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"guid": "one-time",
|
|
98
|
+
"status": "active",
|
|
99
|
+
"name": "One-time contribution",
|
|
100
|
+
"description": "Any-amount one-time contribution from users or supporters.",
|
|
101
|
+
"amount": 0,
|
|
102
|
+
"currency": "USD",
|
|
103
|
+
"frequency": "one-time",
|
|
104
|
+
"channels": ["github-sponsors", "bank-transfer"]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
"history": []
|
|
109
|
+
}
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lynkr",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.3",
|
|
4
4
|
"description": "Self-hosted Claude Code & Cursor proxy with Databricks,AWS BedRock,Azure adapters, openrouter, Ollama,llamacpp,LM Studio, workspace tooling, and MCP integration.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dev": "nodemon index.js",
|
|
15
15
|
"lint": "eslint src index.js",
|
|
16
16
|
"test": "npm run test:unit && npm run test:performance",
|
|
17
|
-
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com 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/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",
|
|
17
|
+
"test:unit": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com 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/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",
|
|
18
18
|
"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",
|
|
19
19
|
"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",
|
|
20
20
|
"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",
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"express": "^5.1.0",
|
|
56
56
|
"express-rate-limit": "^8.2.1",
|
|
57
57
|
"fast-glob": "^3.3.2",
|
|
58
|
+
"hnswlib-node": "^3.0.0",
|
|
59
|
+
"js-tiktoken": "^1.0.20",
|
|
58
60
|
"js-yaml": "^4.1.1",
|
|
59
61
|
"openai": "^6.14.0",
|
|
60
62
|
"pino": "^8.17.2",
|