lynkr 9.0.2 → 9.1.2
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/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 +2 -2
- package/public/dashboard.html +665 -0
- package/src/api/files-router.js +6 -6
- package/src/api/middleware/budget.js +19 -1
- package/src/api/middleware/load-shedding.js +17 -0
- package/src/api/openai-router.js +1 -1
- package/src/api/router.js +185 -47
- package/src/clients/databricks.js +9 -5
- package/src/clients/openai-format.js +31 -5
- package/src/config/index.js +7 -0
- 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 +62 -5
- package/src/orchestrator/preflight.js +188 -0
- package/src/routing/index.js +61 -0
- package/src/routing/interaction.js +183 -0
- package/src/routing/risk-analyzer.js +194 -0
- package/src/routing/telemetry.js +7 -0
- package/src/server.js +3 -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/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.2",
|
|
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",
|