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.
Files changed (58) hide show
  1. package/README.md +70 -21
  2. package/bin/cli.js +34 -4
  3. package/bin/lynkr-trajectory.js +136 -0
  4. package/bin/lynkr-usage.js +219 -0
  5. package/funding.json +110 -0
  6. package/index.js +7 -3
  7. package/install.sh +3 -3
  8. package/lynkr-skill.tar.gz +0 -0
  9. package/native/Cargo.toml +26 -0
  10. package/native/index.js +29 -0
  11. package/native/lynkr-native.node +0 -0
  12. package/native/src/lib.rs +321 -0
  13. package/package.json +6 -5
  14. package/public/dashboard.html +665 -0
  15. package/src/api/files-multipart.js +30 -0
  16. package/src/api/files-router.js +81 -0
  17. package/src/api/middleware/budget.js +19 -1
  18. package/src/api/middleware/load-shedding.js +17 -0
  19. package/src/api/openai-router.js +353 -301
  20. package/src/api/router.js +275 -40
  21. package/src/cache/prompt.js +13 -0
  22. package/src/clients/databricks.js +42 -18
  23. package/src/clients/ollama-utils.js +21 -17
  24. package/src/clients/openai-format.js +50 -10
  25. package/src/clients/openrouter-utils.js +42 -37
  26. package/src/clients/prompt-cache-injection.js +140 -0
  27. package/src/clients/provider-capabilities.js +41 -0
  28. package/src/clients/responses-format.js +8 -7
  29. package/src/clients/standard-tools.js +1 -1
  30. package/src/clients/xml-tool-extractor.js +307 -0
  31. package/src/cluster.js +82 -0
  32. package/src/config/index.js +16 -0
  33. package/src/context/distill.js +15 -0
  34. package/src/context/tool-result-compressor.js +563 -0
  35. package/src/dashboard/api.js +170 -0
  36. package/src/dashboard/router.js +13 -0
  37. package/src/headroom/client.js +3 -109
  38. package/src/headroom/index.js +0 -14
  39. package/src/memory/extractor.js +22 -0
  40. package/src/memory/search.js +0 -50
  41. package/src/orchestrator/index.js +163 -204
  42. package/src/orchestrator/preflight.js +188 -0
  43. package/src/routing/index.js +64 -32
  44. package/src/routing/interaction.js +183 -0
  45. package/src/routing/risk-analyzer.js +194 -0
  46. package/src/routing/telemetry.js +47 -2
  47. package/src/server.js +15 -0
  48. package/src/stores/file-store.js +104 -0
  49. package/src/stores/response-store.js +25 -0
  50. package/src/tools/index.js +1 -1
  51. package/src/tools/smart-selection.js +11 -2
  52. package/src/tools/web.js +1 -1
  53. package/src/training/trajectory-compressor.js +266 -0
  54. package/src/usage/aggregator.js +206 -0
  55. package/src/utils/markdown-ansi.js +146 -0
  56. package/.lynkr/telemetry.db +0 -0
  57. package/.lynkr/telemetry.db-shm +0 -0
  58. 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
  };