lynkr 9.7.3 → 9.9.1

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 (80) hide show
  1. package/README.md +63 -25
  2. package/bin/cli.js +16 -1
  3. package/bin/lynkr-init.js +44 -1
  4. package/bin/lynkr-usage.js +78 -0
  5. package/bin/wrap.js +60 -35
  6. package/config/difficulty-anchors.json +22 -0
  7. package/package.json +23 -2
  8. package/scripts/audit-log-reader.js +399 -0
  9. package/scripts/build-eval-set.js +256 -0
  10. package/scripts/calibrate-thresholds.js +38 -157
  11. package/scripts/compact-dictionary.js +204 -0
  12. package/scripts/mine-difficulty-anchors.js +288 -0
  13. package/scripts/test-deduplication.js +448 -0
  14. package/scripts/validate-difficulty-classifier.js +123 -0
  15. package/scripts/validate-intent-anchors.js +186 -0
  16. package/scripts/ws7-anchor-replay.js +108 -0
  17. package/skills/lynkr/SKILL.md +195 -0
  18. package/src/api/middleware/loop-guard.js +87 -0
  19. package/src/api/middleware/request-logging.js +5 -64
  20. package/src/api/middleware/session.js +0 -0
  21. package/src/api/openai-router.js +120 -101
  22. package/src/api/providers-handler.js +27 -2
  23. package/src/api/router.js +467 -125
  24. package/src/budget/index.js +2 -19
  25. package/src/cache/semantic.js +9 -0
  26. package/src/clients/databricks.js +455 -142
  27. package/src/clients/gpt-utils.js +11 -105
  28. package/src/clients/openai-format.js +10 -3
  29. package/src/clients/openrouter-utils.js +49 -24
  30. package/src/clients/prompt-cache-injection.js +1 -0
  31. package/src/clients/provider-capabilities.js +1 -1
  32. package/src/clients/responses-format.js +34 -3
  33. package/src/clients/routing.js +15 -0
  34. package/src/config/index.js +36 -2
  35. package/src/context/gcf.js +275 -0
  36. package/src/context/tool-result-compressor.js +932 -47
  37. package/src/dashboard/api.js +1 -0
  38. package/src/logger/index.js +14 -1
  39. package/src/memory/search.js +12 -40
  40. package/src/memory/tools.js +3 -24
  41. package/src/orchestrator/bypass.js +4 -2
  42. package/src/orchestrator/index.js +120 -85
  43. package/src/routing/affinity-store.js +194 -0
  44. package/src/routing/agentic-detector.js +36 -6
  45. package/src/routing/bandit.js +25 -6
  46. package/src/routing/calibration.js +212 -0
  47. package/src/routing/classifier-setup.js +207 -0
  48. package/src/routing/client-profiles.js +292 -0
  49. package/src/routing/complexity-analyzer.js +88 -15
  50. package/src/routing/deescalator.js +148 -0
  51. package/src/routing/degradation.js +109 -0
  52. package/src/routing/difficulty-classifier.js +219 -0
  53. package/src/routing/feedback.js +157 -0
  54. package/src/routing/index.js +931 -90
  55. package/src/routing/intent-score.js +441 -0
  56. package/src/routing/interaction.js +3 -0
  57. package/src/routing/knn-router.js +70 -21
  58. package/src/routing/model-registry.js +28 -7
  59. package/src/routing/model-tiers.js +25 -2
  60. package/src/routing/reward-pipeline.js +68 -2
  61. package/src/routing/risk-analyzer.js +30 -1
  62. package/src/routing/risk-classifier.js +6 -2
  63. package/src/routing/session-affinity.js +162 -34
  64. package/src/routing/telemetry.js +286 -13
  65. package/src/routing/verifier.js +267 -0
  66. package/src/server.js +86 -21
  67. package/src/sessions/cleanup.js +17 -0
  68. package/src/tools/index.js +1 -15
  69. package/src/tools/smart-selection.js +10 -0
  70. package/src/tools/web-client.js +3 -3
  71. package/.eslintrc.cjs +0 -12
  72. package/benchmark-configs/litellm_config.yaml +0 -86
  73. package/benchmark-configs/lynkr.env +0 -48
  74. package/benchmark-configs/portkey-config.json +0 -60
  75. package/benchmark-configs/portkey-docker.sh +0 -23
  76. package/benchmark-tier-routing.js +0 -449
  77. package/funding.json +0 -110
  78. package/src/api/middleware/validation.js +0 -261
  79. package/src/routing/drift-monitor.js +0 -113
  80. package/src/workers/helpers.js +0 -185
