@cognisos/liminal 2.5.0 → 2.5.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.
- package/dist/bin.js +566 -1120
- package/package.json +4 -3
- package/dist/bin.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
6
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
7
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
8
|
}) : x)(function(x) {
|
|
@@ -14,6 +16,15 @@ var __export = (target, all) => {
|
|
|
14
16
|
for (var name in all)
|
|
15
17
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
18
|
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
17
28
|
|
|
18
29
|
// src/ui/format.ts
|
|
19
30
|
function disableColor() {
|
|
@@ -215,7 +226,7 @@ var init_version = __esm({
|
|
|
215
226
|
"src/version.ts"() {
|
|
216
227
|
"use strict";
|
|
217
228
|
init_format();
|
|
218
|
-
VERSION = true ? "2.5.
|
|
229
|
+
VERSION = true ? "2.5.1" : "2.5.0";
|
|
219
230
|
BANNER_LINES = [
|
|
220
231
|
" ___ ___ _____ ______ ___ ________ ________ ___",
|
|
221
232
|
"|\\ \\ |\\ \\|\\ _ \\ _ \\|\\ \\|\\ ___ \\|\\ __ \\|\\ \\",
|
|
@@ -659,74 +670,6 @@ var init_prompts = __esm({
|
|
|
659
670
|
}
|
|
660
671
|
});
|
|
661
672
|
|
|
662
|
-
// src/rsc/pipeline.ts
|
|
663
|
-
var pipeline_exports = {};
|
|
664
|
-
__export(pipeline_exports, {
|
|
665
|
-
RSCPipelineWrapper: () => RSCPipelineWrapper
|
|
666
|
-
});
|
|
667
|
-
import {
|
|
668
|
-
CompressionPipeline,
|
|
669
|
-
RSCTransport,
|
|
670
|
-
RSCEventEmitter,
|
|
671
|
-
Session,
|
|
672
|
-
CircuitBreaker
|
|
673
|
-
} from "@cognisos/rsc-sdk";
|
|
674
|
-
var RSCPipelineWrapper;
|
|
675
|
-
var init_pipeline = __esm({
|
|
676
|
-
"src/rsc/pipeline.ts"() {
|
|
677
|
-
"use strict";
|
|
678
|
-
RSCPipelineWrapper = class {
|
|
679
|
-
pipeline;
|
|
680
|
-
session;
|
|
681
|
-
events;
|
|
682
|
-
transport;
|
|
683
|
-
circuitBreaker;
|
|
684
|
-
constructor(config) {
|
|
685
|
-
this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
|
|
686
|
-
this.transport = new RSCTransport({
|
|
687
|
-
baseUrl: config.rscBaseUrl,
|
|
688
|
-
apiKey: config.rscApiKey,
|
|
689
|
-
timeout: 3e4,
|
|
690
|
-
maxRetries: 3,
|
|
691
|
-
circuitBreaker: this.circuitBreaker
|
|
692
|
-
});
|
|
693
|
-
this.events = new RSCEventEmitter();
|
|
694
|
-
this.session = new Session(config.sessionId);
|
|
695
|
-
this.pipeline = new CompressionPipeline(
|
|
696
|
-
this.transport,
|
|
697
|
-
{
|
|
698
|
-
threshold: config.compressionThreshold,
|
|
699
|
-
learnFromResponses: config.learnFromResponses,
|
|
700
|
-
latencyBudgetMs: config.latencyBudgetMs,
|
|
701
|
-
sessionId: this.session.sessionId
|
|
702
|
-
},
|
|
703
|
-
this.events
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
async healthCheck() {
|
|
707
|
-
try {
|
|
708
|
-
await this.transport.get("/health");
|
|
709
|
-
return true;
|
|
710
|
-
} catch {
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
getSessionSummary() {
|
|
715
|
-
return this.session.getSummary();
|
|
716
|
-
}
|
|
717
|
-
getCircuitState() {
|
|
718
|
-
return this.circuitBreaker.getState();
|
|
719
|
-
}
|
|
720
|
-
isCircuitOpen() {
|
|
721
|
-
return this.circuitBreaker.getState() === "open";
|
|
722
|
-
}
|
|
723
|
-
resetCircuitBreaker() {
|
|
724
|
-
this.circuitBreaker.reset();
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
|
|
730
673
|
// src/daemon/lifecycle.ts
|
|
731
674
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
|
|
732
675
|
import { fork } from "child_process";
|
|
@@ -828,10 +771,35 @@ var init_aggregator = __esm({
|
|
|
828
771
|
tools = /* @__PURE__ */ new Map();
|
|
829
772
|
cursorMetrics = null;
|
|
830
773
|
costPerMillionTokens;
|
|
774
|
+
/** Actual input_tokens from Anthropic API responses (verified ground truth) */
|
|
775
|
+
actualInputTokensTotal = 0;
|
|
776
|
+
/** Actual output_tokens from Anthropic API responses */
|
|
777
|
+
actualOutputTokensTotal = 0;
|
|
778
|
+
/** Pre-compression input token count from tokenizer */
|
|
779
|
+
originalInputEstimateTotal = 0;
|
|
780
|
+
/** Verified tokens saved: countTokens(pre) - countTokens(post) */
|
|
781
|
+
verifiedSavedTotal = 0;
|
|
782
|
+
/** Number of API responses with verified usage data */
|
|
783
|
+
verifiedRequestCount = 0;
|
|
831
784
|
constructor(costPerMillionTokens = DEFAULT_COST_PER_MILLION_TOKENS) {
|
|
832
785
|
this.costPerMillionTokens = costPerMillionTokens;
|
|
833
786
|
}
|
|
834
787
|
// ── Recording events ──────────────────────────────────────────────
|
|
788
|
+
/**
|
|
789
|
+
* Record verified token counts from an Anthropic API response.
|
|
790
|
+
*
|
|
791
|
+
* @param actualInput - usage.input_tokens from Anthropic (post-compression ground truth)
|
|
792
|
+
* @param actualOutput - usage.output_tokens from Anthropic (generated tokens ground truth)
|
|
793
|
+
* @param verifiedSaved - tokens saved, computed as countTokens(pre) - countTokens(post) using real tokenizer
|
|
794
|
+
* @param originalInputTokens - token count of full request before compression
|
|
795
|
+
*/
|
|
796
|
+
recordApiUsage(actualInput, actualOutput, verifiedSaved, originalInputTokens) {
|
|
797
|
+
this.actualInputTokensTotal += actualInput;
|
|
798
|
+
this.actualOutputTokensTotal += actualOutput;
|
|
799
|
+
this.originalInputEstimateTotal += originalInputTokens;
|
|
800
|
+
this.verifiedSavedTotal += verifiedSaved;
|
|
801
|
+
this.verifiedRequestCount++;
|
|
802
|
+
}
|
|
835
803
|
recordCompression(toolId, tokensProcessed, tokensSaved, latencyMs) {
|
|
836
804
|
const m = this.getOrCreateTool(toolId);
|
|
837
805
|
m.calls++;
|
|
@@ -899,10 +867,18 @@ var init_aggregator = __esm({
|
|
|
899
867
|
for (const [id, m] of this.tools) {
|
|
900
868
|
byTool[id] = { ...m };
|
|
901
869
|
}
|
|
870
|
+
const verifiedInputSaved = this.verifiedSavedTotal;
|
|
871
|
+
const verifiedSavingsRate = this.originalInputEstimateTotal > 0 ? verifiedInputSaved / this.originalInputEstimateTotal : 0;
|
|
902
872
|
return {
|
|
903
873
|
sessionStartedAt: this.startedAt.toISOString(),
|
|
904
874
|
uptimeMs,
|
|
905
875
|
...totals,
|
|
876
|
+
actualInputTokens: this.actualInputTokensTotal,
|
|
877
|
+
actualOutputTokens: this.actualOutputTokensTotal,
|
|
878
|
+
originalInputEstimate: this.originalInputEstimateTotal,
|
|
879
|
+
verifiedInputSaved,
|
|
880
|
+
verifiedSavingsRate,
|
|
881
|
+
verifiedRequestCount: this.verifiedRequestCount,
|
|
906
882
|
savingsRate: rate,
|
|
907
883
|
contextExtension: this.contextExtension(rate),
|
|
908
884
|
estimatedCostSavedUsd: this.estimatedCostSaved(totals.tokensSaved),
|
|
@@ -1007,6 +983,101 @@ var init_store = __esm({
|
|
|
1007
983
|
}
|
|
1008
984
|
});
|
|
1009
985
|
|
|
986
|
+
// src/cursor/stats.ts
|
|
987
|
+
var stats_exports = {};
|
|
988
|
+
__export(stats_exports, {
|
|
989
|
+
parseCursorHookStats: () => parseCursorHookStats,
|
|
990
|
+
readCursorLogEntries: () => readCursorLogEntries
|
|
991
|
+
});
|
|
992
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync } from "fs";
|
|
993
|
+
import { join as join7 } from "path";
|
|
994
|
+
function parseCursorHookStats(cwd = process.cwd()) {
|
|
995
|
+
const logPath = join7(cwd, ".fabric", "compress.log");
|
|
996
|
+
if (!existsSync10(logPath)) return null;
|
|
997
|
+
let compressions = 0, errors = 0;
|
|
998
|
+
let tokensProcessed = 0, tokensSaved = 0;
|
|
999
|
+
let apiMsSumMs = 0;
|
|
1000
|
+
const files = /* @__PURE__ */ new Set();
|
|
1001
|
+
const content = readFileSync8(logPath, "utf-8").trim();
|
|
1002
|
+
if (!content) return null;
|
|
1003
|
+
for (const line of content.split("\n")) {
|
|
1004
|
+
try {
|
|
1005
|
+
const entry = JSON.parse(line);
|
|
1006
|
+
if (entry.type === "compressed") {
|
|
1007
|
+
compressions++;
|
|
1008
|
+
tokensProcessed += entry.inputTokens ?? Math.ceil((entry.inputSize ?? 0) / 3);
|
|
1009
|
+
tokensSaved += entry.tokensSaved ?? 0;
|
|
1010
|
+
apiMsSumMs += entry.apiMs ?? 0;
|
|
1011
|
+
files.add(entry.file);
|
|
1012
|
+
} else if (entry.type === "error") {
|
|
1013
|
+
errors++;
|
|
1014
|
+
}
|
|
1015
|
+
} catch {
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
let cacheCount = 0;
|
|
1019
|
+
try {
|
|
1020
|
+
cacheCount = countFilesRecursive(join7(cwd, ".fabric", "cache"));
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
files: files.size,
|
|
1025
|
+
compressions,
|
|
1026
|
+
errors,
|
|
1027
|
+
tokensProcessed,
|
|
1028
|
+
tokensSaved,
|
|
1029
|
+
apiMsSumMs,
|
|
1030
|
+
cacheCount
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function readCursorLogEntries(maxEntries = 50, cwd = process.cwd()) {
|
|
1034
|
+
const logPath = join7(cwd, ".fabric", "compress.log");
|
|
1035
|
+
if (!existsSync10(logPath)) return [];
|
|
1036
|
+
const content = readFileSync8(logPath, "utf-8").trim();
|
|
1037
|
+
if (!content) return [];
|
|
1038
|
+
const lines = content.split("\n");
|
|
1039
|
+
const recent = lines.slice(-maxEntries);
|
|
1040
|
+
const entries = [];
|
|
1041
|
+
for (const line of recent) {
|
|
1042
|
+
try {
|
|
1043
|
+
const entry = JSON.parse(line);
|
|
1044
|
+
const ts = entry.ts ? new Date(entry.ts) : /* @__PURE__ */ new Date();
|
|
1045
|
+
const time = ts.toTimeString().slice(0, 8);
|
|
1046
|
+
if (entry.type === "compressed") {
|
|
1047
|
+
const pct = entry.savedPct ?? (entry.inputTokens && entry.tokensSaved ? +(entry.tokensSaved / entry.inputTokens * 100).toFixed(1) : 0);
|
|
1048
|
+
entries.push({
|
|
1049
|
+
ts: entry.ts,
|
|
1050
|
+
line: `[${time}] [CURSOR] ${entry.file} \u2192 ${entry.tokensSaved} tok saved (${pct}%) ${entry.apiMs}ms`
|
|
1051
|
+
});
|
|
1052
|
+
} else if (entry.type === "error") {
|
|
1053
|
+
entries.push({
|
|
1054
|
+
ts: entry.ts,
|
|
1055
|
+
line: `[${time}] [CURSOR] ${entry.file} \u2192 ERROR: ${entry.error ?? "unknown"}`
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return entries;
|
|
1062
|
+
}
|
|
1063
|
+
function countFilesRecursive(dir) {
|
|
1064
|
+
if (!existsSync10(dir)) return 0;
|
|
1065
|
+
let count = 0;
|
|
1066
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1067
|
+
if (entry.isDirectory()) {
|
|
1068
|
+
count += countFilesRecursive(join7(dir, entry.name));
|
|
1069
|
+
} else {
|
|
1070
|
+
count++;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return count;
|
|
1074
|
+
}
|
|
1075
|
+
var init_stats = __esm({
|
|
1076
|
+
"src/cursor/stats.ts"() {
|
|
1077
|
+
"use strict";
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1010
1081
|
// src/ui/screen.ts
|
|
1011
1082
|
var SEQ, Screen;
|
|
1012
1083
|
var init_screen = __esm({
|
|
@@ -1402,15 +1473,39 @@ var init_stats_view = __esm({
|
|
|
1402
1473
|
lines.push(formatRow("Savings Rate", sRate, aRate, colW));
|
|
1403
1474
|
lines.push(formatRow("Context Extension", sExt, aExt, colW));
|
|
1404
1475
|
lines.push(blank());
|
|
1476
|
+
lines.push(divider("Verified (Anthropic API)", w));
|
|
1477
|
+
lines.push(blank());
|
|
1478
|
+
if (health && health.verifiedRequestCount && health.verifiedRequestCount > 0) {
|
|
1479
|
+
const actualIn = health.actualInputTokens ?? 0;
|
|
1480
|
+
const actualOut = health.actualOutputTokens ?? 0;
|
|
1481
|
+
const origEst = health.originalInputEstimate ?? 0;
|
|
1482
|
+
const saved = health.verifiedInputSaved ?? 0;
|
|
1483
|
+
const rate = health.verifiedSavingsRate ?? 0;
|
|
1484
|
+
const reqs = health.verifiedRequestCount;
|
|
1485
|
+
lines.push(formatRow("Input (actual)", formatNum(actualIn), "\u2014", colW));
|
|
1486
|
+
lines.push(formatRow("Input (pre-comp)", formatNum(origEst), "\u2014", colW));
|
|
1487
|
+
lines.push(formatRow("Input Saved", formatNum(saved), "\u2014", colW));
|
|
1488
|
+
lines.push(formatRow("Input Savings Rate", `${(rate * 100).toFixed(1)}%`, "\u2014", colW));
|
|
1489
|
+
lines.push(blank());
|
|
1490
|
+
lines.push(formatRow("Output (actual)", formatNum(actualOut), "\u2014", colW));
|
|
1491
|
+
lines.push(formatRow("Total (in + out)", formatNum(actualIn + actualOut), "\u2014", colW));
|
|
1492
|
+
lines.push(blank());
|
|
1493
|
+
lines.push(formatRow("Verified Requests", String(reqs), "\u2014", colW));
|
|
1494
|
+
} else {
|
|
1495
|
+
lines.push(` ${c.dim}No verified data yet \u2014 waiting for API responses${c.reset}`);
|
|
1496
|
+
}
|
|
1497
|
+
lines.push(blank());
|
|
1405
1498
|
lines.push(divider("Cost Impact", w));
|
|
1406
1499
|
lines.push(blank());
|
|
1407
1500
|
lines.push(hdr);
|
|
1408
1501
|
lines.push(sep);
|
|
1409
1502
|
lines.push(formatRow("Est. Cost Saved", sCost, aCost, colW));
|
|
1410
1503
|
lines.push(blank());
|
|
1504
|
+
let byToolRendered = false;
|
|
1411
1505
|
if (health && health.sessions.length > 0) {
|
|
1412
1506
|
lines.push(divider("By Tool", w));
|
|
1413
1507
|
lines.push(blank());
|
|
1508
|
+
byToolRendered = true;
|
|
1414
1509
|
const byTool = /* @__PURE__ */ new Map();
|
|
1415
1510
|
for (const s of health.sessions) {
|
|
1416
1511
|
const existing = byTool.get(s.connector) ?? { calls: 0, compressed: 0, failed: 0, processed: 0, saved: 0, p95: null };
|
|
@@ -1432,6 +1527,19 @@ var init_stats_view = __esm({
|
|
|
1432
1527
|
lines.push(blank());
|
|
1433
1528
|
}
|
|
1434
1529
|
}
|
|
1530
|
+
if (health?.cursor) {
|
|
1531
|
+
const cur = health.cursor;
|
|
1532
|
+
if (!byToolRendered) {
|
|
1533
|
+
lines.push(divider("By Tool", w));
|
|
1534
|
+
lines.push(blank());
|
|
1535
|
+
}
|
|
1536
|
+
const curPct = cur.tokensProcessed > 0 ? `${(cur.tokensSaved / cur.tokensProcessed * 100).toFixed(1)}%` : "\u2014";
|
|
1537
|
+
const avgMs = cur.compressions > 0 ? Math.round(cur.apiMsSumMs / cur.compressions) : 0;
|
|
1538
|
+
lines.push(`${c.bold}${formatLabel("cursor")}${c.reset}`);
|
|
1539
|
+
lines.push(` Files: ${cur.files} unique (${cur.compressions} compressions${cur.errors > 0 ? `, ${c.red}${cur.errors} errors${c.reset}` : ""}) | Saved: ${formatNum(cur.tokensSaved)} tok (${curPct})`);
|
|
1540
|
+
lines.push(` Cache: ${cur.cacheCount} files | Avg API: ${avgMs}ms/file`);
|
|
1541
|
+
lines.push(blank());
|
|
1542
|
+
}
|
|
1435
1543
|
if (cum.sessionCount > 0) {
|
|
1436
1544
|
lines.push(`${c.dim}${cum.sessionCount} session${cum.sessionCount !== 1 ? "s" : ""} recorded${c.reset}`);
|
|
1437
1545
|
}
|
|
@@ -1473,6 +1581,17 @@ var init_config_view = __esm({
|
|
|
1473
1581
|
});
|
|
1474
1582
|
|
|
1475
1583
|
// src/ui/views/logs-view.ts
|
|
1584
|
+
function getCursorLogs() {
|
|
1585
|
+
if (!_readCursorLogs) {
|
|
1586
|
+
try {
|
|
1587
|
+
const mod = (init_stats(), __toCommonJS(stats_exports));
|
|
1588
|
+
_readCursorLogs = () => mod.readCursorLogEntries(50);
|
|
1589
|
+
} catch {
|
|
1590
|
+
_readCursorLogs = () => [];
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return _readCursorLogs();
|
|
1594
|
+
}
|
|
1476
1595
|
function colorizeLog(line, maxWidth) {
|
|
1477
1596
|
const display = line.length > maxWidth ? line.slice(0, maxWidth - 1) + "\u2026" : line;
|
|
1478
1597
|
const match = display.match(/^\[(\d{2}:\d{2}:\d{2}(?:\.\d+)?)\]\s*(.*)$/);
|
|
@@ -1495,21 +1614,33 @@ function colorizeLog(line, maxWidth) {
|
|
|
1495
1614
|
}
|
|
1496
1615
|
return `${c.dim}${ts} ${rest}${c.reset}`;
|
|
1497
1616
|
}
|
|
1498
|
-
var logsView;
|
|
1617
|
+
var _readCursorLogs, logsView;
|
|
1499
1618
|
var init_logs_view = __esm({
|
|
1500
1619
|
"src/ui/views/logs-view.ts"() {
|
|
1501
1620
|
"use strict";
|
|
1502
1621
|
init_format();
|
|
1503
1622
|
init_layout();
|
|
1623
|
+
_readCursorLogs = null;
|
|
1504
1624
|
logsView = {
|
|
1505
1625
|
id: "logs",
|
|
1506
1626
|
label: "Logs",
|
|
1507
1627
|
render(state, size) {
|
|
1508
1628
|
const lines = [];
|
|
1509
1629
|
const w = Math.max(40, size.cols - 6);
|
|
1510
|
-
lines.push(divider("
|
|
1630
|
+
lines.push(divider("All Logs", w));
|
|
1511
1631
|
lines.push(blank());
|
|
1512
|
-
|
|
1632
|
+
const cursorEntries = getCursorLogs();
|
|
1633
|
+
const allLogs = [];
|
|
1634
|
+
for (const line of state.recentLogs) {
|
|
1635
|
+
const match = line.match(/^\[(\d{2}:\d{2}:\d{2}(?:\.\d+)?)\]/);
|
|
1636
|
+
allLogs.push({ ts: match?.[1] ?? "99:99:99", line, source: "daemon" });
|
|
1637
|
+
}
|
|
1638
|
+
for (const entry of cursorEntries) {
|
|
1639
|
+
allLogs.push({ ts: entry.ts, line: entry.line, source: "cursor" });
|
|
1640
|
+
}
|
|
1641
|
+
const daemonLogs = state.recentLogs;
|
|
1642
|
+
const combined = [...daemonLogs, ...cursorEntries.map((e) => e.line)];
|
|
1643
|
+
if (combined.length === 0) {
|
|
1513
1644
|
if (!state.daemonRunning) {
|
|
1514
1645
|
lines.push(`${c.dim}Daemon not running \u2014 no logs to show.${c.reset}`);
|
|
1515
1646
|
} else {
|
|
@@ -1519,12 +1650,12 @@ var init_logs_view = __esm({
|
|
|
1519
1650
|
return lines;
|
|
1520
1651
|
}
|
|
1521
1652
|
const maxLines = Math.max(5, size.rows - 7);
|
|
1522
|
-
const tail =
|
|
1653
|
+
const tail = combined.slice(-maxLines);
|
|
1523
1654
|
for (const line of tail) {
|
|
1524
1655
|
lines.push(colorizeLog(line, w));
|
|
1525
1656
|
}
|
|
1526
1657
|
lines.push(blank());
|
|
1527
|
-
lines.push(`${c.dim}Showing last ${tail.length} lines \u2014 refreshes every 2s${c.reset}`);
|
|
1658
|
+
lines.push(`${c.dim}Showing last ${tail.length} lines (daemon + cursor) \u2014 refreshes every 2s${c.reset}`);
|
|
1528
1659
|
return lines;
|
|
1529
1660
|
}
|
|
1530
1661
|
};
|
|
@@ -1536,7 +1667,7 @@ var hub_exports = {};
|
|
|
1536
1667
|
__export(hub_exports, {
|
|
1537
1668
|
runHub: () => runHub
|
|
1538
1669
|
});
|
|
1539
|
-
import { existsSync as
|
|
1670
|
+
import { existsSync as existsSync16, statSync as statSync3 } from "fs";
|
|
1540
1671
|
function createInitialState() {
|
|
1541
1672
|
const state = {
|
|
1542
1673
|
health: null,
|
|
@@ -1597,7 +1728,7 @@ async function refreshState(state) {
|
|
|
1597
1728
|
}
|
|
1598
1729
|
function tailLogFile(maxLines) {
|
|
1599
1730
|
try {
|
|
1600
|
-
if (!
|
|
1731
|
+
if (!existsSync16(LOG_FILE)) return [];
|
|
1601
1732
|
const stat = statSync3(LOG_FILE);
|
|
1602
1733
|
const readSize = Math.min(stat.size, 32 * 1024);
|
|
1603
1734
|
if (readSize === 0) return [];
|
|
@@ -1823,8 +1954,8 @@ import { stdin, stdout } from "process";
|
|
|
1823
1954
|
|
|
1824
1955
|
// src/auth/supabase.ts
|
|
1825
1956
|
import { randomBytes, createHash } from "crypto";
|
|
1826
|
-
var SUPABASE_URL = "https://nzcneiyymvgxvttbenhp.supabase.co";
|
|
1827
|
-
var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im56Y25laXl5bXZneHZ0dGJlbmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQwNjQ0MjcsImV4cCI6MjA2OTY0MDQyN30.x3E-zGRadbPMmxRqT_PB_KOi00htKpgeb8GiQa4g2z0";
|
|
1957
|
+
var SUPABASE_URL = process.env.LIMINAL_SUPABASE_URL || "https://nzcneiyymvgxvttbenhp.supabase.co";
|
|
1958
|
+
var SUPABASE_ANON_KEY = process.env.LIMINAL_SUPABASE_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im56Y25laXl5bXZneHZ0dGJlbmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQwNjQ0MjcsImV4cCI6MjA2OTY0MDQyN30.x3E-zGRadbPMmxRqT_PB_KOi00htKpgeb8GiQa4g2z0";
|
|
1828
1959
|
function supabaseHeaders(accessToken) {
|
|
1829
1960
|
const headers = {
|
|
1830
1961
|
"Content-Type": "application/json",
|
|
@@ -1907,18 +2038,37 @@ async function createApiKey(accessToken, userId, source = "cli") {
|
|
|
1907
2038
|
);
|
|
1908
2039
|
const apiKey = `${prefix}${randomBytes(32).toString("hex")}`;
|
|
1909
2040
|
const keyHash = createHash("sha256").update(apiKey).digest("hex");
|
|
2041
|
+
let projectId;
|
|
2042
|
+
let orgId;
|
|
2043
|
+
try {
|
|
2044
|
+
const projRes = await fetch(
|
|
2045
|
+
`${SUPABASE_URL}/rest/v1/projects?id=eq.${userId}&select=id,org_id&limit=1`,
|
|
2046
|
+
{ headers: supabaseHeaders(accessToken), signal: AbortSignal.timeout(1e4) }
|
|
2047
|
+
);
|
|
2048
|
+
if (projRes.ok) {
|
|
2049
|
+
const rows = await projRes.json();
|
|
2050
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
2051
|
+
projectId = rows[0].id;
|
|
2052
|
+
orgId = rows[0].org_id;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
} catch {
|
|
2056
|
+
}
|
|
2057
|
+
const insertPayload = {
|
|
2058
|
+
user_id: userId,
|
|
2059
|
+
key_name: keyName,
|
|
2060
|
+
key_hash: keyHash,
|
|
2061
|
+
is_active: true
|
|
2062
|
+
};
|
|
2063
|
+
if (projectId) insertPayload.project_id = projectId;
|
|
2064
|
+
if (orgId) insertPayload.organization_id = orgId;
|
|
1910
2065
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys`, {
|
|
1911
2066
|
method: "POST",
|
|
1912
2067
|
headers: {
|
|
1913
2068
|
...supabaseHeaders(accessToken),
|
|
1914
2069
|
"Prefer": "return=representation"
|
|
1915
2070
|
},
|
|
1916
|
-
body: JSON.stringify(
|
|
1917
|
-
user_id: userId,
|
|
1918
|
-
key_name: keyName,
|
|
1919
|
-
key_hash: keyHash,
|
|
1920
|
-
is_active: true
|
|
1921
|
-
})
|
|
2071
|
+
body: JSON.stringify(insertPayload)
|
|
1922
2072
|
});
|
|
1923
2073
|
if (!res.ok) {
|
|
1924
2074
|
const body = await res.json().catch(() => ({}));
|
|
@@ -2413,7 +2563,7 @@ import forge from "node-forge";
|
|
|
2413
2563
|
var CA_CERT_PATH = join5(LIMINAL_DIR, "ca.pem");
|
|
2414
2564
|
var CA_KEY_PATH = join5(LIMINAL_DIR, "ca-key.pem");
|
|
2415
2565
|
function generateCA() {
|
|
2416
|
-
const keys = forge.pki.rsa.generateKeyPair(
|
|
2566
|
+
const keys = forge.pki.rsa.generateKeyPair(4096);
|
|
2417
2567
|
const cert = forge.pki.createCertificate();
|
|
2418
2568
|
cert.publicKey = keys.publicKey;
|
|
2419
2569
|
cert.serialNumber = generateSerialNumber();
|
|
@@ -2814,7 +2964,7 @@ var verificationStep = {
|
|
|
2814
2964
|
async execute(ctx) {
|
|
2815
2965
|
console.log(` Running health checks...`);
|
|
2816
2966
|
console.log();
|
|
2817
|
-
const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await
|
|
2967
|
+
const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await import("@cognisos/proxy-core");
|
|
2818
2968
|
const config = loadConfig();
|
|
2819
2969
|
const probe = new RSCPipelineWrapper2({
|
|
2820
2970
|
rscApiKey: config.apiKey,
|
|
@@ -2899,9 +3049,9 @@ async function initCommand() {
|
|
|
2899
3049
|
console.log();
|
|
2900
3050
|
}
|
|
2901
3051
|
async function checkPort(port) {
|
|
2902
|
-
const { createServer
|
|
3052
|
+
const { createServer } = await import("net");
|
|
2903
3053
|
return new Promise((resolve) => {
|
|
2904
|
-
const server =
|
|
3054
|
+
const server = createServer();
|
|
2905
3055
|
server.once("error", () => resolve(false));
|
|
2906
3056
|
server.once("listening", () => {
|
|
2907
3057
|
server.close(() => resolve(true));
|
|
@@ -2924,632 +3074,18 @@ async function logoutCommand() {
|
|
|
2924
3074
|
|
|
2925
3075
|
// src/commands/start.ts
|
|
2926
3076
|
init_loader();
|
|
3077
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
3078
|
+
import { join as join8 } from "path";
|
|
3079
|
+
import { homedir as homedir5 } from "os";
|
|
2927
3080
|
|
|
2928
3081
|
// src/proxy/completions.ts
|
|
2929
|
-
import { RSCCircuitOpenError as RSCCircuitOpenError2 } from "@cognisos/rsc-sdk";
|
|
2930
|
-
|
|
2931
|
-
// src/rsc/message-compressor.ts
|
|
2932
3082
|
import { RSCCircuitOpenError } from "@cognisos/rsc-sdk";
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
let i = 0;
|
|
2940
|
-
let proseBuf = [];
|
|
2941
|
-
function flushProse() {
|
|
2942
|
-
if (proseBuf.length > 0) {
|
|
2943
|
-
segments.push({ type: "prose", text: proseBuf.join("\n") });
|
|
2944
|
-
proseBuf = [];
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
while (i < lines.length) {
|
|
2948
|
-
const line = lines[i];
|
|
2949
|
-
const fenceMatch = matchFenceOpen(line);
|
|
2950
|
-
if (fenceMatch) {
|
|
2951
|
-
if (proseBuf.length > 0) {
|
|
2952
|
-
flushProse();
|
|
2953
|
-
segments[segments.length - 1].text += "\n";
|
|
2954
|
-
}
|
|
2955
|
-
const codeBuf = [line];
|
|
2956
|
-
i++;
|
|
2957
|
-
let closed = false;
|
|
2958
|
-
while (i < lines.length) {
|
|
2959
|
-
codeBuf.push(lines[i]);
|
|
2960
|
-
if (matchFenceClose(lines[i], fenceMatch.char, fenceMatch.length)) {
|
|
2961
|
-
closed = true;
|
|
2962
|
-
i++;
|
|
2963
|
-
break;
|
|
2964
|
-
}
|
|
2965
|
-
i++;
|
|
2966
|
-
}
|
|
2967
|
-
let codeText = codeBuf.join("\n");
|
|
2968
|
-
if (i < lines.length) {
|
|
2969
|
-
codeText += "\n";
|
|
2970
|
-
}
|
|
2971
|
-
segments.push({ type: "code", text: codeText });
|
|
2972
|
-
continue;
|
|
2973
|
-
}
|
|
2974
|
-
if (isIndentedCodeLine(line)) {
|
|
2975
|
-
if (proseBuf.length > 0) {
|
|
2976
|
-
flushProse();
|
|
2977
|
-
segments[segments.length - 1].text += "\n";
|
|
2978
|
-
}
|
|
2979
|
-
const codeBuf = [line];
|
|
2980
|
-
i++;
|
|
2981
|
-
while (i < lines.length) {
|
|
2982
|
-
if (isIndentedCodeLine(lines[i])) {
|
|
2983
|
-
codeBuf.push(lines[i]);
|
|
2984
|
-
i++;
|
|
2985
|
-
} else if (lines[i].trim() === "" && i + 1 < lines.length && isIndentedCodeLine(lines[i + 1])) {
|
|
2986
|
-
codeBuf.push(lines[i]);
|
|
2987
|
-
i++;
|
|
2988
|
-
} else {
|
|
2989
|
-
break;
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
let codeText = codeBuf.join("\n");
|
|
2993
|
-
if (i < lines.length) {
|
|
2994
|
-
codeText += "\n";
|
|
2995
|
-
}
|
|
2996
|
-
segments.push({ type: "code", text: codeText });
|
|
2997
|
-
continue;
|
|
2998
|
-
}
|
|
2999
|
-
if (proseBuf.length > 0) {
|
|
3000
|
-
proseBuf.push(line);
|
|
3001
|
-
} else {
|
|
3002
|
-
proseBuf.push(line);
|
|
3003
|
-
}
|
|
3004
|
-
i++;
|
|
3005
|
-
}
|
|
3006
|
-
if (proseBuf.length > 0) {
|
|
3007
|
-
flushProse();
|
|
3008
|
-
}
|
|
3009
|
-
if (segments.length === 0) {
|
|
3010
|
-
return [{ type: "prose", text }];
|
|
3011
|
-
}
|
|
3012
|
-
return segments;
|
|
3013
|
-
}
|
|
3014
|
-
function matchFenceOpen(line) {
|
|
3015
|
-
const match = line.match(/^( {0,3})((`{3,})|~{3,})(.*)$/);
|
|
3016
|
-
if (!match) return null;
|
|
3017
|
-
const fenceStr = match[2];
|
|
3018
|
-
const char = fenceStr[0];
|
|
3019
|
-
if (char === "`" && match[4] && match[4].includes("`")) return null;
|
|
3020
|
-
return { char, length: fenceStr.length };
|
|
3021
|
-
}
|
|
3022
|
-
function matchFenceClose(line, char, minLength) {
|
|
3023
|
-
const match = line.match(/^( {0,3})((`{3,})|(~{3,}))\s*$/);
|
|
3024
|
-
if (!match) return false;
|
|
3025
|
-
const fenceStr = match[2];
|
|
3026
|
-
return fenceStr[0] === char && fenceStr.length >= minLength;
|
|
3027
|
-
}
|
|
3028
|
-
function isIndentedCodeLine(line) {
|
|
3029
|
-
return (line.startsWith(" ") || line.startsWith(" ")) && line.trim().length > 0;
|
|
3030
|
-
}
|
|
3031
|
-
|
|
3032
|
-
// src/rsc/message-compressor.ts
|
|
3033
|
-
var PASSTHROUGH_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
|
|
3034
|
-
function sanitizeCompressedText(text) {
|
|
3035
|
-
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
3036
|
-
}
|
|
3037
|
-
async function compressMessages(messages, pipeline, session, compressRoles) {
|
|
3038
|
-
let anyCompressed = false;
|
|
3039
|
-
let totalTokensSaved = 0;
|
|
3040
|
-
const compressed = await Promise.all(
|
|
3041
|
-
messages.map(async (msg) => {
|
|
3042
|
-
if (!compressRoles.has(msg.role)) return msg;
|
|
3043
|
-
return compressMessage(msg, pipeline, session, (c2, saved) => {
|
|
3044
|
-
anyCompressed = anyCompressed || c2;
|
|
3045
|
-
totalTokensSaved += saved;
|
|
3046
|
-
});
|
|
3047
|
-
})
|
|
3048
|
-
);
|
|
3049
|
-
return { messages: compressed, anyCompressed, totalTokensSaved };
|
|
3050
|
-
}
|
|
3051
|
-
async function compressConversation(pipeline, session, plan, options = { compressToolResults: true }) {
|
|
3052
|
-
if (!plan.shouldCompress) {
|
|
3053
|
-
return {
|
|
3054
|
-
messages: plan.messages.map((tm) => tm.message),
|
|
3055
|
-
anyCompressed: false,
|
|
3056
|
-
totalTokensSaved: 0
|
|
3057
|
-
};
|
|
3058
|
-
}
|
|
3059
|
-
const log = options.logFn;
|
|
3060
|
-
const threshold = options.compressionThreshold ?? 100;
|
|
3061
|
-
const results = new Array(plan.messages.length);
|
|
3062
|
-
const compressible = [];
|
|
3063
|
-
for (let i = 0; i < plan.messages.length; i++) {
|
|
3064
|
-
const tm = plan.messages[i];
|
|
3065
|
-
const role = tm.message.role;
|
|
3066
|
-
const blockTypes = Array.isArray(tm.message.content) ? tm.message.content.map((b) => b.type).join(",") : "string";
|
|
3067
|
-
if (tm.tier === "hot") {
|
|
3068
|
-
log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 HOT (verbatim)`);
|
|
3069
|
-
results[i] = tm.message;
|
|
3070
|
-
continue;
|
|
3071
|
-
}
|
|
3072
|
-
if (tm.eligibleTokens === 0) {
|
|
3073
|
-
log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (0 eligible tok, skip)`);
|
|
3074
|
-
results[i] = tm.message;
|
|
3075
|
-
continue;
|
|
3076
|
-
}
|
|
3077
|
-
const proseEstimate = estimateProseTokens(tm.message, options.compressToolResults);
|
|
3078
|
-
if (proseEstimate < threshold) {
|
|
3079
|
-
log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${proseEstimate} prose tok after segmentation < ${threshold} threshold, skip)`);
|
|
3080
|
-
results[i] = tm.message;
|
|
3081
|
-
continue;
|
|
3082
|
-
}
|
|
3083
|
-
const { batchText, batchedIndices } = extractBatchableText(tm.message, options.compressToolResults);
|
|
3084
|
-
if (!batchText) {
|
|
3085
|
-
results[i] = tm.message;
|
|
3086
|
-
continue;
|
|
3087
|
-
}
|
|
3088
|
-
log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${tm.eligibleTokens} eligible tok, ~${proseEstimate} prose tok, batching)`);
|
|
3089
|
-
compressible.push({ planIdx: i, tm, batchText, batchedIndices });
|
|
3090
|
-
}
|
|
3091
|
-
if (compressible.length === 0) {
|
|
3092
|
-
return {
|
|
3093
|
-
messages: results,
|
|
3094
|
-
anyCompressed: false,
|
|
3095
|
-
totalTokensSaved: 0
|
|
3096
|
-
};
|
|
3097
|
-
}
|
|
3098
|
-
let anyCompressed = false;
|
|
3099
|
-
let totalTokensSaved = 0;
|
|
3100
|
-
if (options.semaphore) await options.semaphore.acquire(options.semaphoreTimeoutMs);
|
|
3101
|
-
try {
|
|
3102
|
-
const batchSegments = compressible.map((entry) => ({
|
|
3103
|
-
index: entry.planIdx,
|
|
3104
|
-
text: entry.batchText
|
|
3105
|
-
}));
|
|
3106
|
-
const batchResults = await pipeline.normalizeBatch(batchSegments);
|
|
3107
|
-
for (const entry of compressible) {
|
|
3108
|
-
const result = batchResults.get(entry.planIdx);
|
|
3109
|
-
if (!result || result.metrics.skipped) {
|
|
3110
|
-
results[entry.planIdx] = entry.tm.message;
|
|
3111
|
-
continue;
|
|
3112
|
-
}
|
|
3113
|
-
const compressed = sanitizeCompressedText(result.text);
|
|
3114
|
-
const saved = Math.max(0, result.metrics.tokensSaved);
|
|
3115
|
-
if (saved > 0) {
|
|
3116
|
-
anyCompressed = true;
|
|
3117
|
-
totalTokensSaved += saved;
|
|
3118
|
-
}
|
|
3119
|
-
session.recordCompression(result.metrics);
|
|
3120
|
-
results[entry.planIdx] = reassembleMessage(entry.tm.message, compressed, entry.batchedIndices);
|
|
3121
|
-
}
|
|
3122
|
-
} catch (err) {
|
|
3123
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3124
|
-
if (err instanceof RSCCircuitOpenError) {
|
|
3125
|
-
session.recordFailure();
|
|
3126
|
-
log?.(`[COMPRESS-ERROR] Circuit open \u2014 passing through (${errMsg})`);
|
|
3127
|
-
} else {
|
|
3128
|
-
log?.(`[COMPRESS-ERROR] ${errMsg}`);
|
|
3129
|
-
}
|
|
3130
|
-
for (const entry of compressible) {
|
|
3131
|
-
if (!results[entry.planIdx]) {
|
|
3132
|
-
results[entry.planIdx] = entry.tm.message;
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
} finally {
|
|
3136
|
-
if (options.semaphore) options.semaphore.release();
|
|
3137
|
-
}
|
|
3138
|
-
return { messages: results, anyCompressed, totalTokensSaved };
|
|
3139
|
-
}
|
|
3140
|
-
function extractBatchableText(msg, compressToolResults) {
|
|
3141
|
-
const batchedIndices = /* @__PURE__ */ new Set();
|
|
3142
|
-
if (typeof msg.content === "string") {
|
|
3143
|
-
if (msg.content.trim()) {
|
|
3144
|
-
return { batchText: msg.content, batchedIndices };
|
|
3145
|
-
}
|
|
3146
|
-
return { batchText: null, batchedIndices };
|
|
3147
|
-
}
|
|
3148
|
-
if (!Array.isArray(msg.content)) return { batchText: null, batchedIndices };
|
|
3149
|
-
const parts = msg.content;
|
|
3150
|
-
const textSegments = [];
|
|
3151
|
-
for (let i = 0; i < parts.length; i++) {
|
|
3152
|
-
const part = parts[i];
|
|
3153
|
-
if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
|
|
3154
|
-
if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
3155
|
-
textSegments.push(part.text);
|
|
3156
|
-
batchedIndices.add(i);
|
|
3157
|
-
}
|
|
3158
|
-
if (part.type === "tool_result" && compressToolResults) {
|
|
3159
|
-
const extracted = extractToolResultText(part);
|
|
3160
|
-
if (extracted) {
|
|
3161
|
-
textSegments.push(extracted);
|
|
3162
|
-
batchedIndices.add(i);
|
|
3163
|
-
}
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
return {
|
|
3167
|
-
batchText: textSegments.length > 0 ? textSegments.join("\n\n") : null,
|
|
3168
|
-
batchedIndices
|
|
3169
|
-
};
|
|
3170
|
-
}
|
|
3171
|
-
function reassembleMessage(msg, compressedText, batchedIndices) {
|
|
3172
|
-
if (typeof msg.content === "string") {
|
|
3173
|
-
return { ...msg, content: compressedText };
|
|
3174
|
-
}
|
|
3175
|
-
if (!Array.isArray(msg.content)) return msg;
|
|
3176
|
-
const parts = msg.content;
|
|
3177
|
-
const newParts = [];
|
|
3178
|
-
let isFirstEligible = true;
|
|
3179
|
-
for (let i = 0; i < parts.length; i++) {
|
|
3180
|
-
if (!batchedIndices.has(i)) {
|
|
3181
|
-
newParts.push(parts[i]);
|
|
3182
|
-
continue;
|
|
3183
|
-
}
|
|
3184
|
-
if (isFirstEligible) {
|
|
3185
|
-
if (parts[i].type === "text") {
|
|
3186
|
-
newParts.push({ ...parts[i], text: compressedText });
|
|
3187
|
-
} else if (parts[i].type === "tool_result") {
|
|
3188
|
-
newParts.push({ ...parts[i], content: compressedText });
|
|
3189
|
-
}
|
|
3190
|
-
isFirstEligible = false;
|
|
3191
|
-
} else {
|
|
3192
|
-
if (parts[i].type === "tool_result") {
|
|
3193
|
-
newParts.push({ ...parts[i], content: "" });
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
return { ...msg, content: newParts };
|
|
3198
|
-
}
|
|
3199
|
-
function estimateProseTokens(msg, compressToolResults) {
|
|
3200
|
-
const text = extractCompressibleText(msg, compressToolResults);
|
|
3201
|
-
if (!text) return 0;
|
|
3202
|
-
const segments = segmentContent(text);
|
|
3203
|
-
return segments.filter((s) => s.type === "prose").reduce((sum, s) => sum + Math.ceil(s.text.length / 4), 0);
|
|
3204
|
-
}
|
|
3205
|
-
function extractCompressibleText(msg, compressToolResults) {
|
|
3206
|
-
if (typeof msg.content === "string") {
|
|
3207
|
-
return msg.content.trim() ? msg.content : null;
|
|
3208
|
-
}
|
|
3209
|
-
if (!Array.isArray(msg.content)) return null;
|
|
3210
|
-
const parts = msg.content;
|
|
3211
|
-
const textSegments = [];
|
|
3212
|
-
for (const part of parts) {
|
|
3213
|
-
if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
|
|
3214
|
-
if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
3215
|
-
textSegments.push(part.text);
|
|
3216
|
-
}
|
|
3217
|
-
if (part.type === "tool_result" && compressToolResults) {
|
|
3218
|
-
const extracted = extractToolResultText(part);
|
|
3219
|
-
if (extracted) textSegments.push(extracted);
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
return textSegments.length > 0 ? textSegments.join("\n\n") : null;
|
|
3223
|
-
}
|
|
3224
|
-
function extractToolResultText(part) {
|
|
3225
|
-
if (typeof part.content === "string" && part.content.trim()) {
|
|
3226
|
-
return part.content;
|
|
3227
|
-
}
|
|
3228
|
-
if (Array.isArray(part.content)) {
|
|
3229
|
-
const texts = part.content.filter((inner) => inner.type === "text" && typeof inner.text === "string" && inner.text.trim()).map((inner) => inner.text);
|
|
3230
|
-
return texts.length > 0 ? texts.join("\n") : null;
|
|
3231
|
-
}
|
|
3232
|
-
return null;
|
|
3233
|
-
}
|
|
3234
|
-
async function compressMessage(msg, pipeline, session, record, options = { compressToolResults: true }) {
|
|
3235
|
-
if (typeof msg.content === "string") {
|
|
3236
|
-
return compressStringContent(msg, pipeline, session, record);
|
|
3237
|
-
}
|
|
3238
|
-
if (Array.isArray(msg.content)) {
|
|
3239
|
-
return compressArrayContent(msg, pipeline, session, record, options);
|
|
3240
|
-
}
|
|
3241
|
-
return msg;
|
|
3242
|
-
}
|
|
3243
|
-
async function compressStringContent(msg, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
|
|
3244
|
-
const text = msg.content;
|
|
3245
|
-
try {
|
|
3246
|
-
const compressed = await compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs);
|
|
3247
|
-
return { ...msg, content: compressed };
|
|
3248
|
-
} catch (err) {
|
|
3249
|
-
if (err instanceof RSCCircuitOpenError) {
|
|
3250
|
-
session.recordFailure();
|
|
3251
|
-
throw err;
|
|
3252
|
-
}
|
|
3253
|
-
session.recordFailure();
|
|
3254
|
-
return msg;
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
async function compressArrayContent(msg, pipeline, session, record, options = { compressToolResults: true }) {
|
|
3258
|
-
const parts = msg.content;
|
|
3259
|
-
const compressedParts = await Promise.all(
|
|
3260
|
-
parts.map(async (part) => {
|
|
3261
|
-
if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) return part;
|
|
3262
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
3263
|
-
try {
|
|
3264
|
-
const compressed = await compressTextWithSegmentation(part.text, pipeline, session, record);
|
|
3265
|
-
return { ...part, text: compressed };
|
|
3266
|
-
} catch (err) {
|
|
3267
|
-
if (err instanceof RSCCircuitOpenError) {
|
|
3268
|
-
session.recordFailure();
|
|
3269
|
-
throw err;
|
|
3270
|
-
}
|
|
3271
|
-
session.recordFailure();
|
|
3272
|
-
return part;
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
if (part.type === "tool_result" && options.compressToolResults) {
|
|
3276
|
-
return compressToolResult(part, pipeline, session, record);
|
|
3277
|
-
}
|
|
3278
|
-
return part;
|
|
3279
|
-
})
|
|
3280
|
-
);
|
|
3281
|
-
return { ...msg, content: compressedParts };
|
|
3282
|
-
}
|
|
3283
|
-
async function compressToolResult(part, pipeline, session, record) {
|
|
3284
|
-
const content = part.content;
|
|
3285
|
-
if (typeof content === "string") {
|
|
3286
|
-
try {
|
|
3287
|
-
const compressed = await compressTextWithSegmentation(content, pipeline, session, record);
|
|
3288
|
-
return { ...part, content: compressed };
|
|
3289
|
-
} catch (err) {
|
|
3290
|
-
if (err instanceof RSCCircuitOpenError) {
|
|
3291
|
-
session.recordFailure();
|
|
3292
|
-
throw err;
|
|
3293
|
-
}
|
|
3294
|
-
session.recordFailure();
|
|
3295
|
-
return part;
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
if (Array.isArray(content)) {
|
|
3299
|
-
try {
|
|
3300
|
-
const compressedInner = await Promise.all(
|
|
3301
|
-
content.map(async (inner) => {
|
|
3302
|
-
if (inner.type === "text" && typeof inner.text === "string") {
|
|
3303
|
-
try {
|
|
3304
|
-
const compressed = await compressTextWithSegmentation(inner.text, pipeline, session, record);
|
|
3305
|
-
return { ...inner, text: compressed };
|
|
3306
|
-
} catch (err) {
|
|
3307
|
-
if (err instanceof RSCCircuitOpenError) throw err;
|
|
3308
|
-
session.recordFailure();
|
|
3309
|
-
return inner;
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
return inner;
|
|
3313
|
-
})
|
|
3314
|
-
);
|
|
3315
|
-
return { ...part, content: compressedInner };
|
|
3316
|
-
} catch (err) {
|
|
3317
|
-
if (err instanceof RSCCircuitOpenError) {
|
|
3318
|
-
session.recordFailure();
|
|
3319
|
-
throw err;
|
|
3320
|
-
}
|
|
3321
|
-
session.recordFailure();
|
|
3322
|
-
return part;
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
return part;
|
|
3326
|
-
}
|
|
3327
|
-
async function compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
|
|
3328
|
-
const segments = segmentContent(text);
|
|
3329
|
-
const hasCode = segments.some((s) => s.type === "code");
|
|
3330
|
-
if (!hasCode) {
|
|
3331
|
-
if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
|
|
3332
|
-
try {
|
|
3333
|
-
const result = await pipeline.compressForLLM(text);
|
|
3334
|
-
session.recordCompression(result.metrics);
|
|
3335
|
-
const saved = Math.max(0, result.metrics.tokensSaved);
|
|
3336
|
-
record(!result.metrics.skipped, saved);
|
|
3337
|
-
return sanitizeCompressedText(result.text);
|
|
3338
|
-
} finally {
|
|
3339
|
-
if (semaphore) semaphore.release();
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
const parts = await Promise.all(
|
|
3343
|
-
segments.map(async (seg) => {
|
|
3344
|
-
if (seg.type === "code") return seg.text;
|
|
3345
|
-
if (seg.text.trim().length === 0) return seg.text;
|
|
3346
|
-
if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
|
|
3347
|
-
try {
|
|
3348
|
-
const result = await pipeline.compressForLLM(seg.text);
|
|
3349
|
-
session.recordCompression(result.metrics);
|
|
3350
|
-
const saved = Math.max(0, result.metrics.tokensSaved);
|
|
3351
|
-
record(!result.metrics.skipped, saved);
|
|
3352
|
-
return sanitizeCompressedText(result.text);
|
|
3353
|
-
} catch (err) {
|
|
3354
|
-
if (err instanceof RSCCircuitOpenError) throw err;
|
|
3355
|
-
session.recordFailure();
|
|
3356
|
-
return seg.text;
|
|
3357
|
-
} finally {
|
|
3358
|
-
if (semaphore) semaphore.release();
|
|
3359
|
-
}
|
|
3360
|
-
})
|
|
3361
|
-
);
|
|
3362
|
-
return parts.join("");
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
// src/rsc/conversation-analyzer.ts
|
|
3366
|
-
var SKIP_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
|
|
3367
|
-
function estimateTokens(text) {
|
|
3368
|
-
return Math.ceil(text.length / 4);
|
|
3369
|
-
}
|
|
3370
|
-
function estimateBlockTokens(block, compressToolResults) {
|
|
3371
|
-
if (SKIP_BLOCK_TYPES.has(block.type)) return 0;
|
|
3372
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
3373
|
-
return estimateTokens(block.text);
|
|
3374
|
-
}
|
|
3375
|
-
if (block.type === "tool_result" && compressToolResults) {
|
|
3376
|
-
if (typeof block.content === "string") {
|
|
3377
|
-
return estimateTokens(block.content);
|
|
3378
|
-
}
|
|
3379
|
-
if (Array.isArray(block.content)) {
|
|
3380
|
-
return block.content.reduce(
|
|
3381
|
-
(sum, inner) => sum + estimateBlockTokens(inner, compressToolResults),
|
|
3382
|
-
0
|
|
3383
|
-
);
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
return 0;
|
|
3387
|
-
}
|
|
3388
|
-
function estimateMessageTokens(msg, config) {
|
|
3389
|
-
if (!config.compressRoles.has(msg.role)) return 0;
|
|
3390
|
-
if (typeof msg.content === "string") {
|
|
3391
|
-
return estimateTokens(msg.content);
|
|
3392
|
-
}
|
|
3393
|
-
if (Array.isArray(msg.content)) {
|
|
3394
|
-
return msg.content.reduce(
|
|
3395
|
-
(sum, part) => sum + estimateBlockTokens(part, config.compressToolResults),
|
|
3396
|
-
0
|
|
3397
|
-
);
|
|
3398
|
-
}
|
|
3399
|
-
return 0;
|
|
3400
|
-
}
|
|
3401
|
-
function analyzeConversation(messages, config) {
|
|
3402
|
-
const n = messages.length;
|
|
3403
|
-
if (n < 5) {
|
|
3404
|
-
const tiered2 = messages.map((msg, i) => ({
|
|
3405
|
-
index: i,
|
|
3406
|
-
message: msg,
|
|
3407
|
-
tier: "hot",
|
|
3408
|
-
eligibleTokens: estimateMessageTokens(msg, config)
|
|
3409
|
-
}));
|
|
3410
|
-
return {
|
|
3411
|
-
messages: tiered2,
|
|
3412
|
-
totalEligibleTokens: 0,
|
|
3413
|
-
shouldCompress: false,
|
|
3414
|
-
hotCount: n,
|
|
3415
|
-
warmCount: 0,
|
|
3416
|
-
coldCount: 0
|
|
3417
|
-
};
|
|
3418
|
-
}
|
|
3419
|
-
const coldEnd = Math.floor(n * config.coldFraction);
|
|
3420
|
-
const hotStart = n - Math.floor(n * config.hotFraction);
|
|
3421
|
-
let totalEligibleTokens = 0;
|
|
3422
|
-
let hotCount = 0;
|
|
3423
|
-
let warmCount = 0;
|
|
3424
|
-
let coldCount = 0;
|
|
3425
|
-
const tiered = messages.map((msg, i) => {
|
|
3426
|
-
let tier;
|
|
3427
|
-
if (i >= hotStart) {
|
|
3428
|
-
tier = "hot";
|
|
3429
|
-
hotCount++;
|
|
3430
|
-
} else if (i < coldEnd) {
|
|
3431
|
-
tier = "cold";
|
|
3432
|
-
coldCount++;
|
|
3433
|
-
} else {
|
|
3434
|
-
tier = "warm";
|
|
3435
|
-
warmCount++;
|
|
3436
|
-
}
|
|
3437
|
-
const eligibleTokens = estimateMessageTokens(msg, config);
|
|
3438
|
-
if (tier !== "hot") {
|
|
3439
|
-
totalEligibleTokens += eligibleTokens;
|
|
3440
|
-
}
|
|
3441
|
-
return { index: i, message: msg, tier, eligibleTokens };
|
|
3442
|
-
});
|
|
3443
|
-
return {
|
|
3444
|
-
messages: tiered,
|
|
3445
|
-
totalEligibleTokens,
|
|
3446
|
-
shouldCompress: totalEligibleTokens >= config.aggregateThreshold,
|
|
3447
|
-
hotCount,
|
|
3448
|
-
warmCount,
|
|
3449
|
-
coldCount
|
|
3450
|
-
};
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
// src/rsc/learning.ts
|
|
3454
|
-
function createStreamLearningBuffer(pipeline) {
|
|
3455
|
-
let buffer = "";
|
|
3456
|
-
return {
|
|
3457
|
-
/** Append a text delta from an SSE chunk */
|
|
3458
|
-
append(text) {
|
|
3459
|
-
buffer += text;
|
|
3460
|
-
},
|
|
3461
|
-
/** Flush the buffer — triggers fire-and-forget learning */
|
|
3462
|
-
flush() {
|
|
3463
|
-
if (buffer.length > 0) {
|
|
3464
|
-
pipeline.triggerLearning(buffer);
|
|
3465
|
-
buffer = "";
|
|
3466
|
-
}
|
|
3467
|
-
},
|
|
3468
|
-
/** Get current buffer contents (for testing) */
|
|
3469
|
-
getBuffer() {
|
|
3470
|
-
return buffer;
|
|
3471
|
-
}
|
|
3472
|
-
};
|
|
3473
|
-
}
|
|
3474
|
-
|
|
3475
|
-
// src/proxy/streaming.ts
|
|
3476
|
-
async function pipeSSEResponse(upstreamResponse, clientRes, onContentDelta, onComplete, totalTokensSaved = 0) {
|
|
3477
|
-
clientRes.writeHead(200, {
|
|
3478
|
-
"Content-Type": "text/event-stream",
|
|
3479
|
-
"Cache-Control": "no-cache",
|
|
3480
|
-
"Connection": "keep-alive",
|
|
3481
|
-
"Access-Control-Allow-Origin": "*"
|
|
3482
|
-
});
|
|
3483
|
-
const reader = upstreamResponse.body.getReader();
|
|
3484
|
-
const decoder = new TextDecoder();
|
|
3485
|
-
let lineBuf = "";
|
|
3486
|
-
const needsAdjustment = totalTokensSaved > 0;
|
|
3487
|
-
try {
|
|
3488
|
-
while (true) {
|
|
3489
|
-
const { done, value } = await reader.read();
|
|
3490
|
-
if (done) break;
|
|
3491
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
3492
|
-
if (!needsAdjustment) {
|
|
3493
|
-
clientRes.write(chunk);
|
|
3494
|
-
lineBuf += chunk;
|
|
3495
|
-
const lines2 = lineBuf.split("\n");
|
|
3496
|
-
lineBuf = lines2.pop() || "";
|
|
3497
|
-
for (const line of lines2) {
|
|
3498
|
-
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
|
3499
|
-
try {
|
|
3500
|
-
const json = JSON.parse(line.slice(6));
|
|
3501
|
-
const content = json?.choices?.[0]?.delta?.content;
|
|
3502
|
-
if (typeof content === "string") {
|
|
3503
|
-
onContentDelta(content);
|
|
3504
|
-
}
|
|
3505
|
-
} catch {
|
|
3506
|
-
}
|
|
3507
|
-
}
|
|
3508
|
-
}
|
|
3509
|
-
continue;
|
|
3510
|
-
}
|
|
3511
|
-
lineBuf += chunk;
|
|
3512
|
-
const lines = lineBuf.split("\n");
|
|
3513
|
-
lineBuf = lines.pop() || "";
|
|
3514
|
-
let adjusted = false;
|
|
3515
|
-
const outputLines = [];
|
|
3516
|
-
for (const line of lines) {
|
|
3517
|
-
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
|
3518
|
-
try {
|
|
3519
|
-
const json = JSON.parse(line.slice(6));
|
|
3520
|
-
if (json?.usage?.prompt_tokens != null) {
|
|
3521
|
-
json.usage.prompt_tokens += totalTokensSaved;
|
|
3522
|
-
if (json.usage.total_tokens != null) {
|
|
3523
|
-
json.usage.total_tokens += totalTokensSaved;
|
|
3524
|
-
}
|
|
3525
|
-
outputLines.push(`data: ${JSON.stringify(json)}`);
|
|
3526
|
-
adjusted = true;
|
|
3527
|
-
} else {
|
|
3528
|
-
outputLines.push(line);
|
|
3529
|
-
}
|
|
3530
|
-
const content = json?.choices?.[0]?.delta?.content;
|
|
3531
|
-
if (typeof content === "string") {
|
|
3532
|
-
onContentDelta(content);
|
|
3533
|
-
}
|
|
3534
|
-
} catch {
|
|
3535
|
-
outputLines.push(line);
|
|
3536
|
-
}
|
|
3537
|
-
} else {
|
|
3538
|
-
outputLines.push(line);
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
if (adjusted) {
|
|
3542
|
-
const reconstructed = outputLines.join("\n") + "\n";
|
|
3543
|
-
clientRes.write(reconstructed);
|
|
3544
|
-
} else {
|
|
3545
|
-
clientRes.write(chunk);
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
} finally {
|
|
3549
|
-
clientRes.end();
|
|
3550
|
-
onComplete();
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3083
|
+
import {
|
|
3084
|
+
compressConversation,
|
|
3085
|
+
analyzeConversation,
|
|
3086
|
+
createStreamLearningBuffer,
|
|
3087
|
+
pipeSSEResponse
|
|
3088
|
+
} from "@cognisos/proxy-core";
|
|
3553
3089
|
|
|
3554
3090
|
// src/terminology.ts
|
|
3555
3091
|
var TIER_LABELS = {
|
|
@@ -3586,7 +3122,7 @@ function extractBearerToken(req) {
|
|
|
3586
3122
|
if (!auth || !auth.startsWith("Bearer ")) return null;
|
|
3587
3123
|
return auth.slice(7);
|
|
3588
3124
|
}
|
|
3589
|
-
async function handleChatCompletions(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
|
|
3125
|
+
async function handleChatCompletions(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey, stickyCache) {
|
|
3590
3126
|
const request = body;
|
|
3591
3127
|
if (!request.messages || !Array.isArray(request.messages)) {
|
|
3592
3128
|
sendJSON(res, 400, {
|
|
@@ -3645,7 +3181,8 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
|
|
|
3645
3181
|
compressionThreshold: config.compressionThreshold,
|
|
3646
3182
|
logFn: blockLogFn,
|
|
3647
3183
|
semaphore,
|
|
3648
|
-
semaphoreTimeoutMs: config.concurrencyTimeoutMs
|
|
3184
|
+
semaphoreTimeoutMs: config.concurrencyTimeoutMs,
|
|
3185
|
+
stickyCache
|
|
3649
3186
|
}
|
|
3650
3187
|
);
|
|
3651
3188
|
const totalBlocks = batchedCount + skippedCount + hotCount;
|
|
@@ -3663,7 +3200,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
|
|
|
3663
3200
|
logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
|
|
3664
3201
|
}
|
|
3665
3202
|
} catch (err) {
|
|
3666
|
-
if (err instanceof
|
|
3203
|
+
if (err instanceof RSCCircuitOpenError) {
|
|
3667
3204
|
logger.log(formatDegradeLog());
|
|
3668
3205
|
} else {
|
|
3669
3206
|
logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3696,7 +3233,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
|
|
|
3696
3233
|
return;
|
|
3697
3234
|
}
|
|
3698
3235
|
if (request.stream && upstreamResponse.body) {
|
|
3699
|
-
const learningBuffer =
|
|
3236
|
+
const learningBuffer = createStreamLearningBuffer(pipeline.pipeline);
|
|
3700
3237
|
logger.log(formatResponseLog(request.model, totalTokensSaved, true));
|
|
3701
3238
|
await pipeSSEResponse(
|
|
3702
3239
|
upstreamResponse,
|
|
@@ -3727,7 +3264,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
|
|
|
3727
3264
|
setCORSHeaders(res);
|
|
3728
3265
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3729
3266
|
res.end(finalBody);
|
|
3730
|
-
|
|
3267
|
+
{
|
|
3731
3268
|
try {
|
|
3732
3269
|
const parsed = JSON.parse(responseBody);
|
|
3733
3270
|
const content = parsed?.choices?.[0]?.message?.content;
|
|
@@ -3749,103 +3286,13 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
|
|
|
3749
3286
|
}
|
|
3750
3287
|
|
|
3751
3288
|
// src/proxy/messages.ts
|
|
3752
|
-
import { RSCCircuitOpenError as
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
json.message.usage.input_tokens += tokensSaved;
|
|
3760
|
-
return `data: ${JSON.stringify(json)}`;
|
|
3761
|
-
}
|
|
3762
|
-
} catch {
|
|
3763
|
-
}
|
|
3764
|
-
return null;
|
|
3765
|
-
}
|
|
3766
|
-
async function pipeAnthropicSSEResponse(upstreamResponse, clientRes, onContentDelta, onComplete, totalTokensSaved = 0) {
|
|
3767
|
-
clientRes.writeHead(200, {
|
|
3768
|
-
"Content-Type": "text/event-stream",
|
|
3769
|
-
"Cache-Control": "no-cache",
|
|
3770
|
-
"Connection": "keep-alive",
|
|
3771
|
-
"Access-Control-Allow-Origin": "*"
|
|
3772
|
-
});
|
|
3773
|
-
const reader = upstreamResponse.body.getReader();
|
|
3774
|
-
const decoder = new TextDecoder();
|
|
3775
|
-
let lineBuf = "";
|
|
3776
|
-
let currentEvent = "";
|
|
3777
|
-
let usageAdjusted = false;
|
|
3778
|
-
const needsAdjustment = totalTokensSaved > 0;
|
|
3779
|
-
try {
|
|
3780
|
-
while (true) {
|
|
3781
|
-
const { done, value } = await reader.read();
|
|
3782
|
-
if (done) break;
|
|
3783
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
3784
|
-
if (!needsAdjustment || usageAdjusted) {
|
|
3785
|
-
clientRes.write(chunk);
|
|
3786
|
-
lineBuf += chunk;
|
|
3787
|
-
const lines2 = lineBuf.split("\n");
|
|
3788
|
-
lineBuf = lines2.pop() || "";
|
|
3789
|
-
for (const line of lines2) {
|
|
3790
|
-
if (line.startsWith("event: ")) {
|
|
3791
|
-
currentEvent = line.slice(7).trim();
|
|
3792
|
-
} else if (line.startsWith("data: ") && currentEvent === "content_block_delta") {
|
|
3793
|
-
try {
|
|
3794
|
-
const json = JSON.parse(line.slice(6));
|
|
3795
|
-
if (json?.delta?.type === "text_delta" && typeof json.delta.text === "string") {
|
|
3796
|
-
onContentDelta(json.delta.text);
|
|
3797
|
-
}
|
|
3798
|
-
} catch {
|
|
3799
|
-
}
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
continue;
|
|
3803
|
-
}
|
|
3804
|
-
lineBuf += chunk;
|
|
3805
|
-
const lines = lineBuf.split("\n");
|
|
3806
|
-
lineBuf = lines.pop() || "";
|
|
3807
|
-
let adjusted = false;
|
|
3808
|
-
const outputLines = [];
|
|
3809
|
-
for (const line of lines) {
|
|
3810
|
-
if (line.startsWith("event: ")) {
|
|
3811
|
-
currentEvent = line.slice(7).trim();
|
|
3812
|
-
outputLines.push(line);
|
|
3813
|
-
} else if (line.startsWith("data: ") && currentEvent === "message_start" && !usageAdjusted) {
|
|
3814
|
-
const adjustedLine = adjustMessageStartLine(line, totalTokensSaved);
|
|
3815
|
-
if (adjustedLine) {
|
|
3816
|
-
outputLines.push(adjustedLine);
|
|
3817
|
-
usageAdjusted = true;
|
|
3818
|
-
adjusted = true;
|
|
3819
|
-
} else {
|
|
3820
|
-
outputLines.push(line);
|
|
3821
|
-
}
|
|
3822
|
-
} else {
|
|
3823
|
-
outputLines.push(line);
|
|
3824
|
-
if (line.startsWith("data: ") && currentEvent === "content_block_delta") {
|
|
3825
|
-
try {
|
|
3826
|
-
const json = JSON.parse(line.slice(6));
|
|
3827
|
-
if (json?.delta?.type === "text_delta" && typeof json.delta.text === "string") {
|
|
3828
|
-
onContentDelta(json.delta.text);
|
|
3829
|
-
}
|
|
3830
|
-
} catch {
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
if (adjusted) {
|
|
3836
|
-
const reconstructed = outputLines.join("\n") + "\n" + (lineBuf ? "" : "");
|
|
3837
|
-
clientRes.write(reconstructed);
|
|
3838
|
-
} else {
|
|
3839
|
-
clientRes.write(chunk);
|
|
3840
|
-
}
|
|
3841
|
-
}
|
|
3842
|
-
} finally {
|
|
3843
|
-
clientRes.end();
|
|
3844
|
-
onComplete();
|
|
3845
|
-
}
|
|
3846
|
-
}
|
|
3847
|
-
|
|
3848
|
-
// src/proxy/messages.ts
|
|
3289
|
+
import { RSCCircuitOpenError as RSCCircuitOpenError2 } from "@cognisos/rsc-sdk";
|
|
3290
|
+
import {
|
|
3291
|
+
compressConversation as compressConversation2,
|
|
3292
|
+
analyzeConversation as analyzeConversation2,
|
|
3293
|
+
createStreamLearningBuffer as createStreamLearningBuffer2,
|
|
3294
|
+
pipeAnthropicSSEResponse
|
|
3295
|
+
} from "@cognisos/proxy-core";
|
|
3849
3296
|
function setCORSHeaders2(res) {
|
|
3850
3297
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3851
3298
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
@@ -3880,7 +3327,7 @@ function convertCompressedToAnthropic(messages) {
|
|
|
3880
3327
|
content: msg.content
|
|
3881
3328
|
}));
|
|
3882
3329
|
}
|
|
3883
|
-
async function handleAnthropicMessages(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
|
|
3330
|
+
async function handleAnthropicMessages(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey, onUsage, stickyCache) {
|
|
3884
3331
|
const request = body;
|
|
3885
3332
|
if (!request.messages || !Array.isArray(request.messages)) {
|
|
3886
3333
|
sendAnthropicError(res, 400, "invalid_request_error", "messages is required and must be an array");
|
|
@@ -3895,6 +3342,8 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3895
3342
|
sendAnthropicError(res, 401, "authentication_error", "Authentication required (x-api-key or Authorization header)");
|
|
3896
3343
|
return;
|
|
3897
3344
|
}
|
|
3345
|
+
const { countTokens: countTok } = await import("@cognisos/proxy-core");
|
|
3346
|
+
const originalInputTokens = countTok(JSON.stringify(request));
|
|
3898
3347
|
let messages = request.messages;
|
|
3899
3348
|
let anyCompressed = false;
|
|
3900
3349
|
let totalTokensSaved = 0;
|
|
@@ -3903,7 +3352,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3903
3352
|
try {
|
|
3904
3353
|
const compressRoles = new Set(config.compressRoles);
|
|
3905
3354
|
const compressible = convertAnthropicToCompressible(request.messages);
|
|
3906
|
-
const plan =
|
|
3355
|
+
const plan = analyzeConversation2(compressible, {
|
|
3907
3356
|
hotFraction: config.hotFraction,
|
|
3908
3357
|
coldFraction: config.coldFraction,
|
|
3909
3358
|
aggregateThreshold: config.aggregateThreshold,
|
|
@@ -3931,7 +3380,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3931
3380
|
hotCount++;
|
|
3932
3381
|
}
|
|
3933
3382
|
};
|
|
3934
|
-
const result = await
|
|
3383
|
+
const result = await compressConversation2(
|
|
3935
3384
|
pipeline.pipeline,
|
|
3936
3385
|
pipeline.session,
|
|
3937
3386
|
plan,
|
|
@@ -3940,7 +3389,8 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3940
3389
|
compressionThreshold: config.compressionThreshold,
|
|
3941
3390
|
logFn: blockLogFn,
|
|
3942
3391
|
semaphore,
|
|
3943
|
-
semaphoreTimeoutMs: config.concurrencyTimeoutMs
|
|
3392
|
+
semaphoreTimeoutMs: config.concurrencyTimeoutMs,
|
|
3393
|
+
stickyCache
|
|
3944
3394
|
}
|
|
3945
3395
|
);
|
|
3946
3396
|
const totalBlocks = batchedCount + skippedCount + hotCount;
|
|
@@ -3958,7 +3408,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3958
3408
|
logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
|
|
3959
3409
|
}
|
|
3960
3410
|
} catch (err) {
|
|
3961
|
-
if (err instanceof
|
|
3411
|
+
if (err instanceof RSCCircuitOpenError2) {
|
|
3962
3412
|
logger.log(formatDegradeLog());
|
|
3963
3413
|
} else {
|
|
3964
3414
|
logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3968,6 +3418,8 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3968
3418
|
}
|
|
3969
3419
|
const upstreamUrl = `${config.anthropicUpstreamUrl}/v1/messages`;
|
|
3970
3420
|
const upstreamBody = { ...request, messages };
|
|
3421
|
+
const compressedInputTokens = countTok(JSON.stringify(upstreamBody));
|
|
3422
|
+
const verifiedTokensSaved = Math.max(0, originalInputTokens - compressedInputTokens);
|
|
3971
3423
|
const upstreamHeaders = {
|
|
3972
3424
|
...authHeaders,
|
|
3973
3425
|
"anthropic-version": req.headers["anthropic-version"] || "2023-06-01",
|
|
@@ -3996,35 +3448,44 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
3996
3448
|
return;
|
|
3997
3449
|
}
|
|
3998
3450
|
if (request.stream && upstreamResponse.body) {
|
|
3999
|
-
const learningBuffer =
|
|
3451
|
+
const learningBuffer = createStreamLearningBuffer2(pipeline.pipeline);
|
|
4000
3452
|
logger.log(formatResponseLog(request.model, totalTokensSaved, true));
|
|
4001
3453
|
await pipeAnthropicSSEResponse(
|
|
4002
3454
|
upstreamResponse,
|
|
4003
3455
|
res,
|
|
4004
3456
|
(text) => learningBuffer?.append(text),
|
|
4005
3457
|
() => learningBuffer?.flush(),
|
|
4006
|
-
totalTokensSaved
|
|
3458
|
+
totalTokensSaved,
|
|
3459
|
+
onUsage ? (usage2) => {
|
|
3460
|
+
onUsage(usage2, verifiedTokensSaved, originalInputTokens);
|
|
3461
|
+
logger.log(`[TOKENS] input: ${usage2.inputTokens} | output: ${usage2.outputTokens} | verified_saved: ${verifiedTokensSaved} | orig_tok: ${originalInputTokens} | comp_tok: ${compressedInputTokens}`);
|
|
3462
|
+
} : void 0
|
|
4007
3463
|
);
|
|
4008
3464
|
return;
|
|
4009
3465
|
}
|
|
4010
3466
|
const responseBody = await upstreamResponse.text();
|
|
4011
3467
|
logger.log(formatResponseLog(request.model, totalTokensSaved));
|
|
4012
3468
|
let finalBody = responseBody;
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
3469
|
+
try {
|
|
3470
|
+
const parsed = JSON.parse(responseBody);
|
|
3471
|
+
if (parsed?.usage) {
|
|
3472
|
+
const actualInput = (parsed.usage.input_tokens ?? 0) + (parsed.usage.cache_creation_input_tokens ?? 0) + (parsed.usage.cache_read_input_tokens ?? 0);
|
|
3473
|
+
const actualOutput = parsed.usage.output_tokens ?? 0;
|
|
3474
|
+
if (onUsage) {
|
|
3475
|
+
onUsage({ inputTokens: actualInput, outputTokens: actualOutput }, verifiedTokensSaved, originalInputTokens);
|
|
3476
|
+
logger.log(`[TOKENS] input: ${actualInput} | output: ${actualOutput} | verified_saved: ${verifiedTokensSaved} | orig_tok: ${originalInputTokens} | comp_tok: ${compressedInputTokens}`);
|
|
3477
|
+
}
|
|
3478
|
+
if (totalTokensSaved > 0 && parsed.usage.input_tokens != null) {
|
|
4017
3479
|
parsed.usage.input_tokens += totalTokensSaved;
|
|
4018
3480
|
finalBody = JSON.stringify(parsed);
|
|
4019
|
-
logger.log(`[TOKENS] Adjusted input_tokens by +${totalTokensSaved}`);
|
|
4020
3481
|
}
|
|
4021
|
-
} catch {
|
|
4022
3482
|
}
|
|
3483
|
+
} catch {
|
|
4023
3484
|
}
|
|
4024
3485
|
setCORSHeaders2(res);
|
|
4025
3486
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4026
3487
|
res.end(finalBody);
|
|
4027
|
-
|
|
3488
|
+
{
|
|
4028
3489
|
try {
|
|
4029
3490
|
const parsed = JSON.parse(responseBody);
|
|
4030
3491
|
const textBlocks = parsed?.content?.filter(
|
|
@@ -4047,100 +3508,12 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
|
|
|
4047
3508
|
}
|
|
4048
3509
|
|
|
4049
3510
|
// src/proxy/responses.ts
|
|
4050
|
-
import { RSCCircuitOpenError as
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
"Cache-Control": "no-cache",
|
|
4057
|
-
"Connection": "keep-alive",
|
|
4058
|
-
"Access-Control-Allow-Origin": "*"
|
|
4059
|
-
});
|
|
4060
|
-
const reader = upstreamResponse.body.getReader();
|
|
4061
|
-
const decoder = new TextDecoder();
|
|
4062
|
-
let lineBuf = "";
|
|
4063
|
-
let currentEvent = "";
|
|
4064
|
-
let usageAdjusted = false;
|
|
4065
|
-
const needsAdjustment = totalTokensSaved > 0;
|
|
4066
|
-
try {
|
|
4067
|
-
while (true) {
|
|
4068
|
-
const { done, value } = await reader.read();
|
|
4069
|
-
if (done) break;
|
|
4070
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
4071
|
-
if (!needsAdjustment || usageAdjusted) {
|
|
4072
|
-
clientRes.write(chunk);
|
|
4073
|
-
lineBuf += chunk;
|
|
4074
|
-
const lines2 = lineBuf.split("\n");
|
|
4075
|
-
lineBuf = lines2.pop() || "";
|
|
4076
|
-
for (const line of lines2) {
|
|
4077
|
-
if (line.startsWith("event: ")) {
|
|
4078
|
-
currentEvent = line.slice(7).trim();
|
|
4079
|
-
} else if (line.startsWith("data: ") && currentEvent === "response.output_text.delta") {
|
|
4080
|
-
try {
|
|
4081
|
-
const json = JSON.parse(line.slice(6));
|
|
4082
|
-
if (typeof json?.delta === "string") {
|
|
4083
|
-
onContentDelta(json.delta);
|
|
4084
|
-
}
|
|
4085
|
-
} catch {
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
}
|
|
4089
|
-
continue;
|
|
4090
|
-
}
|
|
4091
|
-
lineBuf += chunk;
|
|
4092
|
-
const lines = lineBuf.split("\n");
|
|
4093
|
-
lineBuf = lines.pop() || "";
|
|
4094
|
-
let adjusted = false;
|
|
4095
|
-
const outputLines = [];
|
|
4096
|
-
for (const line of lines) {
|
|
4097
|
-
if (line.startsWith("event: ")) {
|
|
4098
|
-
currentEvent = line.slice(7).trim();
|
|
4099
|
-
outputLines.push(line);
|
|
4100
|
-
} else if (line.startsWith("data: ") && currentEvent === "response.completed" && !usageAdjusted) {
|
|
4101
|
-
try {
|
|
4102
|
-
const json = JSON.parse(line.slice(6));
|
|
4103
|
-
if (json?.response?.usage?.input_tokens != null) {
|
|
4104
|
-
json.response.usage.input_tokens += totalTokensSaved;
|
|
4105
|
-
if (json.response.usage.total_tokens != null) {
|
|
4106
|
-
json.response.usage.total_tokens += totalTokensSaved;
|
|
4107
|
-
}
|
|
4108
|
-
outputLines.push(`data: ${JSON.stringify(json)}`);
|
|
4109
|
-
usageAdjusted = true;
|
|
4110
|
-
adjusted = true;
|
|
4111
|
-
} else {
|
|
4112
|
-
outputLines.push(line);
|
|
4113
|
-
}
|
|
4114
|
-
} catch {
|
|
4115
|
-
outputLines.push(line);
|
|
4116
|
-
}
|
|
4117
|
-
} else {
|
|
4118
|
-
outputLines.push(line);
|
|
4119
|
-
if (line.startsWith("data: ") && currentEvent === "response.output_text.delta") {
|
|
4120
|
-
try {
|
|
4121
|
-
const json = JSON.parse(line.slice(6));
|
|
4122
|
-
if (typeof json?.delta === "string") {
|
|
4123
|
-
onContentDelta(json.delta);
|
|
4124
|
-
}
|
|
4125
|
-
} catch {
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
if (adjusted) {
|
|
4131
|
-
const reconstructed = outputLines.join("\n") + "\n";
|
|
4132
|
-
clientRes.write(reconstructed);
|
|
4133
|
-
} else {
|
|
4134
|
-
clientRes.write(chunk);
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
} finally {
|
|
4138
|
-
clientRes.end();
|
|
4139
|
-
onComplete();
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
|
|
4143
|
-
// src/proxy/responses.ts
|
|
3511
|
+
import { RSCCircuitOpenError as RSCCircuitOpenError3 } from "@cognisos/rsc-sdk";
|
|
3512
|
+
import {
|
|
3513
|
+
compressMessages,
|
|
3514
|
+
createStreamLearningBuffer as createStreamLearningBuffer3,
|
|
3515
|
+
pipeResponsesSSE
|
|
3516
|
+
} from "@cognisos/proxy-core";
|
|
4144
3517
|
function setCORSHeaders3(res) {
|
|
4145
3518
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4146
3519
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
@@ -4282,7 +3655,7 @@ async function handleResponses(req, res, body, pipeline, config, logger, semapho
|
|
|
4282
3655
|
}
|
|
4283
3656
|
}
|
|
4284
3657
|
} catch (err) {
|
|
4285
|
-
if (err instanceof
|
|
3658
|
+
if (err instanceof RSCCircuitOpenError3) {
|
|
4286
3659
|
logger.log(formatDegradeLog());
|
|
4287
3660
|
} else {
|
|
4288
3661
|
logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4317,7 +3690,7 @@ async function handleResponses(req, res, body, pipeline, config, logger, semapho
|
|
|
4317
3690
|
return;
|
|
4318
3691
|
}
|
|
4319
3692
|
if (request.stream && upstreamResponse.body) {
|
|
4320
|
-
const learningBuffer =
|
|
3693
|
+
const learningBuffer = createStreamLearningBuffer3(pipeline.pipeline);
|
|
4321
3694
|
logger.log(formatResponseLog(request.model, totalTokensSaved, true));
|
|
4322
3695
|
await pipeResponsesSSE(
|
|
4323
3696
|
upstreamResponse,
|
|
@@ -4348,7 +3721,7 @@ async function handleResponses(req, res, body, pipeline, config, logger, semapho
|
|
|
4348
3721
|
setCORSHeaders3(res);
|
|
4349
3722
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4350
3723
|
res.end(finalBody);
|
|
4351
|
-
|
|
3724
|
+
{
|
|
4352
3725
|
try {
|
|
4353
3726
|
const parsed = JSON.parse(responseBody);
|
|
4354
3727
|
if (parsed?.output) {
|
|
@@ -4577,6 +3950,11 @@ function createRequestHandler(deps) {
|
|
|
4577
3950
|
}
|
|
4578
3951
|
return;
|
|
4579
3952
|
}
|
|
3953
|
+
if (req.headers["x-liminal-bypass"] === "true") {
|
|
3954
|
+
logger.log(`[PROXY] Bypass: ${method} ${fullUrl}`);
|
|
3955
|
+
await passthroughToUpstream(req, res, fullUrl, config, logger);
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
4580
3958
|
if (method === "GET" && (url === "/health" || url === "/") && !getMitmUpstreamBase(req)) {
|
|
4581
3959
|
const sessionSummaries = sessions.getAllSummaries();
|
|
4582
3960
|
sendJSON3(res, 200, {
|
|
@@ -4604,12 +3982,14 @@ function createRequestHandler(deps) {
|
|
|
4604
3982
|
calls_failed: s.failedCalls,
|
|
4605
3983
|
p95_latency_ms: latencyMonitor.getSessionP95(s.key),
|
|
4606
3984
|
last_active_ago_ms: Date.now() - s.lastAccessedAt
|
|
4607
|
-
}))
|
|
3985
|
+
})),
|
|
3986
|
+
...deps.getVerifiedTokens ? deps.getVerifiedTokens() : {},
|
|
3987
|
+
...deps.getCursorMetrics ? { cursor: deps.getCursorMetrics() } : {}
|
|
4608
3988
|
});
|
|
4609
3989
|
return;
|
|
4610
3990
|
}
|
|
4611
3991
|
const sessionKey = identifySession(req, url);
|
|
4612
|
-
const pipeline = sessions.getOrCreate(sessionKey);
|
|
3992
|
+
const { pipeline, stickyCache } = sessions.getOrCreate(sessionKey);
|
|
4613
3993
|
if (method === "POST" && (url === "/v1/chat/completions" || url === "/chat/completions")) {
|
|
4614
3994
|
const body = await readBody(req);
|
|
4615
3995
|
let parsed;
|
|
@@ -4621,7 +4001,7 @@ function createRequestHandler(deps) {
|
|
|
4621
4001
|
});
|
|
4622
4002
|
return;
|
|
4623
4003
|
}
|
|
4624
|
-
await handleChatCompletions(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
|
|
4004
|
+
await handleChatCompletions(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw, stickyCache);
|
|
4625
4005
|
return;
|
|
4626
4006
|
}
|
|
4627
4007
|
if (method === "POST" && (url === "/v1/responses" || url === "/responses")) {
|
|
@@ -4650,7 +4030,7 @@ function createRequestHandler(deps) {
|
|
|
4650
4030
|
});
|
|
4651
4031
|
return;
|
|
4652
4032
|
}
|
|
4653
|
-
await handleAnthropicMessages(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
|
|
4033
|
+
await handleAnthropicMessages(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw, deps.onUsage, stickyCache);
|
|
4654
4034
|
return;
|
|
4655
4035
|
}
|
|
4656
4036
|
await passthroughToUpstream(req, res, fullUrl, config, logger);
|
|
@@ -4666,72 +4046,8 @@ function createRequestHandler(deps) {
|
|
|
4666
4046
|
};
|
|
4667
4047
|
}
|
|
4668
4048
|
|
|
4669
|
-
// src/
|
|
4670
|
-
import
|
|
4671
|
-
var MAX_PORT_RETRIES = 5;
|
|
4672
|
-
var ProxyServer = class {
|
|
4673
|
-
server = null;
|
|
4674
|
-
activePort = null;
|
|
4675
|
-
requestedPort;
|
|
4676
|
-
handler;
|
|
4677
|
-
connectHandler;
|
|
4678
|
-
constructor(port, handler, connectHandler) {
|
|
4679
|
-
this.requestedPort = port;
|
|
4680
|
-
this.handler = handler;
|
|
4681
|
-
this.connectHandler = connectHandler ?? null;
|
|
4682
|
-
}
|
|
4683
|
-
async start() {
|
|
4684
|
-
let lastError = null;
|
|
4685
|
-
for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
|
|
4686
|
-
const port = this.requestedPort + attempt;
|
|
4687
|
-
try {
|
|
4688
|
-
await this.listen(port);
|
|
4689
|
-
this.activePort = port;
|
|
4690
|
-
return port;
|
|
4691
|
-
} catch (err) {
|
|
4692
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
4693
|
-
if (err.code !== "EADDRINUSE") {
|
|
4694
|
-
throw lastError;
|
|
4695
|
-
}
|
|
4696
|
-
}
|
|
4697
|
-
}
|
|
4698
|
-
throw lastError ?? new Error(`All ports ${this.requestedPort}-${this.requestedPort + MAX_PORT_RETRIES - 1} in use`);
|
|
4699
|
-
}
|
|
4700
|
-
listen(port) {
|
|
4701
|
-
return new Promise((resolve, reject) => {
|
|
4702
|
-
const server = http.createServer(this.handler);
|
|
4703
|
-
if (this.connectHandler) {
|
|
4704
|
-
server.on("connect", this.connectHandler);
|
|
4705
|
-
}
|
|
4706
|
-
server.on("error", reject);
|
|
4707
|
-
server.listen(port, "127.0.0.1", () => {
|
|
4708
|
-
server.removeListener("error", reject);
|
|
4709
|
-
this.server = server;
|
|
4710
|
-
resolve();
|
|
4711
|
-
});
|
|
4712
|
-
});
|
|
4713
|
-
}
|
|
4714
|
-
async stop() {
|
|
4715
|
-
if (!this.server) return;
|
|
4716
|
-
return new Promise((resolve) => {
|
|
4717
|
-
this.server.close(() => {
|
|
4718
|
-
this.server = null;
|
|
4719
|
-
this.activePort = null;
|
|
4720
|
-
resolve();
|
|
4721
|
-
});
|
|
4722
|
-
});
|
|
4723
|
-
}
|
|
4724
|
-
isRunning() {
|
|
4725
|
-
return this.server !== null && this.server.listening;
|
|
4726
|
-
}
|
|
4727
|
-
getPort() {
|
|
4728
|
-
return this.activePort;
|
|
4729
|
-
}
|
|
4730
|
-
/** Expose internal HTTP server for MITM bridge socket injection */
|
|
4731
|
-
getHttpServer() {
|
|
4732
|
-
return this.server;
|
|
4733
|
-
}
|
|
4734
|
-
};
|
|
4049
|
+
// src/commands/start.ts
|
|
4050
|
+
import { ProxyServer } from "@cognisos/proxy-core";
|
|
4735
4051
|
|
|
4736
4052
|
// src/daemon/logger.ts
|
|
4737
4053
|
init_paths();
|
|
@@ -4789,7 +4105,7 @@ var FileLogger = class {
|
|
|
4789
4105
|
};
|
|
4790
4106
|
|
|
4791
4107
|
// src/rsc/session-manager.ts
|
|
4792
|
-
|
|
4108
|
+
import { RSCPipelineWrapper } from "@cognisos/proxy-core";
|
|
4793
4109
|
var DEFAULT_MAX_SESSIONS = 10;
|
|
4794
4110
|
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
4795
4111
|
var EVICTION_INTERVAL_MS = 6e4;
|
|
@@ -4814,7 +4130,7 @@ var SessionManager = class {
|
|
|
4814
4130
|
if (existing) {
|
|
4815
4131
|
existing.lastAccessedAt = Date.now();
|
|
4816
4132
|
existing.requestCount++;
|
|
4817
|
-
return existing.pipeline;
|
|
4133
|
+
return { pipeline: existing.pipeline, stickyCache: existing.stickyCache };
|
|
4818
4134
|
}
|
|
4819
4135
|
if (this.sessions.size >= this.config.maxSessions) {
|
|
4820
4136
|
this.evictLRU();
|
|
@@ -4823,14 +4139,16 @@ var SessionManager = class {
|
|
|
4823
4139
|
...this.config.pipelineConfig,
|
|
4824
4140
|
sessionId: key.raw
|
|
4825
4141
|
});
|
|
4142
|
+
const stickyCache = /* @__PURE__ */ new Map();
|
|
4826
4143
|
this.sessions.set(key.raw, {
|
|
4827
4144
|
pipeline,
|
|
4145
|
+
stickyCache,
|
|
4828
4146
|
lastAccessedAt: Date.now(),
|
|
4829
4147
|
requestCount: 1,
|
|
4830
4148
|
connector: key.connector
|
|
4831
4149
|
});
|
|
4832
4150
|
this.config.onSessionCreated?.(key.raw, pipeline);
|
|
4833
|
-
return pipeline;
|
|
4151
|
+
return { pipeline, stickyCache };
|
|
4834
4152
|
}
|
|
4835
4153
|
getAllSummaries() {
|
|
4836
4154
|
const entries = [];
|
|
@@ -4895,59 +4213,8 @@ var SessionManager = class {
|
|
|
4895
4213
|
}
|
|
4896
4214
|
};
|
|
4897
4215
|
|
|
4898
|
-
// src/
|
|
4899
|
-
|
|
4900
|
-
constructor(timeoutMs) {
|
|
4901
|
-
super(`Semaphore acquire timed out after ${timeoutMs}ms`);
|
|
4902
|
-
this.name = "SemaphoreTimeoutError";
|
|
4903
|
-
}
|
|
4904
|
-
};
|
|
4905
|
-
var Semaphore = class {
|
|
4906
|
-
permits;
|
|
4907
|
-
queue = [];
|
|
4908
|
-
constructor(maxPermits) {
|
|
4909
|
-
if (maxPermits < 1) throw new RangeError("maxPermits must be >= 1");
|
|
4910
|
-
this.permits = maxPermits;
|
|
4911
|
-
}
|
|
4912
|
-
get available() {
|
|
4913
|
-
return this.permits;
|
|
4914
|
-
}
|
|
4915
|
-
get waiting() {
|
|
4916
|
-
return this.queue.length;
|
|
4917
|
-
}
|
|
4918
|
-
acquire(timeoutMs) {
|
|
4919
|
-
if (this.permits > 0) {
|
|
4920
|
-
this.permits--;
|
|
4921
|
-
return Promise.resolve();
|
|
4922
|
-
}
|
|
4923
|
-
return new Promise((resolve, reject) => {
|
|
4924
|
-
const waiter = { resolve, reject };
|
|
4925
|
-
this.queue.push(waiter);
|
|
4926
|
-
if (timeoutMs !== void 0 && timeoutMs >= 0) {
|
|
4927
|
-
const timer = setTimeout(() => {
|
|
4928
|
-
const idx = this.queue.indexOf(waiter);
|
|
4929
|
-
if (idx !== -1) {
|
|
4930
|
-
this.queue.splice(idx, 1);
|
|
4931
|
-
reject(new SemaphoreTimeoutError(timeoutMs));
|
|
4932
|
-
}
|
|
4933
|
-
}, timeoutMs);
|
|
4934
|
-
const originalResolve = waiter.resolve;
|
|
4935
|
-
waiter.resolve = () => {
|
|
4936
|
-
clearTimeout(timer);
|
|
4937
|
-
originalResolve();
|
|
4938
|
-
};
|
|
4939
|
-
}
|
|
4940
|
-
});
|
|
4941
|
-
}
|
|
4942
|
-
release() {
|
|
4943
|
-
const next = this.queue.shift();
|
|
4944
|
-
if (next) {
|
|
4945
|
-
next.resolve();
|
|
4946
|
-
} else {
|
|
4947
|
-
this.permits++;
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
};
|
|
4216
|
+
// src/commands/start.ts
|
|
4217
|
+
import { Semaphore } from "@cognisos/proxy-core";
|
|
4951
4218
|
|
|
4952
4219
|
// src/rsc/latency-monitor.ts
|
|
4953
4220
|
var DEFAULT_CONFIG = {
|
|
@@ -5399,15 +4666,36 @@ async function startCommand(flags) {
|
|
|
5399
4666
|
event.tokensSaved,
|
|
5400
4667
|
event.processingTimeMs ?? 0
|
|
5401
4668
|
);
|
|
4669
|
+
pipeline.session.recordCompression(event);
|
|
5402
4670
|
});
|
|
5403
4671
|
pipeline.events.on("compression_skipped", (event) => {
|
|
5404
4672
|
const detail = event.reason === "latency_budget" ? `${event.reason} (${event.estimatedTokens}tok, budget:${event.budgetMs}ms, elapsed:${event.elapsedMs}ms)` : event.reason === "below_threshold" ? `${event.reason} (${event.estimatedTokens}tok < ${config.compressionThreshold}tok threshold)` : `${event.reason} (${event.estimatedTokens}tok)`;
|
|
5405
4673
|
logger.log(`[LIMINAL] [${key}] Skipped: ${detail}`);
|
|
5406
4674
|
statsAggregator.recordSkipped(connector, event.estimatedTokens ?? 0);
|
|
4675
|
+
pipeline.session.recordCompression({
|
|
4676
|
+
...event,
|
|
4677
|
+
inputTokens: event.estimatedTokens ?? 0,
|
|
4678
|
+
outputTokens: event.estimatedTokens ?? 0,
|
|
4679
|
+
tokensSaved: 0,
|
|
4680
|
+
ratio: 1,
|
|
4681
|
+
deltaMdlBits: 0,
|
|
4682
|
+
processingTimeMs: 0,
|
|
4683
|
+
skipped: true,
|
|
4684
|
+
fabricHits: 0,
|
|
4685
|
+
fabricMisses: 0
|
|
4686
|
+
});
|
|
5407
4687
|
});
|
|
5408
4688
|
pipeline.events.on("error", (event) => {
|
|
5409
4689
|
logger.log(`[LIMINAL] [${key}] Error: ${event.error.message}`);
|
|
5410
4690
|
statsAggregator.recordFailure(connector);
|
|
4691
|
+
pipeline.session.recordFailure();
|
|
4692
|
+
});
|
|
4693
|
+
pipeline.events.on("learning", (event) => {
|
|
4694
|
+
if (event.error) {
|
|
4695
|
+
logger.log(`[LIMINAL] [${key}] Learning error: ${event.error}`);
|
|
4696
|
+
} else if (event.tokensIngested > 0) {
|
|
4697
|
+
logger.log(`[LIMINAL] [${key}] Learning: ${event.tokensIngested} tokens ingested (deferred:${event.deferred})`);
|
|
4698
|
+
}
|
|
5411
4699
|
});
|
|
5412
4700
|
pipeline.events.on("degradation", (event) => {
|
|
5413
4701
|
logger.log(`[LIMINAL] [${key}] Circuit ${event.circuitState}: ${event.reason}`);
|
|
@@ -5435,7 +4723,50 @@ async function startCommand(flags) {
|
|
|
5435
4723
|
logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message} (${alert.activeSessions} sessions) \u2014 ${alert.suggestion}`);
|
|
5436
4724
|
});
|
|
5437
4725
|
const mitmStats = new MitmStats();
|
|
5438
|
-
const deps = {
|
|
4726
|
+
const deps = {
|
|
4727
|
+
sessions,
|
|
4728
|
+
semaphore,
|
|
4729
|
+
latencyMonitor,
|
|
4730
|
+
mitmStats,
|
|
4731
|
+
config: resolvedConfig,
|
|
4732
|
+
logger,
|
|
4733
|
+
onUsage: (usage2, verifiedSaved, originalInputTokens) => {
|
|
4734
|
+
statsAggregator.recordApiUsage(usage2.inputTokens, usage2.outputTokens, verifiedSaved, originalInputTokens);
|
|
4735
|
+
if (resolvedConfig.rscApiKey && resolvedConfig.rscBaseUrl) {
|
|
4736
|
+
const body = JSON.stringify({
|
|
4737
|
+
model: "cli",
|
|
4738
|
+
actual_input_tokens: usage2.inputTokens,
|
|
4739
|
+
actual_output_tokens: usage2.outputTokens,
|
|
4740
|
+
original_input_bytes: originalInputTokens * 4
|
|
4741
|
+
});
|
|
4742
|
+
fetch(`${resolvedConfig.rscBaseUrl}/api/v1/usage/verified`, {
|
|
4743
|
+
method: "POST",
|
|
4744
|
+
headers: {
|
|
4745
|
+
"Content-Type": "application/json",
|
|
4746
|
+
"Authorization": `Bearer ${resolvedConfig.rscApiKey}`
|
|
4747
|
+
},
|
|
4748
|
+
body,
|
|
4749
|
+
signal: AbortSignal.timeout(5e3)
|
|
4750
|
+
}).catch(() => {
|
|
4751
|
+
});
|
|
4752
|
+
}
|
|
4753
|
+
},
|
|
4754
|
+
getVerifiedTokens: () => {
|
|
4755
|
+
const snap = statsAggregator.snapshot();
|
|
4756
|
+
return {
|
|
4757
|
+
actualInputTokens: snap.actualInputTokens,
|
|
4758
|
+
actualOutputTokens: snap.actualOutputTokens,
|
|
4759
|
+
originalInputEstimate: snap.originalInputEstimate,
|
|
4760
|
+
verifiedInputSaved: snap.verifiedInputSaved,
|
|
4761
|
+
verifiedSavingsRate: snap.verifiedSavingsRate,
|
|
4762
|
+
verifiedRequestCount: snap.verifiedRequestCount
|
|
4763
|
+
};
|
|
4764
|
+
},
|
|
4765
|
+
getCursorMetrics: () => {
|
|
4766
|
+
const { parseCursorHookStats: parseCursorHookStats3 } = (init_stats(), __toCommonJS(stats_exports));
|
|
4767
|
+
return parseCursorHookStats3();
|
|
4768
|
+
}
|
|
4769
|
+
};
|
|
5439
4770
|
const handler = createRequestHandler(deps);
|
|
5440
4771
|
let mitmHandler;
|
|
5441
4772
|
const connectHandler = createConnectHandler({
|
|
@@ -5458,6 +4789,11 @@ async function startCommand(flags) {
|
|
|
5458
4789
|
try {
|
|
5459
4790
|
const actualPort = await server.start();
|
|
5460
4791
|
writePidFile(process.pid);
|
|
4792
|
+
try {
|
|
4793
|
+
const portFilePath = join8(homedir5(), ".liminal", ".port");
|
|
4794
|
+
writeFileSync6(portFilePath, String(actualPort), "utf-8");
|
|
4795
|
+
} catch {
|
|
4796
|
+
}
|
|
5461
4797
|
logger.log(`[DAEMON] Liminal proxy started on http://127.0.0.1:${actualPort}`);
|
|
5462
4798
|
logger.log(`[DAEMON] Upstream (OpenAI): ${config.upstreamBaseUrl}`);
|
|
5463
4799
|
logger.log(`[DAEMON] Upstream (Anthropic): ${config.anthropicUpstreamUrl}`);
|
|
@@ -5486,7 +4822,7 @@ async function startCommand(flags) {
|
|
|
5486
4822
|
if (caReady) {
|
|
5487
4823
|
console.log(success("MITM bridge active", "CA trusted"));
|
|
5488
4824
|
}
|
|
5489
|
-
const { RSCPipelineWrapper: RSCPipelineWrapper3 } = await
|
|
4825
|
+
const { RSCPipelineWrapper: RSCPipelineWrapper3 } = await import("@cognisos/proxy-core");
|
|
5490
4826
|
const probe2 = new RSCPipelineWrapper3({
|
|
5491
4827
|
rscApiKey: config.apiKey,
|
|
5492
4828
|
rscBaseUrl: config.apiBaseUrl,
|
|
@@ -5524,7 +4860,7 @@ async function startCommand(flags) {
|
|
|
5524
4860
|
console.log();
|
|
5525
4861
|
return;
|
|
5526
4862
|
}
|
|
5527
|
-
const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await
|
|
4863
|
+
const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await import("@cognisos/proxy-core");
|
|
5528
4864
|
const probe = new RSCPipelineWrapper2({
|
|
5529
4865
|
rscApiKey: config.apiKey,
|
|
5530
4866
|
rscBaseUrl: config.apiBaseUrl,
|
|
@@ -5640,8 +4976,9 @@ async function stopCommand() {
|
|
|
5640
4976
|
init_lifecycle();
|
|
5641
4977
|
init_loader();
|
|
5642
4978
|
init_format();
|
|
5643
|
-
import { existsSync as
|
|
5644
|
-
import {
|
|
4979
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
4980
|
+
import { homedir as homedir6 } from "os";
|
|
4981
|
+
import { join as join9 } from "path";
|
|
5645
4982
|
async function statusCommand() {
|
|
5646
4983
|
if (!isConfigured()) {
|
|
5647
4984
|
console.log(error(
|
|
@@ -5660,7 +4997,13 @@ async function statusCommand() {
|
|
|
5660
4997
|
return;
|
|
5661
4998
|
}
|
|
5662
4999
|
const config = loadConfig();
|
|
5663
|
-
|
|
5000
|
+
let port = config.port;
|
|
5001
|
+
try {
|
|
5002
|
+
const portFilePath = join9(homedir6(), ".liminal", ".port");
|
|
5003
|
+
const actualPort = parseInt(readFileSync9(portFilePath, "utf-8").trim(), 10);
|
|
5004
|
+
if (!isNaN(actualPort)) port = actualPort;
|
|
5005
|
+
} catch {
|
|
5006
|
+
}
|
|
5664
5007
|
try {
|
|
5665
5008
|
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
5666
5009
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -5709,27 +5052,27 @@ async function statusCommand() {
|
|
|
5709
5052
|
}
|
|
5710
5053
|
}
|
|
5711
5054
|
function printCursorHookStats() {
|
|
5712
|
-
const logPath =
|
|
5713
|
-
const hooksPath =
|
|
5055
|
+
const logPath = join9(process.cwd(), ".fabric", "compress.log");
|
|
5056
|
+
const hooksPath = join9(process.cwd(), ".cursor", "hooks.json");
|
|
5714
5057
|
let hooksActive = false;
|
|
5715
5058
|
try {
|
|
5716
|
-
if (
|
|
5717
|
-
const data = JSON.parse(
|
|
5059
|
+
if (existsSync11(hooksPath)) {
|
|
5060
|
+
const data = JSON.parse(readFileSync9(hooksPath, "utf-8"));
|
|
5718
5061
|
const entries = data?.hooks?.preToolUse ?? [];
|
|
5719
5062
|
hooksActive = entries.some((e) => e.command?.includes("fabric-compress"));
|
|
5720
5063
|
}
|
|
5721
5064
|
} catch {
|
|
5722
5065
|
}
|
|
5723
|
-
if (!hooksActive && !
|
|
5066
|
+
if (!hooksActive && !existsSync11(logPath)) return;
|
|
5724
5067
|
console.log(sectionHeader("Cursor Hooks"));
|
|
5725
5068
|
console.log();
|
|
5726
5069
|
console.log(label("Status", hooksActive ? `${c.green}active${c.reset}` : `${c.dim}not installed${c.reset}`));
|
|
5727
|
-
if (!
|
|
5070
|
+
if (!existsSync11(logPath)) {
|
|
5728
5071
|
console.log(label("Stats", `${c.dim}no compression data yet${c.reset}`));
|
|
5729
5072
|
console.log();
|
|
5730
5073
|
return;
|
|
5731
5074
|
}
|
|
5732
|
-
const lines =
|
|
5075
|
+
const lines = readFileSync9(logPath, "utf-8").trim().split("\n");
|
|
5733
5076
|
let compressed = 0;
|
|
5734
5077
|
let errors = 0;
|
|
5735
5078
|
let totalInputSize = 0;
|
|
@@ -5763,7 +5106,7 @@ function printCursorHookStats() {
|
|
|
5763
5106
|
const avgApiMs = compressed > 0 ? Math.round(totalApiMs / compressed) : 0;
|
|
5764
5107
|
let cacheCount = 0;
|
|
5765
5108
|
try {
|
|
5766
|
-
cacheCount = countFiles(
|
|
5109
|
+
cacheCount = countFiles(join9(process.cwd(), ".fabric", "cache"));
|
|
5767
5110
|
} catch {
|
|
5768
5111
|
}
|
|
5769
5112
|
console.log(label("Files", `${files.size} unique ${c.dim}(${compressed} compressions${errors > 0 ? `, ${c.red}${errors} errors${c.reset}${c.dim}` : ""})${c.reset}`));
|
|
@@ -5776,12 +5119,12 @@ function printCursorHookStats() {
|
|
|
5776
5119
|
console.log();
|
|
5777
5120
|
}
|
|
5778
5121
|
function countFiles(dir) {
|
|
5779
|
-
if (!
|
|
5780
|
-
const { readdirSync:
|
|
5122
|
+
if (!existsSync11(dir)) return 0;
|
|
5123
|
+
const { readdirSync: readdirSync3, statSync: statSync4 } = __require("fs");
|
|
5781
5124
|
let count = 0;
|
|
5782
|
-
for (const entry of
|
|
5125
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
5783
5126
|
if (entry.isDirectory()) {
|
|
5784
|
-
count += countFiles(
|
|
5127
|
+
count += countFiles(join9(dir, entry.name));
|
|
5785
5128
|
} else {
|
|
5786
5129
|
count++;
|
|
5787
5130
|
}
|
|
@@ -5864,17 +5207,17 @@ async function configCommand(flags) {
|
|
|
5864
5207
|
|
|
5865
5208
|
// src/commands/logs.ts
|
|
5866
5209
|
init_paths();
|
|
5867
|
-
import { readFileSync as
|
|
5210
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12, statSync as statSync2, createReadStream } from "fs";
|
|
5868
5211
|
import { watchFile, unwatchFile } from "fs";
|
|
5869
5212
|
async function logsCommand(flags) {
|
|
5870
5213
|
const follow = flags.has("follow") || flags.has("f");
|
|
5871
5214
|
const linesFlag = flags.get("lines") ?? flags.get("n");
|
|
5872
5215
|
const lines = typeof linesFlag === "string" ? parseInt(linesFlag, 10) : 50;
|
|
5873
|
-
if (!
|
|
5216
|
+
if (!existsSync12(LOG_FILE)) {
|
|
5874
5217
|
console.log('No log file found. Start the daemon with "liminal start" to generate logs.');
|
|
5875
5218
|
return;
|
|
5876
5219
|
}
|
|
5877
|
-
const content =
|
|
5220
|
+
const content = readFileSync10(LOG_FILE, "utf-8");
|
|
5878
5221
|
const allLines = content.split("\n");
|
|
5879
5222
|
const tail = allLines.slice(-lines - 1);
|
|
5880
5223
|
process.stdout.write(tail.join("\n"));
|
|
@@ -5900,7 +5243,7 @@ async function logsCommand(flags) {
|
|
|
5900
5243
|
}
|
|
5901
5244
|
|
|
5902
5245
|
// src/commands/uninstall.ts
|
|
5903
|
-
import { existsSync as
|
|
5246
|
+
import { existsSync as existsSync13, rmSync, readFileSync as readFileSync11 } from "fs";
|
|
5904
5247
|
init_paths();
|
|
5905
5248
|
init_lifecycle();
|
|
5906
5249
|
init_prompts();
|
|
@@ -5910,9 +5253,9 @@ var GREEN = "\x1B[32m";
|
|
|
5910
5253
|
var YELLOW = "\x1B[33m";
|
|
5911
5254
|
var RESET = "\x1B[0m";
|
|
5912
5255
|
function loadConfiguredTools() {
|
|
5913
|
-
if (!
|
|
5256
|
+
if (!existsSync13(CONFIG_FILE)) return [];
|
|
5914
5257
|
try {
|
|
5915
|
-
const raw =
|
|
5258
|
+
const raw = readFileSync11(CONFIG_FILE, "utf-8");
|
|
5916
5259
|
const config = JSON.parse(raw);
|
|
5917
5260
|
if (Array.isArray(config.tools)) return config.tools;
|
|
5918
5261
|
} catch {
|
|
@@ -6000,7 +5343,7 @@ async function uninstallCommand() {
|
|
|
6000
5343
|
}
|
|
6001
5344
|
}
|
|
6002
5345
|
}
|
|
6003
|
-
if (
|
|
5346
|
+
if (existsSync13(LIMINAL_DIR)) {
|
|
6004
5347
|
console.log();
|
|
6005
5348
|
const removeData = await selectPrompt({
|
|
6006
5349
|
message: "Remove ~/.liminal/ directory? (config, logs, PID file)",
|
|
@@ -6120,9 +5463,10 @@ async function untrustCACommand() {
|
|
|
6120
5463
|
// src/commands/setup-cursor.ts
|
|
6121
5464
|
init_loader();
|
|
6122
5465
|
init_version();
|
|
6123
|
-
import { existsSync as
|
|
6124
|
-
import {
|
|
6125
|
-
import {
|
|
5466
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4, readdirSync as readdirSync2, rmdirSync, appendFileSync as appendFileSync3 } from "fs";
|
|
5467
|
+
import { createHash as createHash3 } from "crypto";
|
|
5468
|
+
import { homedir as homedir7 } from "os";
|
|
5469
|
+
import { join as join10 } from "path";
|
|
6126
5470
|
|
|
6127
5471
|
// src/cursor/hook-template.ts
|
|
6128
5472
|
function getHookScript() {
|
|
@@ -6375,10 +5719,11 @@ async function main() {
|
|
|
6375
5719
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
6376
5720
|
fs.writeFileSync(cachePath, result.text, "utf-8");
|
|
6377
5721
|
|
|
6378
|
-
// Use server-side token counts
|
|
6379
|
-
// Fall back to ~
|
|
6380
|
-
const inputTokens = result.inputTokens || Math.ceil(result.inputSize /
|
|
6381
|
-
const outputTokens = result.outputTokens || Math.ceil(result.outputSize /
|
|
5722
|
+
// Use server-side token counts from tiktoken-rs (cl100k_base BPE tokenizer)
|
|
5723
|
+
// Fall back to ~3 chars/token estimate if server doesn't return counts
|
|
5724
|
+
const inputTokens = result.inputTokens || Math.ceil(result.inputSize / 3);
|
|
5725
|
+
const outputTokens = result.outputTokens || Math.ceil(result.outputSize / 3);
|
|
5726
|
+
const tokensSaved = Math.max(0, inputTokens - outputTokens);
|
|
6382
5727
|
logEntry({
|
|
6383
5728
|
type: "compressed",
|
|
6384
5729
|
file: relativePath,
|
|
@@ -6386,8 +5731,8 @@ async function main() {
|
|
|
6386
5731
|
outputSize: result.outputSize,
|
|
6387
5732
|
inputTokens: inputTokens,
|
|
6388
5733
|
outputTokens: outputTokens,
|
|
6389
|
-
tokensSaved:
|
|
6390
|
-
savedPct: +((
|
|
5734
|
+
tokensSaved: tokensSaved,
|
|
5735
|
+
savedPct: inputTokens > 0 ? +((tokensSaved / inputTokens) * 100).toFixed(1) : 0,
|
|
6391
5736
|
apiMs: elapsed,
|
|
6392
5737
|
});
|
|
6393
5738
|
|
|
@@ -6408,28 +5753,36 @@ main().catch(() => passthrough());
|
|
|
6408
5753
|
init_format();
|
|
6409
5754
|
var HOOK_SCRIPT_NAME = "fabric-compress.js";
|
|
6410
5755
|
var HOOK_COMMAND = `node .cursor/hooks/${HOOK_SCRIPT_NAME}`;
|
|
5756
|
+
function sha256(content) {
|
|
5757
|
+
return createHash3("sha256").update(content, "utf-8").digest("hex");
|
|
5758
|
+
}
|
|
5759
|
+
function verifyHookIntegrity(scriptPath) {
|
|
5760
|
+
if (!existsSync14(scriptPath)) return false;
|
|
5761
|
+
const onDisk = readFileSync12(scriptPath, "utf-8");
|
|
5762
|
+
return sha256(onDisk) === sha256(getHookScript());
|
|
5763
|
+
}
|
|
6411
5764
|
function isCursorInstalled2() {
|
|
6412
5765
|
if (process.platform === "darwin") {
|
|
6413
|
-
return
|
|
5766
|
+
return existsSync14("/Applications/Cursor.app");
|
|
6414
5767
|
}
|
|
6415
5768
|
if (process.platform === "win32") {
|
|
6416
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
6417
|
-
return
|
|
5769
|
+
const localAppData = process.env.LOCALAPPDATA || join10(homedir7(), "AppData", "Local");
|
|
5770
|
+
return existsSync14(join10(localAppData, "Programs", "Cursor", "Cursor.exe"));
|
|
6418
5771
|
}
|
|
6419
|
-
return
|
|
5772
|
+
return existsSync14("/usr/bin/cursor");
|
|
6420
5773
|
}
|
|
6421
5774
|
function readHooksJson(workspaceRoot) {
|
|
6422
|
-
const hooksPath =
|
|
5775
|
+
const hooksPath = join10(workspaceRoot, ".cursor", "hooks.json");
|
|
6423
5776
|
try {
|
|
6424
|
-
return JSON.parse(
|
|
5777
|
+
return JSON.parse(readFileSync12(hooksPath, "utf-8"));
|
|
6425
5778
|
} catch {
|
|
6426
5779
|
return {};
|
|
6427
5780
|
}
|
|
6428
5781
|
}
|
|
6429
5782
|
function writeHooksJson(workspaceRoot, data) {
|
|
6430
|
-
const dir =
|
|
6431
|
-
if (!
|
|
6432
|
-
|
|
5783
|
+
const dir = join10(workspaceRoot, ".cursor");
|
|
5784
|
+
if (!existsSync14(dir)) mkdirSync5(dir, { recursive: true });
|
|
5785
|
+
writeFileSync7(join10(dir, "hooks.json"), JSON.stringify(data, null, 2) + "\n");
|
|
6433
5786
|
}
|
|
6434
5787
|
function hasOurHook(hooks) {
|
|
6435
5788
|
const entries = hooks.hooks?.preToolUse ?? [];
|
|
@@ -6462,24 +5815,24 @@ function removeOurHook(hooks) {
|
|
|
6462
5815
|
return hooks;
|
|
6463
5816
|
}
|
|
6464
5817
|
function ensureGitignore(workspaceRoot) {
|
|
6465
|
-
const gitignorePath =
|
|
5818
|
+
const gitignorePath = join10(workspaceRoot, ".gitignore");
|
|
6466
5819
|
const entry = ".fabric/";
|
|
6467
|
-
if (
|
|
6468
|
-
const content =
|
|
5820
|
+
if (existsSync14(gitignorePath)) {
|
|
5821
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
6469
5822
|
if (content.includes(entry)) return;
|
|
6470
5823
|
appendFileSync3(gitignorePath, `
|
|
6471
5824
|
# Liminal compression cache
|
|
6472
5825
|
${entry}
|
|
6473
5826
|
`);
|
|
6474
5827
|
} else {
|
|
6475
|
-
|
|
5828
|
+
writeFileSync7(gitignorePath, `# Liminal compression cache
|
|
6476
5829
|
${entry}
|
|
6477
5830
|
`);
|
|
6478
5831
|
}
|
|
6479
5832
|
}
|
|
6480
5833
|
function rmdirIfEmpty(dir) {
|
|
6481
5834
|
try {
|
|
6482
|
-
if (
|
|
5835
|
+
if (existsSync14(dir) && readdirSync2(dir).length === 0) {
|
|
6483
5836
|
rmdirSync(dir);
|
|
6484
5837
|
}
|
|
6485
5838
|
} catch {
|
|
@@ -6502,20 +5855,20 @@ async function setupCursorCommand(flags) {
|
|
|
6502
5855
|
writeHooksJson(workspaceRoot, updated2);
|
|
6503
5856
|
console.log(success("Removed hook entry from .cursor/hooks.json"));
|
|
6504
5857
|
} else {
|
|
6505
|
-
const hooksJsonPath =
|
|
5858
|
+
const hooksJsonPath = join10(workspaceRoot, ".cursor", "hooks.json");
|
|
6506
5859
|
try {
|
|
6507
5860
|
unlinkSync4(hooksJsonPath);
|
|
6508
5861
|
} catch {
|
|
6509
5862
|
}
|
|
6510
5863
|
console.log(success("Deleted .cursor/hooks.json", "no remaining hooks"));
|
|
6511
5864
|
}
|
|
6512
|
-
const scriptPath2 =
|
|
5865
|
+
const scriptPath2 = join10(workspaceRoot, ".cursor", "hooks", HOOK_SCRIPT_NAME);
|
|
6513
5866
|
try {
|
|
6514
5867
|
unlinkSync4(scriptPath2);
|
|
6515
5868
|
console.log(success(`Deleted .cursor/hooks/${HOOK_SCRIPT_NAME}`));
|
|
6516
5869
|
} catch {
|
|
6517
5870
|
}
|
|
6518
|
-
rmdirIfEmpty(
|
|
5871
|
+
rmdirIfEmpty(join10(workspaceRoot, ".cursor", "hooks"));
|
|
6519
5872
|
console.log();
|
|
6520
5873
|
console.log(success("Hooks removed. Reload Cursor to deactivate."));
|
|
6521
5874
|
console.log();
|
|
@@ -6530,6 +5883,11 @@ async function setupCursorCommand(flags) {
|
|
|
6530
5883
|
}
|
|
6531
5884
|
console.log(success("Cursor found"));
|
|
6532
5885
|
console.log();
|
|
5886
|
+
const existingScript = join10(workspaceRoot, ".cursor", "hooks", HOOK_SCRIPT_NAME);
|
|
5887
|
+
if (existsSync14(existingScript) && !verifyHookIntegrity(existingScript)) {
|
|
5888
|
+
console.log(warn(`Existing hook script has been modified externally \u2014 it will be overwritten`));
|
|
5889
|
+
console.log();
|
|
5890
|
+
}
|
|
6533
5891
|
console.log(` ${c.dim}[2/4]${c.reset} Checking Liminal configuration...`);
|
|
6534
5892
|
if (!isConfigured()) {
|
|
6535
5893
|
console.error(error("Liminal is not configured", `Run ${c.bold}liminal init${c.reset}${c.dim} first`));
|
|
@@ -6538,11 +5896,15 @@ async function setupCursorCommand(flags) {
|
|
|
6538
5896
|
console.log(success("API key configured"));
|
|
6539
5897
|
console.log();
|
|
6540
5898
|
console.log(` ${c.dim}[3/4]${c.reset} Installing compression hooks...`);
|
|
6541
|
-
const hooksDir =
|
|
6542
|
-
if (!
|
|
6543
|
-
const scriptPath =
|
|
6544
|
-
|
|
6545
|
-
|
|
5899
|
+
const hooksDir = join10(workspaceRoot, ".cursor", "hooks");
|
|
5900
|
+
if (!existsSync14(hooksDir)) mkdirSync5(hooksDir, { recursive: true });
|
|
5901
|
+
const scriptPath = join10(hooksDir, HOOK_SCRIPT_NAME);
|
|
5902
|
+
writeFileSync7(scriptPath, getHookScript(), { mode: 493 });
|
|
5903
|
+
if (!verifyHookIntegrity(scriptPath)) {
|
|
5904
|
+
console.error(error("Hook script integrity check failed", "File may have been modified after write"));
|
|
5905
|
+
process.exit(1);
|
|
5906
|
+
}
|
|
5907
|
+
console.log(success(`Wrote .cursor/hooks/${HOOK_SCRIPT_NAME}`, "integrity verified"));
|
|
6546
5908
|
const hooks = readHooksJson(workspaceRoot);
|
|
6547
5909
|
const updated = addOurHook(hooks);
|
|
6548
5910
|
writeHooksJson(workspaceRoot, updated);
|
|
@@ -6569,8 +5931,8 @@ init_loader();
|
|
|
6569
5931
|
init_store();
|
|
6570
5932
|
init_aggregator();
|
|
6571
5933
|
init_format();
|
|
6572
|
-
import { existsSync as
|
|
6573
|
-
import { join as
|
|
5934
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
5935
|
+
import { join as join11 } from "path";
|
|
6574
5936
|
async function statsCommand(flags) {
|
|
6575
5937
|
if (!isConfigured()) {
|
|
6576
5938
|
console.log(error(
|
|
@@ -6606,7 +5968,7 @@ async function statsCommand(flags) {
|
|
|
6606
5968
|
agg.recordSkipped(s.connector, 0);
|
|
6607
5969
|
}
|
|
6608
5970
|
}
|
|
6609
|
-
const cursorMetrics =
|
|
5971
|
+
const cursorMetrics = parseCursorHookStats2();
|
|
6610
5972
|
if (cursorMetrics) {
|
|
6611
5973
|
agg.setCursorMetrics(cursorMetrics);
|
|
6612
5974
|
}
|
|
@@ -6759,14 +6121,14 @@ async function statsCommand(flags) {
|
|
|
6759
6121
|
console.log(` ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
6760
6122
|
console.log();
|
|
6761
6123
|
}
|
|
6762
|
-
function
|
|
6763
|
-
const logPath =
|
|
6764
|
-
if (!
|
|
6124
|
+
function parseCursorHookStats2() {
|
|
6125
|
+
const logPath = join11(process.cwd(), ".fabric", "compress.log");
|
|
6126
|
+
if (!existsSync15(logPath)) return null;
|
|
6765
6127
|
let compressions = 0, errors = 0;
|
|
6766
6128
|
let tokensProcessed = 0, tokensSaved = 0;
|
|
6767
6129
|
let apiMsSumMs = 0;
|
|
6768
6130
|
const files = /* @__PURE__ */ new Set();
|
|
6769
|
-
const lines =
|
|
6131
|
+
const lines = readFileSync13(logPath, "utf-8").trim().split("\n");
|
|
6770
6132
|
for (const line of lines) {
|
|
6771
6133
|
try {
|
|
6772
6134
|
const entry = JSON.parse(line);
|
|
@@ -6786,7 +6148,7 @@ function parseCursorHookStats() {
|
|
|
6786
6148
|
}
|
|
6787
6149
|
let cacheCount = 0;
|
|
6788
6150
|
try {
|
|
6789
|
-
cacheCount =
|
|
6151
|
+
cacheCount = countFilesRecursive2(join11(process.cwd(), ".fabric", "cache"));
|
|
6790
6152
|
} catch {
|
|
6791
6153
|
}
|
|
6792
6154
|
return {
|
|
@@ -6799,13 +6161,13 @@ function parseCursorHookStats() {
|
|
|
6799
6161
|
cacheCount
|
|
6800
6162
|
};
|
|
6801
6163
|
}
|
|
6802
|
-
function
|
|
6803
|
-
if (!
|
|
6804
|
-
const { readdirSync:
|
|
6164
|
+
function countFilesRecursive2(dir) {
|
|
6165
|
+
if (!existsSync15(dir)) return 0;
|
|
6166
|
+
const { readdirSync: readdirSync3 } = __require("fs");
|
|
6805
6167
|
let count = 0;
|
|
6806
|
-
for (const entry of
|
|
6168
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
6807
6169
|
if (entry.isDirectory()) {
|
|
6808
|
-
count +=
|
|
6170
|
+
count += countFilesRecursive2(join11(dir, entry.name));
|
|
6809
6171
|
} else {
|
|
6810
6172
|
count++;
|
|
6811
6173
|
}
|
|
@@ -6885,9 +6247,94 @@ function parseArgs(argv) {
|
|
|
6885
6247
|
}
|
|
6886
6248
|
return { command, flags };
|
|
6887
6249
|
}
|
|
6250
|
+
function getCommandHelp(cmd) {
|
|
6251
|
+
const helps = {
|
|
6252
|
+
start: `
|
|
6253
|
+
\${c.bold}liminal start\${c.reset} \u2014 Start the compression proxy
|
|
6254
|
+
|
|
6255
|
+
\${c.bold}Usage:\${c.reset}
|
|
6256
|
+
liminal start [options]
|
|
6257
|
+
|
|
6258
|
+
\${c.bold}Options:\${c.reset}
|
|
6259
|
+
-d, --daemon Run in background (daemon mode)
|
|
6260
|
+
--port PORT Override proxy port (default: from config)
|
|
6261
|
+
--upstream URL Override upstream API base URL
|
|
6262
|
+
|
|
6263
|
+
\${c.bold}Examples:\${c.reset}
|
|
6264
|
+
\${c.dim}$\${c.reset} liminal start \${c.dim}# Foreground mode\${c.reset}
|
|
6265
|
+
\${c.dim}$\${c.reset} liminal start -d \${c.dim}# Background daemon\${c.reset}
|
|
6266
|
+
\${c.dim}$\${c.reset} liminal start --port 3142 \${c.dim}# Custom port\${c.reset}
|
|
6267
|
+
`,
|
|
6268
|
+
stop: `
|
|
6269
|
+
\${c.bold}liminal stop\${c.reset} \u2014 Stop the running proxy
|
|
6270
|
+
|
|
6271
|
+
\${c.bold}Usage:\${c.reset}
|
|
6272
|
+
liminal stop
|
|
6273
|
+
`,
|
|
6274
|
+
status: `
|
|
6275
|
+
\${c.bold}liminal status\${c.reset} \u2014 Show proxy health check
|
|
6276
|
+
|
|
6277
|
+
\${c.bold}Usage:\${c.reset}
|
|
6278
|
+
liminal status
|
|
6279
|
+
`,
|
|
6280
|
+
stats: `
|
|
6281
|
+
\${c.bold}liminal stats\${c.reset} \u2014 Compression metrics & savings
|
|
6282
|
+
|
|
6283
|
+
\${c.bold}Usage:\${c.reset}
|
|
6284
|
+
liminal stats [options]
|
|
6285
|
+
|
|
6286
|
+
\${c.bold}Options:\${c.reset}
|
|
6287
|
+
--json Output as JSON
|
|
6288
|
+
`,
|
|
6289
|
+
config: `
|
|
6290
|
+
\${c.bold}liminal config\${c.reset} \u2014 View or edit configuration
|
|
6291
|
+
|
|
6292
|
+
\${c.bold}Usage:\${c.reset}
|
|
6293
|
+
liminal config \${c.dim}# Show all config\${c.reset}
|
|
6294
|
+
liminal config --get KEY \${c.dim}# Get a specific value\${c.reset}
|
|
6295
|
+
liminal config --set KEY=VALUE \${c.dim}# Set a value\${c.reset}
|
|
6296
|
+
`,
|
|
6297
|
+
logs: `
|
|
6298
|
+
\${c.bold}liminal logs\${c.reset} \u2014 View proxy logs
|
|
6299
|
+
|
|
6300
|
+
\${c.bold}Usage:\${c.reset}
|
|
6301
|
+
liminal logs [options]
|
|
6302
|
+
|
|
6303
|
+
\${c.bold}Options:\${c.reset}
|
|
6304
|
+
--follow, -f Follow log output (tail -f style)
|
|
6305
|
+
--lines N Number of lines to show (default: 50)
|
|
6306
|
+
`,
|
|
6307
|
+
init: `
|
|
6308
|
+
\${c.bold}liminal init\${c.reset} \u2014 Set up Liminal (login, config)
|
|
6309
|
+
|
|
6310
|
+
\${c.bold}Usage:\${c.reset}
|
|
6311
|
+
liminal init
|
|
6312
|
+
`,
|
|
6313
|
+
login: `
|
|
6314
|
+
\${c.bold}liminal login\${c.reset} \u2014 Log in or create an account
|
|
6315
|
+
|
|
6316
|
+
\${c.bold}Usage:\${c.reset}
|
|
6317
|
+
liminal login
|
|
6318
|
+
`,
|
|
6319
|
+
logout: `
|
|
6320
|
+
\${c.bold}liminal logout\${c.reset} \u2014 Log out of your account
|
|
6321
|
+
|
|
6322
|
+
\${c.bold}Usage:\${c.reset}
|
|
6323
|
+
liminal logout
|
|
6324
|
+
`
|
|
6325
|
+
};
|
|
6326
|
+
return helps[cmd] ?? null;
|
|
6327
|
+
}
|
|
6888
6328
|
async function main() {
|
|
6889
6329
|
const { command, flags } = parseArgs(process.argv);
|
|
6890
6330
|
if (flags.has("h") || flags.has("help") || command === "help" || command === "--help" || command === "-h") {
|
|
6331
|
+
if (command && command !== "help" && command !== "--help" && command !== "-h") {
|
|
6332
|
+
const helpText = getCommandHelp(command);
|
|
6333
|
+
if (helpText) {
|
|
6334
|
+
console.log(helpText);
|
|
6335
|
+
process.exit(0);
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6891
6338
|
console.log(usage());
|
|
6892
6339
|
process.exit(0);
|
|
6893
6340
|
}
|
|
@@ -6974,4 +6421,3 @@ async function main() {
|
|
|
6974
6421
|
}
|
|
6975
6422
|
}
|
|
6976
6423
|
main();
|
|
6977
|
-
//# sourceMappingURL=bin.js.map
|