lynkr 9.0.1 → 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/README.md +70 -21
- package/bin/cli.js +34 -4
- package/bin/lynkr-trajectory.js +136 -0
- package/bin/lynkr-usage.js +219 -0
- package/funding.json +110 -0
- package/index.js +7 -3
- package/install.sh +3 -3
- package/lynkr-skill.tar.gz +0 -0
- package/native/Cargo.toml +26 -0
- package/native/index.js +29 -0
- package/native/lynkr-native.node +0 -0
- package/native/src/lib.rs +321 -0
- package/package.json +6 -5
- package/public/dashboard.html +665 -0
- package/src/api/files-multipart.js +30 -0
- package/src/api/files-router.js +81 -0
- package/src/api/middleware/budget.js +19 -1
- package/src/api/middleware/load-shedding.js +17 -0
- package/src/api/openai-router.js +353 -301
- package/src/api/router.js +275 -40
- package/src/cache/prompt.js +13 -0
- package/src/clients/databricks.js +42 -18
- package/src/clients/ollama-utils.js +21 -17
- package/src/clients/openai-format.js +50 -10
- package/src/clients/openrouter-utils.js +42 -37
- package/src/clients/prompt-cache-injection.js +140 -0
- package/src/clients/provider-capabilities.js +41 -0
- package/src/clients/responses-format.js +8 -7
- package/src/clients/standard-tools.js +1 -1
- package/src/clients/xml-tool-extractor.js +307 -0
- package/src/cluster.js +82 -0
- package/src/config/index.js +16 -0
- package/src/context/distill.js +15 -0
- package/src/context/tool-result-compressor.js +563 -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/extractor.js +22 -0
- package/src/memory/search.js +0 -50
- package/src/orchestrator/index.js +163 -204
- package/src/orchestrator/preflight.js +188 -0
- package/src/routing/index.js +64 -32
- package/src/routing/interaction.js +183 -0
- package/src/routing/risk-analyzer.js +194 -0
- package/src/routing/telemetry.js +47 -2
- package/src/server.js +15 -0
- package/src/stores/file-store.js +104 -0
- package/src/stores/response-store.js +25 -0
- package/src/tools/index.js +1 -1
- package/src/tools/smart-selection.js +11 -2
- package/src/tools/web.js +1 -1
- package/src/training/trajectory-compressor.js +266 -0
- package/src/usage/aggregator.js +206 -0
- package/src/utils/markdown-ansi.js +146 -0
- package/.lynkr/telemetry.db +0 -0
- package/.lynkr/telemetry.db-shm +0 -0
- package/.lynkr/telemetry.db-wal +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function parseMultipart(buffer, boundary) {
|
|
2
|
+
const boundaryStr = `--${boundary}`;
|
|
3
|
+
const str = buffer.toString("latin1");
|
|
4
|
+
const parts = str.split(boundaryStr).filter((p) => p.trim() && p.trim() !== "--");
|
|
5
|
+
let file = null;
|
|
6
|
+
let filename = null;
|
|
7
|
+
let mimeType = null;
|
|
8
|
+
let purpose = null;
|
|
9
|
+
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
const headerEnd = part.indexOf("\r\n\r\n");
|
|
12
|
+
if (headerEnd === -1) continue;
|
|
13
|
+
const headers = part.substring(0, headerEnd);
|
|
14
|
+
const body = part.substring(headerEnd + 4).replace(/\r\n$/, "");
|
|
15
|
+
|
|
16
|
+
if (headers.includes('name="purpose"')) {
|
|
17
|
+
purpose = body.trim();
|
|
18
|
+
} else if (headers.includes('name="file"') || headers.includes("filename=")) {
|
|
19
|
+
const fnMatch = headers.match(/filename="([^"]+)"/);
|
|
20
|
+
if (fnMatch) filename = fnMatch[1];
|
|
21
|
+
const ctMatch = headers.match(/Content-Type:\s*(.+)/i);
|
|
22
|
+
if (ctMatch) mimeType = ctMatch[1].trim();
|
|
23
|
+
file = Buffer.from(body, "latin1");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { file, filename, mimeType, purpose };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { parseMultipart };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const logger = require("../logger");
|
|
3
|
+
const fileStore = require("../stores/file-store");
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
const MAX_FILE_SIZE = parseInt(process.env.FILES_MAX_SIZE_MB || "100", 10) * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
router.post("/files", async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let totalSize = 0;
|
|
13
|
+
for await (const chunk of req) {
|
|
14
|
+
totalSize += chunk.length;
|
|
15
|
+
if (totalSize > MAX_FILE_SIZE) {
|
|
16
|
+
return res.status(413).json({ error: { message: `File exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit` } });
|
|
17
|
+
}
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
}
|
|
20
|
+
const buffer = Buffer.concat(chunks);
|
|
21
|
+
|
|
22
|
+
const contentType = req.headers["content-type"] || "";
|
|
23
|
+
let filename = "upload";
|
|
24
|
+
let purpose = "assistants";
|
|
25
|
+
let mimeType = "application/octet-stream";
|
|
26
|
+
|
|
27
|
+
if (contentType.includes("multipart/form-data")) {
|
|
28
|
+
const boundary = contentType.split("boundary=")[1];
|
|
29
|
+
if (boundary) {
|
|
30
|
+
const { parseMultipart } = require("./files-multipart");
|
|
31
|
+
const parsed = parseMultipart(buffer, boundary);
|
|
32
|
+
if (parsed.file) {
|
|
33
|
+
filename = parsed.filename || filename;
|
|
34
|
+
mimeType = parsed.mimeType || mimeType;
|
|
35
|
+
purpose = parsed.purpose || purpose;
|
|
36
|
+
const entry = await fileStore.storeFile(parsed.file, { filename, purpose, mimeType });
|
|
37
|
+
return res.json(entry);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Raw body upload
|
|
43
|
+
mimeType = contentType.split(";")[0].trim() || mimeType;
|
|
44
|
+
filename = req.headers["x-filename"] || filename;
|
|
45
|
+
purpose = req.query.purpose || purpose;
|
|
46
|
+
const entry = await fileStore.storeFile(buffer, { filename, purpose, mimeType });
|
|
47
|
+
res.json(entry);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.error({ err }, "File upload failed");
|
|
50
|
+
res.status(500).json({ error: { message: err.message } });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
router.get("/files", (req, res) => {
|
|
55
|
+
const files = fileStore.listFiles({ purpose: req.query.purpose });
|
|
56
|
+
res.json({ object: "list", data: files });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
router.get("/files/:id", (req, res) => {
|
|
60
|
+
const file = fileStore.getFile(req.params.id);
|
|
61
|
+
if (!file) return res.status(404).json({ error: { message: "File not found" } });
|
|
62
|
+
res.json(file);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
router.get("/files/:id/content", async (req, res) => {
|
|
66
|
+
const file = fileStore.getFile(req.params.id);
|
|
67
|
+
if (!file) return res.status(404).json({ error: { message: "File not found" } });
|
|
68
|
+
const content = await fileStore.getFileContent(req.params.id);
|
|
69
|
+
if (!content) return res.status(404).json({ error: { message: "File content not found" } });
|
|
70
|
+
res.setHeader("Content-Type", file.mime_type);
|
|
71
|
+
res.setHeader("Content-Disposition", `attachment; filename="${file.filename}"`);
|
|
72
|
+
res.send(content);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
router.delete("/files/:id", async (req, res) => {
|
|
76
|
+
const deleted = await fileStore.deleteFile(req.params.id);
|
|
77
|
+
if (!deleted) return res.status(404).json({ error: { message: "File not found" } });
|
|
78
|
+
res.json({ id: req.params.id, object: "file", deleted: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
module.exports = router;
|
|
@@ -57,12 +57,30 @@ function budgetMiddleware(req, res, next) {
|
|
|
57
57
|
}, 'Budget warning: approaching limits');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Attach budget info to request for usage recording later
|
|
61
60
|
req.budgetInfo = {
|
|
62
61
|
userId,
|
|
63
62
|
budgetCheck,
|
|
63
|
+
startTime: Date.now(),
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
// Record usage after response completes
|
|
67
|
+
res.on('finish', () => {
|
|
68
|
+
try {
|
|
69
|
+
const usage = res.locals.usage;
|
|
70
|
+
if (!usage) return;
|
|
71
|
+
budgetManager.recordUsage(userId, req.session?.id || null, {
|
|
72
|
+
tokensInput: usage.prompt_tokens || usage.input_tokens || 0,
|
|
73
|
+
tokensOutput: usage.completion_tokens || usage.output_tokens || 0,
|
|
74
|
+
costUsd: usage.cost_usd || 0,
|
|
75
|
+
model: usage.model || null,
|
|
76
|
+
endpoint: req.path,
|
|
77
|
+
latencyMs: Date.now() - req.budgetInfo.startTime,
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.warn({ err: err.message }, 'Failed to record usage after response');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
66
84
|
next();
|
|
67
85
|
}
|
|
68
86
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const os = require("os");
|
|
1
2
|
const logger = require("../../logger");
|
|
2
3
|
const { ServiceUnavailableError } = require("./error-handling");
|
|
3
4
|
|
|
@@ -55,6 +56,20 @@ class LoadShedder {
|
|
|
55
56
|
return true;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// Check RSS / system memory
|
|
60
|
+
const rssPercent = memUsage.rss / os.totalmem();
|
|
61
|
+
if (rssPercent > this.memoryThreshold) {
|
|
62
|
+
logger.warn(
|
|
63
|
+
{
|
|
64
|
+
rssPercent: (rssPercent * 100).toFixed(2),
|
|
65
|
+
threshold: (this.memoryThreshold * 100).toFixed(2),
|
|
66
|
+
},
|
|
67
|
+
"Load shedding: RSS memory usage exceeded threshold"
|
|
68
|
+
);
|
|
69
|
+
this.cachedOverloadState = true;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
// Check active requests
|
|
59
74
|
if (this.activeRequests > this.activeRequestsThreshold) {
|
|
60
75
|
logger.warn(
|
|
@@ -81,8 +96,10 @@ class LoadShedder {
|
|
|
81
96
|
activeRequests: this.activeRequests,
|
|
82
97
|
totalShed: this.totalShed,
|
|
83
98
|
heapUsedPercent: ((memUsage.heapUsed / memUsage.heapTotal) * 100).toFixed(2),
|
|
99
|
+
rssPercent: ((memUsage.rss / os.totalmem()) * 100).toFixed(2),
|
|
84
100
|
thresholds: {
|
|
85
101
|
heapThreshold: (this.heapThreshold * 100).toFixed(2),
|
|
102
|
+
memoryThreshold: (this.memoryThreshold * 100).toFixed(2),
|
|
86
103
|
activeRequestsThreshold: this.activeRequestsThreshold,
|
|
87
104
|
},
|
|
88
105
|
};
|