@@ -0,0 +1,275 @@
1
+ const logger = require("../logger");
2
+ const { countTokens } = require("../routing/tokenizer");
3
+
4
+ // GCF (Graph Compact Format) context compression. Drop-in alternative to the
5
+ // TOON adapter (src/context/toon.js): same encode-only, fail-open,
6
+ // read-only-context contract; only the encoder differs. Round-trips losslessly.
7
+ //
8
+ // @blackwell-systems/gcf ships a CommonJS build, so the encoder is resolved with
9
+ // a synchronous require and cached.
10
+
11
+ let cachedEncode;
12
+ let cachedDecode;
13
+ let cachedLoadError;
14
+ let warnedMissingDependency = false;
15
+
16
+ // A clear byte reduction reliably implies a token reduction, so blobs that shrink by
17
+ // at least this margin convert without paying to tokenize both strings. Blobs in the
18
+ // ambiguous zone (or larger) fall through to the exact token comparison.
19
+ const BYTE_FASTPATH_RATIO = 0.9;
20
+
21
+ function normaliseSettings(settings = {}) {
22
+ const minBytesRaw =
23
+ typeof settings.minBytes === "number" ? settings.minBytes : Number.parseInt(settings.minBytes ?? "4096", 10);
24
+ return {
25
+ enabled: settings.enabled === true,
26
+ minBytes: Number.isFinite(minBytesRaw) && minBytesRaw > 0 ? minBytesRaw : 4096,
27
+ failOpen: settings.failOpen !== false,
28
+ logStats: settings.logStats !== false,
29
+ verify: settings.verify !== false,
30
+ };
31
+ }
32
+
33
+ // Order-insensitive for object keys, order-sensitive for arrays: the round-trip
34
+ // check treats two JSON values as equal when they carry the same data.
35
+ function deepEqual(a, b) {
36
+ if (a === b) return true;
37
+ if (a === null || b === null || typeof a !== typeof b) return false;
38
+ if (Array.isArray(a)) {
39
+ if (!Array.isArray(b) || a.length !== b.length) return false;
40
+ return a.every((v, i) => deepEqual(v, b[i]));
41
+ }
42
+ if (typeof a === "object") {
43
+ const ka = Object.keys(a);
44
+ const kb = Object.keys(b);
45
+ if (ka.length !== kb.length) return false;
46
+ return ka.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]));
47
+ }
48
+ return false;
49
+ }
50
+
51
+ function resolveEncodeFn(overrideEncode) {
52
+ if (typeof overrideEncode === "function") return overrideEncode;
53
+ if (cachedEncode !== undefined) return cachedEncode;
54
+ try {
55
+ const mod = require("@blackwell-systems/gcf");
56
+ const fn = mod?.encodeGeneric ?? mod?.default?.encodeGeneric ?? null;
57
+ cachedEncode = typeof fn === "function" ? fn : null;
58
+ cachedLoadError = cachedEncode
59
+ ? null
60
+ : new Error("Missing encodeGeneric() export from @blackwell-systems/gcf");
61
+ } catch (err) {
62
+ cachedEncode = null;
63
+ cachedLoadError = err;
64
+ }
65
+ return cachedEncode;
66
+ }
67
+
68
+ function resolveDecodeFn(overrideDecode) {
69
+ if (typeof overrideDecode === "function") return overrideDecode;
70
+ if (cachedDecode !== undefined) return cachedDecode;
71
+ try {
72
+ const mod = require("@blackwell-systems/gcf");
73
+ const fn = mod?.decodeGeneric ?? mod?.default?.decodeGeneric ?? null;
74
+ cachedDecode = typeof fn === "function" ? fn : null;
75
+ } catch {
76
+ cachedDecode = null;
77
+ }
78
+ return cachedDecode;
79
+ }
80
+
81
+ function looksLikeJsonObjectOrArray(text) {
82
+ if (typeof text !== "string") return false;
83
+ const trimmed = text.trim();
84
+ if (trimmed.length < 2) return false;
85
+ return (
86
+ (trimmed.startsWith("{") && trimmed.endsWith("}")) ||
87
+ (trimmed.startsWith("[") && trimmed.endsWith("]"))
88
+ );
89
+ }
90
+
91
+ function safeJsonParse(text) {
92
+ try {
93
+ return JSON.parse(text);
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ function toGcfString(encodeFn, value) {
100
+ const encoded = encodeFn(value);
101
+ if (typeof encoded === "string") return encoded;
102
+ if (encoded && typeof encoded[Symbol.iterator] === "function") {
103
+ return Array.from(encoded).join("\n");
104
+ }
105
+ return "";
106
+ }
107
+
108
+ function compressStringContent(content, cfg, encodeFn, decodeFn, stats, model) {
109
+ if (typeof content !== "string") return content;
110
+
111
+ const originalBytes = Buffer.byteLength(content, "utf8");
112
+ if (originalBytes < cfg.minBytes) {
113
+ stats.skippedBySize += 1;
114
+ return content;
115
+ }
116
+
117
+ stats.candidateCount += 1;
118
+ if (!looksLikeJsonObjectOrArray(content)) {
119
+ stats.skippedByShape += 1;
120
+ return content;
121
+ }
122
+
123
+ const parsed = safeJsonParse(content);
124
+ if (!parsed || typeof parsed !== "object") {
125
+ stats.skippedByParse += 1;
126
+ return content;
127
+ }
128
+
129
+ const gcfText = toGcfString(encodeFn, parsed);
130
+ if (typeof gcfText !== "string" || gcfText.trim().length === 0) {
131
+ return content;
132
+ }
133
+
134
+ const gcfBytes = Buffer.byteLength(gcfText, "utf8");
135
+
136
+ // Fast path: a clear byte reduction reliably means a token reduction too, so skip
137
+ // the (per-blob, hot-path) token comparison.
138
+ if (gcfBytes <= originalBytes * BYTE_FASTPATH_RATIO) {
139
+ if (!verifiesLossless(parsed, gcfText, cfg, decodeFn, stats)) return content;
140
+ stats.convertedCount += 1;
141
+ stats.originalBytes += originalBytes;
142
+ stats.compressedBytes += gcfBytes;
143
+ return gcfText;
144
+ }
145
+
146
+ // Ambiguous zone (or larger in bytes): compare token counts with the target model's
147
+ // encoding, and keep the original JSON if GCF does not reduce the token count.
148
+ const originalTokens = countTokens(content, model);
149
+ const gcfTokens = countTokens(gcfText, model);
150
+ if (gcfTokens >= originalTokens) {
151
+ stats.skippedByGrowth += 1;
152
+ return content;
153
+ }
154
+
155
+ if (!verifiesLossless(parsed, gcfText, cfg, decodeFn, stats)) return content;
156
+ stats.convertedCount += 1;
157
+ stats.originalBytes += originalBytes;
158
+ stats.compressedBytes += gcfBytes;
159
+ stats.originalTokens += originalTokens;
160
+ stats.compressedTokens += gcfTokens;
161
+ return gcfText;
162
+ }
163
+
164
+ // Round-trip check on a payload we have otherwise decided to convert: decode the
165
+ // encoding and require that it reproduces the input exactly. GCF is lossless by
166
+ // design, so this is insurance rather than an expected path; it makes the
167
+ // compression provably lossless per payload. Returns true (safe to convert) when
168
+ // verification is off or the decoder is unavailable.
169
+ function verifiesLossless(parsed, gcfText, cfg, decodeFn, stats) {
170
+ if (!cfg.verify || typeof decodeFn !== "function") return true;
171
+ let decoded;
172
+ try {
173
+ decoded = decodeFn(gcfText);
174
+ } catch {
175
+ decoded = undefined;
176
+ }
177
+ if (deepEqual(parsed, decoded)) return true;
178
+ stats.skippedByVerify += 1;
179
+ return false;
180
+ }
181
+
182
+ function applyGcfCompression(payload, settings = {}, options = {}) {
183
+ const cfg = normaliseSettings(settings);
184
+ const stats = {
185
+ enabled: cfg.enabled,
186
+ available: true,
187
+ convertedCount: 0,
188
+ candidateCount: 0,
189
+ skippedBySize: 0,
190
+ skippedByShape: 0,
191
+ skippedByParse: 0,
192
+ skippedByGrowth: 0,
193
+ skippedByVerify: 0,
194
+ failureCount: 0,
195
+ originalBytes: 0,
196
+ compressedBytes: 0,
197
+ originalTokens: 0,
198
+ compressedTokens: 0,
199
+ };
200
+
201
+ if (!cfg.enabled) return { payload, stats };
202
+ if (!payload || !Array.isArray(payload.messages) || payload.messages.length === 0) {
203
+ return { payload, stats };
204
+ }
205
+
206
+ // Token counts for the never-grow guard use the target model's encoding when known.
207
+ const model = typeof payload.model === "string" ? payload.model : null;
208
+
209
+ const encodeFn = resolveEncodeFn(options.encode);
210
+ if (typeof encodeFn !== "function") {
211
+ stats.available = false;
212
+ const err = cachedLoadError ?? new Error("GCF encoder unavailable");
213
+ if (!cfg.failOpen) throw err;
214
+ if (!warnedMissingDependency) {
215
+ logger.warn(
216
+ { error: err.message },
217
+ "GCF enabled but encoder dependency is unavailable; falling back to JSON",
218
+ );
219
+ warnedMissingDependency = true;
220
+ }
221
+ return { payload, stats };
222
+ }
223
+
224
+ const decodeFn = cfg.verify ? resolveDecodeFn(options.decode) : null;
225
+
226
+ for (const message of payload.messages) {
227
+ if (!message || typeof message !== "object") continue;
228
+ if (message.role === "tool") continue; // Never mutate machine-executed protocol payloads
229
+ try {
230
+ if (typeof message.content === "string") {
231
+ message.content = compressStringContent(message.content, cfg, encodeFn, decodeFn, stats, model);
232
+ continue;
233
+ }
234
+
235
+ if (!Array.isArray(message.content)) continue;
236
+ for (const block of message.content) {
237
+ if (!block || typeof block !== "object") continue;
238
+
239
+ // Keep protocol blocks untouched. Only compress user-language text fields.
240
+ if (block.type === "text" && typeof block.text === "string") {
241
+ block.text = compressStringContent(block.text, cfg, encodeFn, decodeFn, stats, model);
242
+ continue;
243
+ }
244
+
245
+ if (block.type === "input_text" && typeof block.input_text === "string") {
246
+ block.input_text = compressStringContent(block.input_text, cfg, encodeFn, decodeFn, stats, model);
247
+ }
248
+ }
249
+ } catch (err) {
250
+ stats.failureCount += 1;
251
+ if (!cfg.failOpen) throw err;
252
+ }
253
+ }
254
+
255
+ if (cfg.logStats && (stats.convertedCount > 0 || stats.skippedByVerify > 0)) {
256
+ logger.info(
257
+ {
258
+ convertedCount: stats.convertedCount,
259
+ candidateCount: stats.candidateCount,
260
+ skippedByVerify: stats.skippedByVerify,
261
+ originalBytes: stats.originalBytes,
262
+ compressedBytes: stats.compressedBytes,
263
+ originalTokens: stats.originalTokens,
264
+ compressedTokens: stats.compressedTokens,
265
+ },
266
+ "GCF compression applied to message context",
267
+ );
268
+ }
269
+
270
+ return { payload, stats };
271
+ }
272
+
273
+ module.exports = {
274
+ applyGcfCompression,
275
+ };