@cognisos/liminal 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bin.js +1710 -258
  2. package/dist/bin.js.map +1 -1
  3. package/package.json +4 -2
package/dist/bin.js CHANGED
@@ -1,7 +1,84 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/rsc/pipeline.ts
13
+ var pipeline_exports = {};
14
+ __export(pipeline_exports, {
15
+ RSCPipelineWrapper: () => RSCPipelineWrapper
16
+ });
17
+ import {
18
+ CompressionPipeline,
19
+ RSCTransport,
20
+ RSCEventEmitter,
21
+ Session,
22
+ CircuitBreaker
23
+ } from "@cognisos/rsc-sdk";
24
+ var RSCPipelineWrapper;
25
+ var init_pipeline = __esm({
26
+ "src/rsc/pipeline.ts"() {
27
+ "use strict";
28
+ RSCPipelineWrapper = class {
29
+ pipeline;
30
+ session;
31
+ events;
32
+ transport;
33
+ circuitBreaker;
34
+ constructor(config) {
35
+ this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
36
+ this.transport = new RSCTransport({
37
+ baseUrl: config.rscBaseUrl,
38
+ apiKey: config.rscApiKey,
39
+ timeout: 3e4,
40
+ maxRetries: 3,
41
+ circuitBreaker: this.circuitBreaker
42
+ });
43
+ this.events = new RSCEventEmitter();
44
+ this.session = new Session(config.sessionId);
45
+ this.pipeline = new CompressionPipeline(
46
+ this.transport,
47
+ {
48
+ threshold: config.compressionThreshold,
49
+ learnFromResponses: config.learnFromResponses,
50
+ latencyBudgetMs: config.latencyBudgetMs,
51
+ sessionId: this.session.sessionId
52
+ },
53
+ this.events
54
+ );
55
+ }
56
+ async healthCheck() {
57
+ try {
58
+ await this.transport.get("/health");
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ getSessionSummary() {
65
+ return this.session.getSummary();
66
+ }
67
+ getCircuitState() {
68
+ return this.circuitBreaker.getState();
69
+ }
70
+ isCircuitOpen() {
71
+ return this.circuitBreaker.getState() === "open";
72
+ }
73
+ resetCircuitBreaker() {
74
+ this.circuitBreaker.reset();
75
+ }
76
+ };
77
+ }
78
+ });
2
79
 
3
80
  // src/version.ts
4
- var VERSION = true ? "2.4.0" : "0.2.1";
81
+ var VERSION = true ? "2.4.2" : "0.2.1";
5
82
  var BANNER_LINES = [
6
83
  " ___ ___ _____ ______ ___ ________ ________ ___",
7
84
  "|\\ \\ |\\ \\|\\ _ \\ _ \\|\\ \\|\\ ___ \\|\\ __ \\|\\ \\",
@@ -41,11 +118,21 @@ var DEFAULTS = {
41
118
  anthropicUpstreamUrl: "https://api.anthropic.com",
42
119
  port: 3141,
43
120
  compressionThreshold: 100,
44
- compressRoles: ["user"],
121
+ aggregateThreshold: 500,
122
+ hotFraction: 0.3,
123
+ coldFraction: 0.3,
124
+ compressRoles: ["user", "assistant"],
125
+ compressToolResults: true,
45
126
  learnFromResponses: true,
46
- latencyBudgetMs: 5e3,
127
+ latencyBudgetMs: 1e4,
47
128
  enabled: true,
48
- tools: []
129
+ tools: [],
130
+ concurrencyLimit: 6,
131
+ concurrencyTimeoutMs: 15e3,
132
+ maxSessions: 10,
133
+ sessionTtlMs: 18e5,
134
+ latencyWarningMs: 4e3,
135
+ latencyCriticalMs: 8e3
49
136
  };
50
137
  var CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
51
138
  "apiBaseUrl",
@@ -53,11 +140,21 @@ var CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
53
140
  "anthropicUpstreamUrl",
54
141
  "port",
55
142
  "compressionThreshold",
143
+ "aggregateThreshold",
144
+ "hotFraction",
145
+ "coldFraction",
56
146
  "compressRoles",
147
+ "compressToolResults",
57
148
  "learnFromResponses",
58
149
  "latencyBudgetMs",
59
150
  "enabled",
60
- "tools"
151
+ "tools",
152
+ "concurrencyLimit",
153
+ "concurrencyTimeoutMs",
154
+ "maxSessions",
155
+ "sessionTtlMs",
156
+ "latencyWarningMs",
157
+ "latencyCriticalMs"
61
158
  ]);
62
159
 
63
160
  // src/config/loader.ts
@@ -77,11 +174,21 @@ function loadConfig() {
77
174
  anthropicUpstreamUrl: DEFAULTS.anthropicUpstreamUrl,
78
175
  port: DEFAULTS.port,
79
176
  compressionThreshold: DEFAULTS.compressionThreshold,
177
+ aggregateThreshold: DEFAULTS.aggregateThreshold,
178
+ hotFraction: DEFAULTS.hotFraction,
179
+ coldFraction: DEFAULTS.coldFraction,
80
180
  compressRoles: DEFAULTS.compressRoles,
181
+ compressToolResults: DEFAULTS.compressToolResults,
81
182
  learnFromResponses: DEFAULTS.learnFromResponses,
82
183
  latencyBudgetMs: DEFAULTS.latencyBudgetMs,
83
184
  enabled: DEFAULTS.enabled,
84
185
  tools: DEFAULTS.tools,
186
+ concurrencyLimit: DEFAULTS.concurrencyLimit,
187
+ concurrencyTimeoutMs: DEFAULTS.concurrencyTimeoutMs,
188
+ maxSessions: DEFAULTS.maxSessions,
189
+ sessionTtlMs: DEFAULTS.sessionTtlMs,
190
+ latencyWarningMs: DEFAULTS.latencyWarningMs,
191
+ latencyCriticalMs: DEFAULTS.latencyCriticalMs,
85
192
  ...fileConfig
86
193
  };
87
194
  if (process.env.LIMINAL_API_KEY) merged.apiKey = process.env.LIMINAL_API_KEY;
@@ -485,7 +592,7 @@ import { createInterface } from "readline/promises";
485
592
  import { stdin, stdout } from "process";
486
593
 
487
594
  // src/auth/supabase.ts
488
- import { randomBytes } from "crypto";
595
+ import { randomBytes, createHash } from "crypto";
489
596
  var SUPABASE_URL = "https://nzcneiyymvgxvttbenhp.supabase.co";
490
597
  var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im56Y25laXl5bXZneHZ0dGJlbmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQwNjQ0MjcsImV4cCI6MjA2OTY0MDQyN30.x3E-zGRadbPMmxRqT_PB_KOi00htKpgeb8GiQa4g2z0";
491
598
  function supabaseHeaders(accessToken) {
@@ -542,25 +649,13 @@ async function signUp(email, password, name) {
542
649
  email: body.user?.email ?? email
543
650
  };
544
651
  }
545
- async function fetchApiKey(accessToken, userId) {
546
- const params = new URLSearchParams({
547
- user_id: `eq.${userId}`,
548
- is_active: "eq.true",
549
- select: "api_key",
550
- limit: "1"
551
- });
552
- const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys?${params}`, {
652
+ async function createApiKey(accessToken, userId) {
653
+ await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys?user_id=eq.${userId}`, {
654
+ method: "DELETE",
553
655
  headers: supabaseHeaders(accessToken)
554
656
  });
555
- if (!res.ok) return null;
556
- const rows = await res.json();
557
- if (Array.isArray(rows) && rows.length > 0 && rows[0].api_key) {
558
- return rows[0].api_key;
559
- }
560
- return null;
561
- }
562
- async function createApiKey(accessToken, userId) {
563
- const apiKey = `fmcp_${randomBytes(32).toString("hex")}`;
657
+ const apiKey = `lim_${randomBytes(32).toString("hex")}`;
658
+ const keyHash = createHash("sha256").update(apiKey).digest("hex");
564
659
  const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys`, {
565
660
  method: "POST",
566
661
  headers: {
@@ -570,7 +665,7 @@ async function createApiKey(accessToken, userId) {
570
665
  body: JSON.stringify({
571
666
  user_id: userId,
572
667
  key_name: "Liminal CLI",
573
- api_key: apiKey,
668
+ key_hash: keyHash,
574
669
  is_active: true
575
670
  })
576
671
  });
@@ -582,8 +677,6 @@ async function createApiKey(accessToken, userId) {
582
677
  return apiKey;
583
678
  }
584
679
  async function authenticateAndGetKey(auth) {
585
- const existingKey = await fetchApiKey(auth.accessToken, auth.userId);
586
- if (existingKey) return existingKey;
587
680
  return createApiKey(auth.accessToken, auth.userId);
588
681
  }
589
682
 
@@ -592,7 +685,7 @@ async function loginCommand() {
592
685
  printBanner();
593
686
  try {
594
687
  const config = loadConfig();
595
- if (config.apiKey && config.apiKey.startsWith("fmcp_")) {
688
+ if (config.apiKey && (config.apiKey.startsWith("lim_") || config.apiKey.startsWith("fmcp_"))) {
596
689
  console.log(" Already logged in.");
597
690
  console.log(" Run \x1B[1mliminal logout\x1B[0m first to switch accounts.");
598
691
  return;
@@ -819,9 +912,9 @@ import { homedir as homedir4 } from "os";
819
912
  var INFO3 = {
820
913
  id: "cursor",
821
914
  label: "Cursor",
822
- description: "AI-first code editor (GUI config required)",
915
+ description: "AI-first code editor (proxy-based, automated)",
823
916
  protocol: "openai-chat",
824
- automatable: false
917
+ automatable: true
825
918
  };
826
919
  function getCursorPaths() {
827
920
  const platform = process.platform;
@@ -871,23 +964,23 @@ var cursorConnector = {
871
964
  return [];
872
965
  },
873
966
  async setup(port) {
874
- const baseUrl = `http://127.0.0.1:${port}/v1`;
875
967
  return {
876
968
  success: true,
877
969
  shellExports: [],
878
- // No env vars — GUI only
879
970
  postSetupInstructions: [
880
- "Cursor requires manual configuration:",
971
+ "Run the automated setup:",
972
+ "",
973
+ " liminal setup cursor",
881
974
  "",
882
- " 1. Open Cursor Settings (not VS Code settings)",
883
- " 2. Go to Models",
884
- ' 3. Enable "Override OpenAI Base URL (when using key)"',
885
- ` 4. Set the base URL to: ${baseUrl}`,
886
- ' 5. Enter any string as the API key (e.g., "liminal")',
887
- " 6. Restart Cursor",
975
+ "This will:",
976
+ " 1. Generate and install a local CA certificate",
977
+ " 2. Launch Cursor with --proxy-server=http://127.0.0.1:" + port,
888
978
  "",
889
- "Cursor uses OpenAI format for all models, including Claude.",
890
- "Both Chat Completions and Agent mode (Responses API) are supported."
979
+ "All LLM API calls are transparently intercepted and compressed.",
980
+ "No Cursor settings changes needed \u2014 works with all models.",
981
+ "",
982
+ "Or launch Cursor manually:",
983
+ ` cursor --proxy-server=http://127.0.0.1:${port}`
891
984
  ]
892
985
  };
893
986
  },
@@ -1140,64 +1233,6 @@ async function logoutCommand() {
1140
1233
  console.log(" Run \x1B[1mliminal login\x1B[0m to reconnect.");
1141
1234
  }
1142
1235
 
1143
- // src/rsc/pipeline.ts
1144
- import {
1145
- CompressionPipeline,
1146
- RSCTransport,
1147
- RSCEventEmitter,
1148
- Session,
1149
- CircuitBreaker
1150
- } from "@cognisos/rsc-sdk";
1151
- var RSCPipelineWrapper = class {
1152
- pipeline;
1153
- session;
1154
- events;
1155
- transport;
1156
- circuitBreaker;
1157
- constructor(config) {
1158
- this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
1159
- this.transport = new RSCTransport({
1160
- baseUrl: config.rscBaseUrl,
1161
- apiKey: config.rscApiKey,
1162
- timeout: 3e4,
1163
- maxRetries: 3,
1164
- circuitBreaker: this.circuitBreaker
1165
- });
1166
- this.events = new RSCEventEmitter();
1167
- this.session = new Session(config.sessionId);
1168
- this.pipeline = new CompressionPipeline(
1169
- this.transport,
1170
- {
1171
- threshold: config.compressionThreshold,
1172
- learnFromResponses: config.learnFromResponses,
1173
- latencyBudgetMs: config.latencyBudgetMs,
1174
- sessionId: this.session.sessionId
1175
- },
1176
- this.events
1177
- );
1178
- }
1179
- async healthCheck() {
1180
- try {
1181
- await this.transport.get("/health");
1182
- return true;
1183
- } catch {
1184
- return false;
1185
- }
1186
- }
1187
- getSessionSummary() {
1188
- return this.session.getSummary();
1189
- }
1190
- getCircuitState() {
1191
- return this.circuitBreaker.getState();
1192
- }
1193
- isCircuitOpen() {
1194
- return this.circuitBreaker.getState() === "open";
1195
- }
1196
- resetCircuitBreaker() {
1197
- this.circuitBreaker.reset();
1198
- }
1199
- };
1200
-
1201
1236
  // src/proxy/completions.ts
1202
1237
  import { RSCCircuitOpenError as RSCCircuitOpenError2 } from "@cognisos/rsc-sdk";
1203
1238
 
@@ -1303,66 +1338,154 @@ function isIndentedCodeLine(line) {
1303
1338
  }
1304
1339
 
1305
1340
  // src/rsc/message-compressor.ts
1341
+ var PASSTHROUGH_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
1342
+ function sanitizeCompressedText(text) {
1343
+ return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
1344
+ }
1306
1345
  async function compressMessages(messages, pipeline, session, compressRoles) {
1307
1346
  let anyCompressed = false;
1308
1347
  let totalTokensSaved = 0;
1309
1348
  const compressed = await Promise.all(
1310
1349
  messages.map(async (msg) => {
1311
1350
  if (!compressRoles.has(msg.role)) return msg;
1312
- if (typeof msg.content === "string") {
1313
- return compressStringContent(msg, pipeline, session, (c, saved) => {
1314
- anyCompressed = anyCompressed || c;
1315
- totalTokensSaved += saved;
1316
- });
1317
- }
1318
- if (Array.isArray(msg.content)) {
1319
- return compressArrayContent(msg, pipeline, session, (c, saved) => {
1320
- anyCompressed = anyCompressed || c;
1321
- totalTokensSaved += saved;
1322
- });
1323
- }
1324
- return msg;
1351
+ return compressMessage(msg, pipeline, session, (c, saved) => {
1352
+ anyCompressed = anyCompressed || c;
1353
+ totalTokensSaved += saved;
1354
+ });
1325
1355
  })
1326
1356
  );
1327
1357
  return { messages: compressed, anyCompressed, totalTokensSaved };
1328
1358
  }
1329
- async function compressStringContent(msg, pipeline, session, record) {
1330
- const text = msg.content;
1359
+ async function compressConversation(pipeline, session, plan, options = { compressToolResults: true }) {
1360
+ if (!plan.shouldCompress) {
1361
+ return {
1362
+ messages: plan.messages.map((tm) => tm.message),
1363
+ anyCompressed: false,
1364
+ totalTokensSaved: 0
1365
+ };
1366
+ }
1367
+ let anyCompressed = false;
1368
+ let totalTokensSaved = 0;
1369
+ const record = (c, saved) => {
1370
+ anyCompressed = anyCompressed || c;
1371
+ totalTokensSaved += saved;
1372
+ };
1373
+ const log = options.logFn;
1374
+ const threshold = options.compressionThreshold ?? 100;
1375
+ const results = new Array(plan.messages.length);
1376
+ const compressible = [];
1377
+ for (let i = 0; i < plan.messages.length; i++) {
1378
+ const tm = plan.messages[i];
1379
+ const role = tm.message.role;
1380
+ const blockTypes = Array.isArray(tm.message.content) ? tm.message.content.map((b) => b.type).join(",") : "string";
1381
+ if (tm.tier === "hot") {
1382
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 HOT (verbatim)`);
1383
+ results[i] = tm.message;
1384
+ continue;
1385
+ }
1386
+ if (tm.eligibleTokens === 0) {
1387
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (0 eligible tok, skip)`);
1388
+ results[i] = tm.message;
1389
+ continue;
1390
+ }
1391
+ const proseEstimate = estimateProseTokens(tm.message, options.compressToolResults);
1392
+ if (proseEstimate < threshold) {
1393
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${proseEstimate} prose tok after segmentation < ${threshold} threshold, skip)`);
1394
+ results[i] = tm.message;
1395
+ continue;
1396
+ }
1397
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${tm.eligibleTokens} eligible tok, ~${proseEstimate} prose tok, compressing)`);
1398
+ compressible.push({ planIdx: i, tm });
1399
+ }
1400
+ compressible.sort((a, b) => b.tm.eligibleTokens - a.tm.eligibleTokens);
1401
+ for (const { planIdx, tm } of compressible) {
1402
+ results[planIdx] = await batchCompressMessage(tm.message, pipeline, session, record, options);
1403
+ }
1404
+ return { messages: results, anyCompressed, totalTokensSaved };
1405
+ }
1406
+ function estimateProseTokens(msg, compressToolResults) {
1407
+ const text = extractCompressibleText(msg, compressToolResults);
1408
+ if (!text) return 0;
1331
1409
  const segments = segmentContent(text);
1332
- const hasCode = segments.some((s) => s.type === "code");
1333
- if (!hasCode) {
1334
- try {
1335
- const result = await pipeline.compressForLLM(text);
1336
- session.recordCompression(result.metrics);
1337
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1338
- return { ...msg, content: result.text };
1339
- } catch (err) {
1340
- if (err instanceof RSCCircuitOpenError) {
1341
- session.recordFailure();
1342
- throw err;
1410
+ return segments.filter((s) => s.type === "prose").reduce((sum, s) => sum + Math.ceil(s.text.length / 4), 0);
1411
+ }
1412
+ function extractCompressibleText(msg, compressToolResults) {
1413
+ if (typeof msg.content === "string") {
1414
+ return msg.content.trim() ? msg.content : null;
1415
+ }
1416
+ if (!Array.isArray(msg.content)) return null;
1417
+ const parts = msg.content;
1418
+ const textSegments = [];
1419
+ for (const part of parts) {
1420
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
1421
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
1422
+ textSegments.push(part.text);
1423
+ }
1424
+ if (part.type === "tool_result" && compressToolResults) {
1425
+ const extracted = extractToolResultText(part);
1426
+ if (extracted) textSegments.push(extracted);
1427
+ }
1428
+ }
1429
+ return textSegments.length > 0 ? textSegments.join("\n\n") : null;
1430
+ }
1431
+ function extractToolResultText(part) {
1432
+ if (typeof part.content === "string" && part.content.trim()) {
1433
+ return part.content;
1434
+ }
1435
+ if (Array.isArray(part.content)) {
1436
+ const texts = part.content.filter((inner) => inner.type === "text" && typeof inner.text === "string" && inner.text.trim()).map((inner) => inner.text);
1437
+ return texts.length > 0 ? texts.join("\n") : null;
1438
+ }
1439
+ return null;
1440
+ }
1441
+ async function batchCompressMessage(msg, pipeline, session, record, options = { compressToolResults: true }) {
1442
+ if (typeof msg.content === "string") {
1443
+ return compressStringContent(msg, pipeline, session, record, options.semaphore, options.semaphoreTimeoutMs);
1444
+ }
1445
+ if (!Array.isArray(msg.content)) return msg;
1446
+ const parts = msg.content;
1447
+ const textSegments = [];
1448
+ const batchedIndices = /* @__PURE__ */ new Set();
1449
+ for (let i = 0; i < parts.length; i++) {
1450
+ const part = parts[i];
1451
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
1452
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
1453
+ textSegments.push(part.text);
1454
+ batchedIndices.add(i);
1455
+ }
1456
+ if (part.type === "tool_result" && options.compressToolResults) {
1457
+ const extracted = extractToolResultText(part);
1458
+ if (extracted) {
1459
+ textSegments.push(extracted);
1460
+ batchedIndices.add(i);
1343
1461
  }
1344
- session.recordFailure();
1345
- return msg;
1346
1462
  }
1347
1463
  }
1464
+ if (textSegments.length === 0) return msg;
1465
+ const batchText = textSegments.join("\n\n");
1348
1466
  try {
1349
- const parts = await Promise.all(
1350
- segments.map(async (seg) => {
1351
- if (seg.type === "code") return seg.text;
1352
- if (seg.text.trim().length === 0) return seg.text;
1353
- try {
1354
- const result = await pipeline.compressForLLM(seg.text);
1355
- session.recordCompression(result.metrics);
1356
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1357
- return result.text;
1358
- } catch (err) {
1359
- if (err instanceof RSCCircuitOpenError) throw err;
1360
- session.recordFailure();
1361
- return seg.text;
1467
+ const compressed = await compressTextWithSegmentation(batchText, pipeline, session, record, options.semaphore, options.semaphoreTimeoutMs);
1468
+ const newParts = [];
1469
+ let isFirstEligible = true;
1470
+ for (let i = 0; i < parts.length; i++) {
1471
+ if (!batchedIndices.has(i)) {
1472
+ newParts.push(parts[i]);
1473
+ continue;
1474
+ }
1475
+ if (isFirstEligible) {
1476
+ if (parts[i].type === "text") {
1477
+ newParts.push({ ...parts[i], text: compressed });
1478
+ } else if (parts[i].type === "tool_result") {
1479
+ newParts.push({ ...parts[i], content: compressed });
1362
1480
  }
1363
- })
1364
- );
1365
- return { ...msg, content: parts.join("") };
1481
+ isFirstEligible = false;
1482
+ } else {
1483
+ if (parts[i].type === "tool_result") {
1484
+ newParts.push({ ...parts[i], content: "" });
1485
+ }
1486
+ }
1487
+ }
1488
+ return { ...msg, content: newParts };
1366
1489
  } catch (err) {
1367
1490
  if (err instanceof RSCCircuitOpenError) {
1368
1491
  session.recordFailure();
@@ -1372,37 +1495,34 @@ async function compressStringContent(msg, pipeline, session, record) {
1372
1495
  return msg;
1373
1496
  }
1374
1497
  }
1375
- async function compressTextWithSegmentation(text, pipeline, session, record) {
1376
- const segments = segmentContent(text);
1377
- const hasCode = segments.some((s) => s.type === "code");
1378
- if (!hasCode) {
1379
- const result = await pipeline.compressForLLM(text);
1380
- session.recordCompression(result.metrics);
1381
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1382
- return result.text;
1498
+ async function compressMessage(msg, pipeline, session, record, options = { compressToolResults: true }) {
1499
+ if (typeof msg.content === "string") {
1500
+ return compressStringContent(msg, pipeline, session, record);
1501
+ }
1502
+ if (Array.isArray(msg.content)) {
1503
+ return compressArrayContent(msg, pipeline, session, record, options);
1504
+ }
1505
+ return msg;
1506
+ }
1507
+ async function compressStringContent(msg, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
1508
+ const text = msg.content;
1509
+ try {
1510
+ const compressed = await compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs);
1511
+ return { ...msg, content: compressed };
1512
+ } catch (err) {
1513
+ if (err instanceof RSCCircuitOpenError) {
1514
+ session.recordFailure();
1515
+ throw err;
1516
+ }
1517
+ session.recordFailure();
1518
+ return msg;
1383
1519
  }
1384
- const parts = await Promise.all(
1385
- segments.map(async (seg) => {
1386
- if (seg.type === "code") return seg.text;
1387
- if (seg.text.trim().length === 0) return seg.text;
1388
- try {
1389
- const result = await pipeline.compressForLLM(seg.text);
1390
- session.recordCompression(result.metrics);
1391
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1392
- return result.text;
1393
- } catch (err) {
1394
- if (err instanceof RSCCircuitOpenError) throw err;
1395
- session.recordFailure();
1396
- return seg.text;
1397
- }
1398
- })
1399
- );
1400
- return parts.join("");
1401
1520
  }
1402
- async function compressArrayContent(msg, pipeline, session, record) {
1521
+ async function compressArrayContent(msg, pipeline, session, record, options = { compressToolResults: true }) {
1403
1522
  const parts = msg.content;
1404
1523
  const compressedParts = await Promise.all(
1405
1524
  parts.map(async (part) => {
1525
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) return part;
1406
1526
  if (part.type === "text" && typeof part.text === "string") {
1407
1527
  try {
1408
1528
  const compressed = await compressTextWithSegmentation(part.text, pipeline, session, record);
@@ -1416,11 +1536,183 @@ async function compressArrayContent(msg, pipeline, session, record) {
1416
1536
  return part;
1417
1537
  }
1418
1538
  }
1539
+ if (part.type === "tool_result" && options.compressToolResults) {
1540
+ return compressToolResult(part, pipeline, session, record);
1541
+ }
1419
1542
  return part;
1420
1543
  })
1421
1544
  );
1422
1545
  return { ...msg, content: compressedParts };
1423
1546
  }
1547
+ async function compressToolResult(part, pipeline, session, record) {
1548
+ const content = part.content;
1549
+ if (typeof content === "string") {
1550
+ try {
1551
+ const compressed = await compressTextWithSegmentation(content, pipeline, session, record);
1552
+ return { ...part, content: compressed };
1553
+ } catch (err) {
1554
+ if (err instanceof RSCCircuitOpenError) {
1555
+ session.recordFailure();
1556
+ throw err;
1557
+ }
1558
+ session.recordFailure();
1559
+ return part;
1560
+ }
1561
+ }
1562
+ if (Array.isArray(content)) {
1563
+ try {
1564
+ const compressedInner = await Promise.all(
1565
+ content.map(async (inner) => {
1566
+ if (inner.type === "text" && typeof inner.text === "string") {
1567
+ try {
1568
+ const compressed = await compressTextWithSegmentation(inner.text, pipeline, session, record);
1569
+ return { ...inner, text: compressed };
1570
+ } catch (err) {
1571
+ if (err instanceof RSCCircuitOpenError) throw err;
1572
+ session.recordFailure();
1573
+ return inner;
1574
+ }
1575
+ }
1576
+ return inner;
1577
+ })
1578
+ );
1579
+ return { ...part, content: compressedInner };
1580
+ } catch (err) {
1581
+ if (err instanceof RSCCircuitOpenError) {
1582
+ session.recordFailure();
1583
+ throw err;
1584
+ }
1585
+ session.recordFailure();
1586
+ return part;
1587
+ }
1588
+ }
1589
+ return part;
1590
+ }
1591
+ async function compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
1592
+ const segments = segmentContent(text);
1593
+ const hasCode = segments.some((s) => s.type === "code");
1594
+ if (!hasCode) {
1595
+ if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
1596
+ try {
1597
+ const result = await pipeline.compressForLLM(text);
1598
+ session.recordCompression(result.metrics);
1599
+ const saved = Math.max(0, result.metrics.tokensSaved);
1600
+ record(!result.metrics.skipped, saved);
1601
+ return sanitizeCompressedText(result.text);
1602
+ } finally {
1603
+ if (semaphore) semaphore.release();
1604
+ }
1605
+ }
1606
+ const parts = await Promise.all(
1607
+ segments.map(async (seg) => {
1608
+ if (seg.type === "code") return seg.text;
1609
+ if (seg.text.trim().length === 0) return seg.text;
1610
+ if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
1611
+ try {
1612
+ const result = await pipeline.compressForLLM(seg.text);
1613
+ session.recordCompression(result.metrics);
1614
+ const saved = Math.max(0, result.metrics.tokensSaved);
1615
+ record(!result.metrics.skipped, saved);
1616
+ return sanitizeCompressedText(result.text);
1617
+ } catch (err) {
1618
+ if (err instanceof RSCCircuitOpenError) throw err;
1619
+ session.recordFailure();
1620
+ return seg.text;
1621
+ } finally {
1622
+ if (semaphore) semaphore.release();
1623
+ }
1624
+ })
1625
+ );
1626
+ return parts.join("");
1627
+ }
1628
+
1629
+ // src/rsc/conversation-analyzer.ts
1630
+ var SKIP_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
1631
+ function estimateTokens(text) {
1632
+ return Math.ceil(text.length / 4);
1633
+ }
1634
+ function estimateBlockTokens(block, compressToolResults) {
1635
+ if (SKIP_BLOCK_TYPES.has(block.type)) return 0;
1636
+ if (block.type === "text" && typeof block.text === "string") {
1637
+ return estimateTokens(block.text);
1638
+ }
1639
+ if (block.type === "tool_result" && compressToolResults) {
1640
+ if (typeof block.content === "string") {
1641
+ return estimateTokens(block.content);
1642
+ }
1643
+ if (Array.isArray(block.content)) {
1644
+ return block.content.reduce(
1645
+ (sum, inner) => sum + estimateBlockTokens(inner, compressToolResults),
1646
+ 0
1647
+ );
1648
+ }
1649
+ }
1650
+ return 0;
1651
+ }
1652
+ function estimateMessageTokens(msg, config) {
1653
+ if (!config.compressRoles.has(msg.role)) return 0;
1654
+ if (typeof msg.content === "string") {
1655
+ return estimateTokens(msg.content);
1656
+ }
1657
+ if (Array.isArray(msg.content)) {
1658
+ return msg.content.reduce(
1659
+ (sum, part) => sum + estimateBlockTokens(part, config.compressToolResults),
1660
+ 0
1661
+ );
1662
+ }
1663
+ return 0;
1664
+ }
1665
+ function analyzeConversation(messages, config) {
1666
+ const n = messages.length;
1667
+ if (n < 5) {
1668
+ const tiered2 = messages.map((msg, i) => ({
1669
+ index: i,
1670
+ message: msg,
1671
+ tier: "hot",
1672
+ eligibleTokens: estimateMessageTokens(msg, config)
1673
+ }));
1674
+ return {
1675
+ messages: tiered2,
1676
+ totalEligibleTokens: 0,
1677
+ shouldCompress: false,
1678
+ hotCount: n,
1679
+ warmCount: 0,
1680
+ coldCount: 0
1681
+ };
1682
+ }
1683
+ const coldEnd = Math.floor(n * config.coldFraction);
1684
+ const hotStart = n - Math.floor(n * config.hotFraction);
1685
+ let totalEligibleTokens = 0;
1686
+ let hotCount = 0;
1687
+ let warmCount = 0;
1688
+ let coldCount = 0;
1689
+ const tiered = messages.map((msg, i) => {
1690
+ let tier;
1691
+ if (i >= hotStart) {
1692
+ tier = "hot";
1693
+ hotCount++;
1694
+ } else if (i < coldEnd) {
1695
+ tier = "cold";
1696
+ coldCount++;
1697
+ } else {
1698
+ tier = "warm";
1699
+ warmCount++;
1700
+ }
1701
+ const eligibleTokens = estimateMessageTokens(msg, config);
1702
+ if (tier !== "hot") {
1703
+ totalEligibleTokens += eligibleTokens;
1704
+ }
1705
+ return { index: i, message: msg, tier, eligibleTokens };
1706
+ });
1707
+ return {
1708
+ messages: tiered,
1709
+ totalEligibleTokens,
1710
+ shouldCompress: totalEligibleTokens >= config.aggregateThreshold,
1711
+ hotCount,
1712
+ warmCount,
1713
+ coldCount
1714
+ };
1715
+ }
1424
1716
 
1425
1717
  // src/rsc/learning.ts
1426
1718
  function createStreamLearningBuffer(pipeline) {
@@ -1523,6 +1815,25 @@ async function pipeSSEResponse(upstreamResponse, clientRes, onContentDelta, onCo
1523
1815
  }
1524
1816
  }
1525
1817
 
1818
+ // src/terminology.ts
1819
+ var TIER_LABELS = {
1820
+ HOT: "Active",
1821
+ WARM: "Recent",
1822
+ COLD: "Archived"
1823
+ };
1824
+ function formatTiersLog(hot, warm, cold, eligibleTokens) {
1825
+ return `[MEMORY] ${TIER_LABELS.HOT}:${hot} ${TIER_LABELS.WARM}:${warm} ${TIER_LABELS.COLD}:${cold} \xB7 ${eligibleTokens} tokens eligible`;
1826
+ }
1827
+ function formatSavedLog(tokensSaved, latencyMs) {
1828
+ return `[SAVED] ${tokensSaved} tokens (${latencyMs}ms)`;
1829
+ }
1830
+ function formatDegradeLog() {
1831
+ return "[STATUS] Connection degraded \u2014 passing through directly";
1832
+ }
1833
+ function formatResponseLog(model, tokensSaved, streaming = false) {
1834
+ return `[RESPONSE] ${streaming ? "Streaming " : ""}${model} response \u2192 client (saved:${tokensSaved}tok)`;
1835
+ }
1836
+
1526
1837
  // src/proxy/completions.ts
1527
1838
  function setCORSHeaders(res) {
1528
1839
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -1539,7 +1850,7 @@ function extractBearerToken(req) {
1539
1850
  if (!auth || !auth.startsWith("Bearer ")) return null;
1540
1851
  return auth.slice(7);
1541
1852
  }
1542
- async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1853
+ async function handleChatCompletions(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
1543
1854
  const request = body;
1544
1855
  if (!request.messages || !Array.isArray(request.messages)) {
1545
1856
  sendJSON(res, 400, {
@@ -1558,23 +1869,45 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1558
1869
  let anyCompressed = false;
1559
1870
  let totalTokensSaved = 0;
1560
1871
  if (config.enabled && !pipeline.isCircuitOpen()) {
1872
+ const compressStart = Date.now();
1561
1873
  try {
1562
1874
  const compressRoles = new Set(config.compressRoles);
1563
- const result = await compressMessages(
1564
- request.messages,
1875
+ const plan = analyzeConversation(request.messages, {
1876
+ hotFraction: config.hotFraction,
1877
+ coldFraction: config.coldFraction,
1878
+ aggregateThreshold: config.aggregateThreshold,
1879
+ compressRoles,
1880
+ compressToolResults: config.compressToolResults
1881
+ });
1882
+ if (plan.shouldCompress) {
1883
+ logger.log(formatTiersLog(plan.hotCount, plan.warmCount, plan.coldCount, plan.totalEligibleTokens));
1884
+ }
1885
+ const result = await compressConversation(
1565
1886
  pipeline.pipeline,
1566
1887
  pipeline.session,
1567
- compressRoles
1888
+ plan,
1889
+ {
1890
+ compressToolResults: config.compressToolResults,
1891
+ compressionThreshold: config.compressionThreshold,
1892
+ logFn: (msg) => logger.log(msg),
1893
+ semaphore,
1894
+ semaphoreTimeoutMs: config.concurrencyTimeoutMs
1895
+ }
1568
1896
  );
1569
1897
  messages = result.messages;
1570
1898
  anyCompressed = result.anyCompressed;
1571
1899
  totalTokensSaved = result.totalTokensSaved;
1900
+ const latencyMs = Date.now() - compressStart;
1901
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
1902
+ if (alert) {
1903
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
1904
+ }
1572
1905
  if (result.totalTokensSaved > 0) {
1573
- logger.log(`[COMPRESS] Saved ${result.totalTokensSaved} tokens`);
1906
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
1574
1907
  }
1575
1908
  } catch (err) {
1576
1909
  if (err instanceof RSCCircuitOpenError2) {
1577
- logger.log("[DEGRADE] Circuit breaker open \u2014 passing through directly");
1910
+ logger.log(formatDegradeLog());
1578
1911
  } else {
1579
1912
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
1580
1913
  }
@@ -1607,6 +1940,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1607
1940
  }
1608
1941
  if (request.stream && upstreamResponse.body) {
1609
1942
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
1943
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
1610
1944
  await pipeSSEResponse(
1611
1945
  upstreamResponse,
1612
1946
  res,
@@ -1617,6 +1951,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1617
1951
  return;
1618
1952
  }
1619
1953
  const responseBody = await upstreamResponse.text();
1954
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
1620
1955
  let finalBody = responseBody;
1621
1956
  if (totalTokensSaved > 0) {
1622
1957
  try {
@@ -1757,7 +2092,7 @@ async function pipeAnthropicSSEResponse(upstreamResponse, clientRes, onContentDe
1757
2092
  function setCORSHeaders2(res) {
1758
2093
  res.setHeader("Access-Control-Allow-Origin", "*");
1759
2094
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
1760
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta");
2095
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, x-liminal-session");
1761
2096
  }
1762
2097
  function sendAnthropicError(res, status, type, message) {
1763
2098
  setCORSHeaders2(res);
@@ -1788,7 +2123,7 @@ function convertCompressedToAnthropic(messages) {
1788
2123
  content: msg.content
1789
2124
  }));
1790
2125
  }
1791
- async function handleAnthropicMessages(req, res, body, pipeline, config, logger) {
2126
+ async function handleAnthropicMessages(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
1792
2127
  const request = body;
1793
2128
  if (!request.messages || !Array.isArray(request.messages)) {
1794
2129
  sendAnthropicError(res, 400, "invalid_request_error", "messages is required and must be an array");
@@ -1807,24 +2142,46 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1807
2142
  let anyCompressed = false;
1808
2143
  let totalTokensSaved = 0;
1809
2144
  if (config.enabled && !pipeline.isCircuitOpen()) {
2145
+ const compressStart = Date.now();
1810
2146
  try {
1811
2147
  const compressRoles = new Set(config.compressRoles);
1812
2148
  const compressible = convertAnthropicToCompressible(request.messages);
1813
- const result = await compressMessages(
1814
- compressible,
2149
+ const plan = analyzeConversation(compressible, {
2150
+ hotFraction: config.hotFraction,
2151
+ coldFraction: config.coldFraction,
2152
+ aggregateThreshold: config.aggregateThreshold,
2153
+ compressRoles,
2154
+ compressToolResults: config.compressToolResults
2155
+ });
2156
+ if (plan.shouldCompress) {
2157
+ logger.log(formatTiersLog(plan.hotCount, plan.warmCount, plan.coldCount, plan.totalEligibleTokens));
2158
+ }
2159
+ const result = await compressConversation(
1815
2160
  pipeline.pipeline,
1816
2161
  pipeline.session,
1817
- compressRoles
2162
+ plan,
2163
+ {
2164
+ compressToolResults: config.compressToolResults,
2165
+ compressionThreshold: config.compressionThreshold,
2166
+ logFn: (msg) => logger.log(msg),
2167
+ semaphore,
2168
+ semaphoreTimeoutMs: config.concurrencyTimeoutMs
2169
+ }
1818
2170
  );
1819
2171
  messages = convertCompressedToAnthropic(result.messages);
1820
2172
  anyCompressed = result.anyCompressed;
1821
2173
  totalTokensSaved = result.totalTokensSaved;
2174
+ const latencyMs = Date.now() - compressStart;
2175
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
2176
+ if (alert) {
2177
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
2178
+ }
1822
2179
  if (result.totalTokensSaved > 0) {
1823
- logger.log(`[COMPRESS] Saved ${result.totalTokensSaved} tokens`);
2180
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
1824
2181
  }
1825
2182
  } catch (err) {
1826
2183
  if (err instanceof RSCCircuitOpenError3) {
1827
- logger.log("[DEGRADE] Circuit breaker open -- passing through directly");
2184
+ logger.log(formatDegradeLog());
1828
2185
  } else {
1829
2186
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
1830
2187
  }
@@ -1862,6 +2219,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1862
2219
  }
1863
2220
  if (request.stream && upstreamResponse.body) {
1864
2221
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
2222
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
1865
2223
  await pipeAnthropicSSEResponse(
1866
2224
  upstreamResponse,
1867
2225
  res,
@@ -1872,6 +2230,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1872
2230
  return;
1873
2231
  }
1874
2232
  const responseBody = await upstreamResponse.text();
2233
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
1875
2234
  let finalBody = responseBody;
1876
2235
  if (totalTokensSaved > 0) {
1877
2236
  try {
@@ -2102,7 +2461,7 @@ function extractOutputText(output) {
2102
2461
  }
2103
2462
  return texts.join("");
2104
2463
  }
2105
- async function handleResponses(req, res, body, pipeline, config, logger) {
2464
+ async function handleResponses(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
2106
2465
  const request = body;
2107
2466
  if (request.input === void 0 || request.input === null) {
2108
2467
  sendJSON2(res, 400, {
@@ -2121,6 +2480,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2121
2480
  let anyCompressed = false;
2122
2481
  let totalTokensSaved = 0;
2123
2482
  if (config.enabled && !pipeline.isCircuitOpen()) {
2483
+ const compressStart = Date.now();
2124
2484
  try {
2125
2485
  const compressRoles = new Set(config.compressRoles);
2126
2486
  const compressible = inputToCompressibleMessages(request.input);
@@ -2134,13 +2494,18 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2134
2494
  compressedInput = applyCompressedToInput(request.input, result.messages);
2135
2495
  anyCompressed = result.anyCompressed;
2136
2496
  totalTokensSaved = result.totalTokensSaved;
2497
+ const latencyMs = Date.now() - compressStart;
2498
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
2499
+ if (alert) {
2500
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
2501
+ }
2137
2502
  if (result.totalTokensSaved > 0) {
2138
- logger.log(`[COMPRESS] Responses API: saved ${result.totalTokensSaved} tokens`);
2503
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
2139
2504
  }
2140
2505
  }
2141
2506
  } catch (err) {
2142
2507
  if (err instanceof RSCCircuitOpenError4) {
2143
- logger.log("[DEGRADE] Circuit breaker open \u2014 passing through directly");
2508
+ logger.log(formatDegradeLog());
2144
2509
  } else {
2145
2510
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
2146
2511
  }
@@ -2175,6 +2540,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2175
2540
  }
2176
2541
  if (request.stream && upstreamResponse.body) {
2177
2542
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
2543
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
2178
2544
  await pipeResponsesSSE(
2179
2545
  upstreamResponse,
2180
2546
  res,
@@ -2185,6 +2551,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2185
2551
  return;
2186
2552
  }
2187
2553
  const responseBody = await upstreamResponse.text();
2554
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
2188
2555
  let finalBody = responseBody;
2189
2556
  if (totalTokensSaved > 0) {
2190
2557
  try {
@@ -2226,11 +2593,51 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2226
2593
  }
2227
2594
  }
2228
2595
 
2596
+ // src/proxy/session-identity.ts
2597
+ import * as crypto from "crypto";
2598
+ function identifySession(req, pathname) {
2599
+ const connector = detectConnector(req, pathname);
2600
+ const windowHash = deriveWindowHash(req);
2601
+ return { connector, windowHash, raw: `${connector}:${windowHash}` };
2602
+ }
2603
+ function detectConnector(req, pathname) {
2604
+ if (req.headers["x-api-key"] || req.headers["anthropic-version"]) {
2605
+ return "claude-code";
2606
+ }
2607
+ if (pathname.startsWith("/v1/responses") || pathname.startsWith("/responses")) {
2608
+ return "codex";
2609
+ }
2610
+ const ua = req.headers["user-agent"] ?? "";
2611
+ if (/cursor/i.test(ua)) {
2612
+ return "cursor";
2613
+ }
2614
+ return "openai-compatible";
2615
+ }
2616
+ function deriveWindowHash(req) {
2617
+ const liminalSession = req.headers["x-liminal-session"];
2618
+ if (typeof liminalSession === "string" && liminalSession.length > 0) {
2619
+ return liminalSession;
2620
+ }
2621
+ const credential = extractCredential(req);
2622
+ if (!credential) return "anonymous";
2623
+ return crypto.createHash("sha256").update(credential).digest("hex").slice(0, 8);
2624
+ }
2625
+ function extractCredential(req) {
2626
+ const apiKey = req.headers["x-api-key"];
2627
+ if (typeof apiKey === "string" && apiKey.length > 0) return apiKey;
2628
+ const auth = req.headers["authorization"];
2629
+ if (typeof auth === "string") {
2630
+ const match = auth.match(/^Bearer\s+(.+)$/i);
2631
+ if (match) return match[1];
2632
+ }
2633
+ return null;
2634
+ }
2635
+
2229
2636
  // src/proxy/handler.ts
2230
2637
  function setCORSHeaders4(res) {
2231
2638
  res.setHeader("Access-Control-Allow-Origin", "*");
2232
2639
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH");
2233
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, anthropic-dangerous-direct-browser-access");
2640
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, anthropic-dangerous-direct-browser-access, x-liminal-session");
2234
2641
  res.setHeader("Access-Control-Max-Age", "86400");
2235
2642
  }
2236
2643
  function sendJSON3(res, status, body) {
@@ -2331,7 +2738,8 @@ async function passthroughToUpstream(req, res, fullUrl, config, logger) {
2331
2738
  }
2332
2739
  }
2333
2740
  }
2334
- function createRequestHandler(pipeline, config, logger) {
2741
+ function createRequestHandler(deps) {
2742
+ const { sessions, semaphore, latencyMonitor, mitmStats, config, logger } = deps;
2335
2743
  const startTime = Date.now();
2336
2744
  return async (req, res) => {
2337
2745
  try {
@@ -2347,27 +2755,38 @@ function createRequestHandler(pipeline, config, logger) {
2347
2755
  return;
2348
2756
  }
2349
2757
  if (method === "GET" && (url === "/health" || url === "/")) {
2350
- const summary = pipeline.getSessionSummary();
2758
+ const sessionSummaries = sessions.getAllSummaries();
2351
2759
  sendJSON3(res, 200, {
2352
2760
  status: "ok",
2353
2761
  version: config.rscApiKey ? "connected" : "no-api-key",
2354
- rsc_connected: !pipeline.isCircuitOpen(),
2355
- circuit_state: pipeline.getCircuitState(),
2356
- session_id: summary.sessionId,
2357
2762
  uptime_ms: Date.now() - startTime,
2358
- session: {
2359
- tokens_processed: summary.tokensProcessed,
2360
- tokens_saved: summary.tokensSaved,
2361
- calls_total: summary.totalCalls,
2362
- calls_compressed: summary.compressedCalls,
2363
- calls_skipped: summary.skippedCalls,
2364
- calls_failed: summary.failedCalls,
2365
- patterns_learned: summary.patternsLearned,
2366
- estimated_cost_saved_usd: summary.estimatedCostSaved
2367
- }
2763
+ concurrency: {
2764
+ active_sessions: sessions.activeCount,
2765
+ semaphore_available: semaphore.available,
2766
+ semaphore_waiting: semaphore.waiting,
2767
+ max_concurrent_rsc_calls: config.concurrencyLimit
2768
+ },
2769
+ latency: {
2770
+ global_p95_ms: latencyMonitor.getGlobalP95()
2771
+ },
2772
+ mitm: mitmStats.snapshot(),
2773
+ sessions: sessionSummaries.map((s) => ({
2774
+ session_key: s.key,
2775
+ connector: s.connector,
2776
+ circuit_state: s.circuitState,
2777
+ tokens_processed: s.tokensProcessed,
2778
+ tokens_saved: s.tokensSaved,
2779
+ calls_total: s.totalCalls,
2780
+ calls_compressed: s.compressedCalls,
2781
+ calls_failed: s.failedCalls,
2782
+ p95_latency_ms: latencyMonitor.getSessionP95(s.key),
2783
+ last_active_ago_ms: Date.now() - s.lastAccessedAt
2784
+ }))
2368
2785
  });
2369
2786
  return;
2370
2787
  }
2788
+ const sessionKey = identifySession(req, url);
2789
+ const pipeline = sessions.getOrCreate(sessionKey);
2371
2790
  if (method === "POST" && (url === "/v1/chat/completions" || url === "/chat/completions")) {
2372
2791
  const body = await readBody(req);
2373
2792
  let parsed;
@@ -2379,7 +2798,7 @@ function createRequestHandler(pipeline, config, logger) {
2379
2798
  });
2380
2799
  return;
2381
2800
  }
2382
- await handleChatCompletions(req, res, parsed, pipeline, config, logger);
2801
+ await handleChatCompletions(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2383
2802
  return;
2384
2803
  }
2385
2804
  if (method === "POST" && (url === "/v1/responses" || url === "/responses")) {
@@ -2393,7 +2812,7 @@ function createRequestHandler(pipeline, config, logger) {
2393
2812
  });
2394
2813
  return;
2395
2814
  }
2396
- await handleResponses(req, res, parsed, pipeline, config, logger);
2815
+ await handleResponses(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2397
2816
  return;
2398
2817
  }
2399
2818
  if (method === "POST" && (url === "/v1/messages" || url === "/messages")) {
@@ -2408,7 +2827,7 @@ function createRequestHandler(pipeline, config, logger) {
2408
2827
  });
2409
2828
  return;
2410
2829
  }
2411
- await handleAnthropicMessages(req, res, parsed, pipeline, config, logger);
2830
+ await handleAnthropicMessages(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2412
2831
  return;
2413
2832
  }
2414
2833
  await passthroughToUpstream(req, res, fullUrl, config, logger);
@@ -2432,9 +2851,11 @@ var ProxyServer = class {
2432
2851
  activePort = null;
2433
2852
  requestedPort;
2434
2853
  handler;
2435
- constructor(port, handler) {
2854
+ connectHandler;
2855
+ constructor(port, handler, connectHandler) {
2436
2856
  this.requestedPort = port;
2437
2857
  this.handler = handler;
2858
+ this.connectHandler = connectHandler ?? null;
2438
2859
  }
2439
2860
  async start() {
2440
2861
  let lastError = null;
@@ -2456,6 +2877,9 @@ var ProxyServer = class {
2456
2877
  listen(port) {
2457
2878
  return new Promise((resolve, reject) => {
2458
2879
  const server = http.createServer(this.handler);
2880
+ if (this.connectHandler) {
2881
+ server.on("connect", this.connectHandler);
2882
+ }
2459
2883
  server.on("error", reject);
2460
2884
  server.listen(port, "127.0.0.1", () => {
2461
2885
  server.removeListener("error", reject);
@@ -2480,6 +2904,10 @@ var ProxyServer = class {
2480
2904
  getPort() {
2481
2905
  return this.activePort;
2482
2906
  }
2907
+ /** Expose internal HTTP server for MITM bridge socket injection */
2908
+ getHttpServer() {
2909
+ return this.server;
2910
+ }
2483
2911
  };
2484
2912
 
2485
2913
  // src/daemon/logger.ts
@@ -2530,26 +2958,710 @@ var FileLogger = class {
2530
2958
  }
2531
2959
  };
2532
2960
 
2533
- // src/daemon/lifecycle.ts
2534
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync6 } from "fs";
2535
- import { fork } from "child_process";
2536
- import { fileURLToPath } from "url";
2537
- function writePidFile(pid) {
2538
- writeFileSync3(PID_FILE, String(pid), "utf-8");
2539
- }
2540
- function readPidFile() {
2541
- if (!existsSync6(PID_FILE)) return null;
2542
- try {
2543
- const content = readFileSync4(PID_FILE, "utf-8").trim();
2544
- const pid = parseInt(content, 10);
2545
- return isNaN(pid) ? null : pid;
2546
- } catch {
2547
- return null;
2548
- }
2549
- }
2961
+ // src/rsc/session-manager.ts
2962
+ init_pipeline();
2963
+ var DEFAULT_MAX_SESSIONS = 10;
2964
+ var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
2965
+ var EVICTION_INTERVAL_MS = 6e4;
2966
+ var SessionManager = class {
2967
+ sessions = /* @__PURE__ */ new Map();
2968
+ config;
2969
+ evictionTimer = null;
2970
+ constructor(config) {
2971
+ this.config = {
2972
+ ...config,
2973
+ maxSessions: config.maxSessions ?? DEFAULT_MAX_SESSIONS,
2974
+ sessionTtlMs: config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS
2975
+ };
2976
+ this.evictionTimer = setInterval(() => this.evictStale(), EVICTION_INTERVAL_MS);
2977
+ if (this.evictionTimer.unref) {
2978
+ this.evictionTimer.unref();
2979
+ }
2980
+ }
2981
+ // ── Public API ──────────────────────────────────────────────────────
2982
+ getOrCreate(key) {
2983
+ const existing = this.sessions.get(key.raw);
2984
+ if (existing) {
2985
+ existing.lastAccessedAt = Date.now();
2986
+ existing.requestCount++;
2987
+ return existing.pipeline;
2988
+ }
2989
+ if (this.sessions.size >= this.config.maxSessions) {
2990
+ this.evictLRU();
2991
+ }
2992
+ const pipeline = new RSCPipelineWrapper({
2993
+ ...this.config.pipelineConfig,
2994
+ sessionId: key.raw
2995
+ });
2996
+ this.sessions.set(key.raw, {
2997
+ pipeline,
2998
+ lastAccessedAt: Date.now(),
2999
+ requestCount: 1,
3000
+ connector: key.connector
3001
+ });
3002
+ this.config.onSessionCreated?.(key.raw, pipeline);
3003
+ return pipeline;
3004
+ }
3005
+ getAllSummaries() {
3006
+ const entries = [];
3007
+ for (const [key, managed] of this.sessions) {
3008
+ entries.push(this.buildHealthEntry(key, managed));
3009
+ }
3010
+ return entries;
3011
+ }
3012
+ getSessionSummary(key) {
3013
+ const managed = this.sessions.get(key);
3014
+ if (!managed) return null;
3015
+ return this.buildHealthEntry(key, managed);
3016
+ }
3017
+ get activeCount() {
3018
+ return this.sessions.size;
3019
+ }
3020
+ shutdown() {
3021
+ if (this.evictionTimer !== null) {
3022
+ clearInterval(this.evictionTimer);
3023
+ this.evictionTimer = null;
3024
+ }
3025
+ this.sessions.clear();
3026
+ }
3027
+ // ── Internals ───────────────────────────────────────────────────────
3028
+ buildHealthEntry(key, managed) {
3029
+ const summary = managed.pipeline.getSessionSummary();
3030
+ return {
3031
+ key,
3032
+ connector: managed.connector,
3033
+ circuitState: managed.pipeline.getCircuitState(),
3034
+ tokensProcessed: summary.tokensProcessed,
3035
+ tokensSaved: summary.tokensSaved,
3036
+ totalCalls: summary.totalCalls,
3037
+ compressedCalls: summary.compressedCalls,
3038
+ failedCalls: summary.failedCalls,
3039
+ lastAccessedAt: managed.lastAccessedAt
3040
+ };
3041
+ }
3042
+ evictStale() {
3043
+ const now = Date.now();
3044
+ const cutoff = now - this.config.sessionTtlMs;
3045
+ for (const [key, managed] of this.sessions) {
3046
+ if (managed.lastAccessedAt < cutoff) {
3047
+ this.sessions.delete(key);
3048
+ this.config.onSessionEvicted?.(key);
3049
+ }
3050
+ }
3051
+ }
3052
+ evictLRU() {
3053
+ let oldestKey = null;
3054
+ let oldestTime = Infinity;
3055
+ for (const [key, managed] of this.sessions) {
3056
+ if (managed.lastAccessedAt < oldestTime) {
3057
+ oldestTime = managed.lastAccessedAt;
3058
+ oldestKey = key;
3059
+ }
3060
+ }
3061
+ if (oldestKey !== null) {
3062
+ this.sessions.delete(oldestKey);
3063
+ this.config.onSessionEvicted?.(oldestKey);
3064
+ }
3065
+ }
3066
+ };
3067
+
3068
+ // src/rsc/semaphore.ts
3069
+ var SemaphoreTimeoutError = class extends Error {
3070
+ constructor(timeoutMs) {
3071
+ super(`Semaphore acquire timed out after ${timeoutMs}ms`);
3072
+ this.name = "SemaphoreTimeoutError";
3073
+ }
3074
+ };
3075
+ var Semaphore = class {
3076
+ permits;
3077
+ queue = [];
3078
+ constructor(maxPermits) {
3079
+ if (maxPermits < 1) throw new RangeError("maxPermits must be >= 1");
3080
+ this.permits = maxPermits;
3081
+ }
3082
+ get available() {
3083
+ return this.permits;
3084
+ }
3085
+ get waiting() {
3086
+ return this.queue.length;
3087
+ }
3088
+ acquire(timeoutMs) {
3089
+ if (this.permits > 0) {
3090
+ this.permits--;
3091
+ return Promise.resolve();
3092
+ }
3093
+ return new Promise((resolve, reject) => {
3094
+ const waiter = { resolve, reject };
3095
+ this.queue.push(waiter);
3096
+ if (timeoutMs !== void 0 && timeoutMs >= 0) {
3097
+ const timer = setTimeout(() => {
3098
+ const idx = this.queue.indexOf(waiter);
3099
+ if (idx !== -1) {
3100
+ this.queue.splice(idx, 1);
3101
+ reject(new SemaphoreTimeoutError(timeoutMs));
3102
+ }
3103
+ }, timeoutMs);
3104
+ const originalResolve = waiter.resolve;
3105
+ waiter.resolve = () => {
3106
+ clearTimeout(timer);
3107
+ originalResolve();
3108
+ };
3109
+ }
3110
+ });
3111
+ }
3112
+ release() {
3113
+ const next = this.queue.shift();
3114
+ if (next) {
3115
+ next.resolve();
3116
+ } else {
3117
+ this.permits++;
3118
+ }
3119
+ }
3120
+ };
3121
+
3122
+ // src/rsc/latency-monitor.ts
3123
+ var DEFAULT_CONFIG = {
3124
+ warningThresholdMs: 4e3,
3125
+ criticalThresholdMs: 8e3,
3126
+ windowSize: 50
3127
+ };
3128
+ var CircularBuffer = class {
3129
+ buffer;
3130
+ index = 0;
3131
+ count = 0;
3132
+ capacity;
3133
+ constructor(capacity) {
3134
+ this.capacity = capacity;
3135
+ this.buffer = new Array(capacity);
3136
+ }
3137
+ push(value) {
3138
+ this.buffer[this.index] = value;
3139
+ this.index = (this.index + 1) % this.capacity;
3140
+ if (this.count < this.capacity) {
3141
+ this.count++;
3142
+ }
3143
+ }
3144
+ getValues() {
3145
+ if (this.count < this.capacity) {
3146
+ return this.buffer.slice(0, this.count);
3147
+ }
3148
+ return [...this.buffer.slice(this.index), ...this.buffer.slice(0, this.index)];
3149
+ }
3150
+ get size() {
3151
+ return this.count;
3152
+ }
3153
+ };
3154
+ function calculateP95(values) {
3155
+ if (values.length === 0) return null;
3156
+ const sorted = [...values].sort((a, b) => a - b);
3157
+ const idx = Math.floor(sorted.length * 0.95);
3158
+ return sorted[Math.min(idx, sorted.length - 1)];
3159
+ }
3160
+ var LatencyMonitor = class {
3161
+ config;
3162
+ sessionWindows = /* @__PURE__ */ new Map();
3163
+ globalWindow;
3164
+ callbacks = [];
3165
+ constructor(config) {
3166
+ this.config = { ...DEFAULT_CONFIG, ...config };
3167
+ this.globalWindow = new CircularBuffer(this.config.windowSize * 4);
3168
+ }
3169
+ record(sessionKey, latencyMs) {
3170
+ let sessionBuf = this.sessionWindows.get(sessionKey);
3171
+ if (!sessionBuf) {
3172
+ sessionBuf = new CircularBuffer(this.config.windowSize);
3173
+ this.sessionWindows.set(sessionKey, sessionBuf);
3174
+ }
3175
+ sessionBuf.push(latencyMs);
3176
+ this.globalWindow.push(latencyMs);
3177
+ const globalP95 = calculateP95(this.globalWindow.getValues());
3178
+ if (globalP95 === null) return null;
3179
+ let alert = null;
3180
+ if (globalP95 >= this.config.criticalThresholdMs) {
3181
+ alert = {
3182
+ type: "critical",
3183
+ message: `Global p95 latency ${globalP95.toFixed(0)}ms exceeds critical threshold ${this.config.criticalThresholdMs}ms`,
3184
+ sessionKey,
3185
+ p95Ms: globalP95,
3186
+ thresholdMs: this.config.criticalThresholdMs,
3187
+ activeSessions: this.sessionWindows.size,
3188
+ suggestion: "Reduce active sessions or increase latency budget"
3189
+ };
3190
+ } else if (globalP95 >= this.config.warningThresholdMs) {
3191
+ alert = {
3192
+ type: "warning",
3193
+ message: `Global p95 latency ${globalP95.toFixed(0)}ms exceeds warning threshold ${this.config.warningThresholdMs}ms`,
3194
+ sessionKey,
3195
+ p95Ms: globalP95,
3196
+ thresholdMs: this.config.warningThresholdMs,
3197
+ activeSessions: this.sessionWindows.size,
3198
+ suggestion: "Consider reducing active sessions"
3199
+ };
3200
+ }
3201
+ if (alert) {
3202
+ for (const cb of this.callbacks) {
3203
+ cb(alert);
3204
+ }
3205
+ }
3206
+ return alert;
3207
+ }
3208
+ getSessionP95(sessionKey) {
3209
+ const buf = this.sessionWindows.get(sessionKey);
3210
+ if (!buf) return null;
3211
+ return calculateP95(buf.getValues());
3212
+ }
3213
+ getGlobalP95() {
3214
+ return calculateP95(this.globalWindow.getValues());
3215
+ }
3216
+ onAlert(cb) {
3217
+ this.callbacks.push(cb);
3218
+ }
3219
+ get sessionCount() {
3220
+ return this.sessionWindows.size;
3221
+ }
3222
+ };
3223
+
3224
+ // src/tls/connect-handler.ts
3225
+ import * as net from "net";
3226
+
3227
+ // src/tls/allowlist.ts
3228
+ var MITM_HOSTS = /* @__PURE__ */ new Set([
3229
+ "api.openai.com",
3230
+ "api.anthropic.com",
3231
+ "generativelanguage.googleapis.com"
3232
+ ]);
3233
+ function shouldIntercept(hostname) {
3234
+ return MITM_HOSTS.has(hostname);
3235
+ }
3236
+
3237
+ // src/tls/connect-handler.ts
3238
+ function createConnectHandler(options) {
3239
+ const { logger, onIntercept, onPassthrough } = options;
3240
+ return (req, clientSocket, head) => {
3241
+ const target = req.url ?? "";
3242
+ const [hostname, portStr] = parseConnectTarget(target);
3243
+ const port = parseInt(portStr, 10) || 443;
3244
+ if (!hostname) {
3245
+ logger.log(`[CONNECT] Invalid target: ${target}`);
3246
+ clientSocket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
3247
+ clientSocket.destroy();
3248
+ return;
3249
+ }
3250
+ if (shouldIntercept(hostname) && onIntercept) {
3251
+ logger.log(`[CONNECT] ${hostname}:${port} \u2192 intercept`);
3252
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
3253
+ if (head.length > 0) {
3254
+ clientSocket.unshift(head);
3255
+ }
3256
+ onIntercept(clientSocket, hostname, port);
3257
+ } else {
3258
+ logger.log(`[TUNNEL] ${hostname}:${port} \u2192 passthrough`);
3259
+ onPassthrough?.();
3260
+ const upstreamSocket = net.connect(port, hostname, () => {
3261
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
3262
+ if (head.length > 0) {
3263
+ upstreamSocket.write(head);
3264
+ }
3265
+ clientSocket.pipe(upstreamSocket);
3266
+ upstreamSocket.pipe(clientSocket);
3267
+ });
3268
+ upstreamSocket.on("error", (err) => {
3269
+ logger.log(`[TUNNEL] ${hostname}:${port} upstream error: ${err.message}`);
3270
+ clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
3271
+ clientSocket.destroy();
3272
+ });
3273
+ clientSocket.on("error", (err) => {
3274
+ logger.log(`[TUNNEL] ${hostname}:${port} client error: ${err.message}`);
3275
+ upstreamSocket.destroy();
3276
+ });
3277
+ clientSocket.on("close", () => upstreamSocket.destroy());
3278
+ upstreamSocket.on("close", () => clientSocket.destroy());
3279
+ }
3280
+ };
3281
+ }
3282
+ function parseConnectTarget(target) {
3283
+ const colonIdx = target.lastIndexOf(":");
3284
+ if (colonIdx === -1) return [target, "443"];
3285
+ return [target.slice(0, colonIdx), target.slice(colonIdx + 1)];
3286
+ }
3287
+
3288
+ // src/tls/ca.ts
3289
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync } from "fs";
3290
+ import { join as join5 } from "path";
3291
+ import forge from "node-forge";
3292
+ var CA_CERT_PATH = join5(LIMINAL_DIR, "ca.pem");
3293
+ var CA_KEY_PATH = join5(LIMINAL_DIR, "ca-key.pem");
3294
+ function generateCA() {
3295
+ const keys = forge.pki.rsa.generateKeyPair(2048);
3296
+ const cert = forge.pki.createCertificate();
3297
+ cert.publicKey = keys.publicKey;
3298
+ cert.serialNumber = generateSerialNumber();
3299
+ cert.validity.notBefore = /* @__PURE__ */ new Date();
3300
+ cert.validity.notAfter = /* @__PURE__ */ new Date();
3301
+ cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 5);
3302
+ const attrs = [
3303
+ { name: "commonName", value: "Liminal Proxy CA" },
3304
+ { name: "organizationName", value: "Liminal (Cognisos)" },
3305
+ { shortName: "OU", value: "Local Development" }
3306
+ ];
3307
+ cert.setSubject(attrs);
3308
+ cert.setIssuer(attrs);
3309
+ cert.setExtensions([
3310
+ { name: "basicConstraints", cA: true, critical: true },
3311
+ { name: "keyUsage", keyCertSign: true, cRLSign: true, critical: true },
3312
+ {
3313
+ name: "subjectKeyIdentifier"
3314
+ }
3315
+ ]);
3316
+ cert.sign(keys.privateKey, forge.md.sha256.create());
3317
+ return {
3318
+ certPem: forge.pki.certificateToPem(cert),
3319
+ keyPem: forge.pki.privateKeyToPem(keys.privateKey)
3320
+ };
3321
+ }
3322
+ function generateAndSaveCA() {
3323
+ if (!existsSync6(LIMINAL_DIR)) {
3324
+ mkdirSync3(LIMINAL_DIR, { recursive: true, mode: 448 });
3325
+ }
3326
+ const { certPem, keyPem } = generateCA();
3327
+ writeFileSync3(CA_CERT_PATH, certPem, { encoding: "utf-8", mode: 420 });
3328
+ writeFileSync3(CA_KEY_PATH, keyPem, { encoding: "utf-8", mode: 384 });
3329
+ return { certPem, keyPem };
3330
+ }
3331
+ function loadCA() {
3332
+ if (!existsSync6(CA_CERT_PATH) || !existsSync6(CA_KEY_PATH)) {
3333
+ return null;
3334
+ }
3335
+ return {
3336
+ certPem: readFileSync4(CA_CERT_PATH, "utf-8"),
3337
+ keyPem: readFileSync4(CA_KEY_PATH, "utf-8")
3338
+ };
3339
+ }
3340
+ function ensureCA() {
3341
+ const existing = loadCA();
3342
+ if (existing) return existing;
3343
+ return generateAndSaveCA();
3344
+ }
3345
+ function hasCA() {
3346
+ return existsSync6(CA_CERT_PATH) && existsSync6(CA_KEY_PATH);
3347
+ }
3348
+ function removeCA() {
3349
+ if (existsSync6(CA_CERT_PATH)) unlinkSync(CA_CERT_PATH);
3350
+ if (existsSync6(CA_KEY_PATH)) unlinkSync(CA_KEY_PATH);
3351
+ }
3352
+ function getCAInfo() {
3353
+ const ca = loadCA();
3354
+ if (!ca) return null;
3355
+ const cert = forge.pki.certificateFromPem(ca.certPem);
3356
+ const cn = cert.subject.getField("CN");
3357
+ const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
3358
+ const md = forge.md.sha256.create();
3359
+ md.update(der);
3360
+ const fingerprint = md.digest().toHex().match(/.{2}/g).join(":").toUpperCase();
3361
+ return {
3362
+ commonName: cn ? cn.value : "Unknown",
3363
+ validFrom: cert.validity.notBefore,
3364
+ validTo: cert.validity.notAfter,
3365
+ fingerprint
3366
+ };
3367
+ }
3368
+ function generateSerialNumber() {
3369
+ const bytes = forge.random.getBytesSync(16);
3370
+ return forge.util.bytesToHex(bytes);
3371
+ }
3372
+
3373
+ // src/tls/trust.ts
3374
+ import { execSync as execSync3 } from "child_process";
3375
+ import { existsSync as existsSync7, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
3376
+ function installCA() {
3377
+ if (!existsSync7(CA_CERT_PATH)) {
3378
+ return { success: false, message: 'CA certificate not found. Run "liminal init" first.', requiresSudo: false };
3379
+ }
3380
+ const platform = process.platform;
3381
+ if (platform === "darwin") return installMacOS();
3382
+ if (platform === "linux") return installLinux();
3383
+ if (platform === "win32") return installWindows();
3384
+ return { success: false, message: `Unsupported platform: ${platform}`, requiresSudo: false };
3385
+ }
3386
+ function removeCA2() {
3387
+ const platform = process.platform;
3388
+ if (platform === "darwin") return removeMacOS();
3389
+ if (platform === "linux") return removeLinux();
3390
+ if (platform === "win32") return removeWindows();
3391
+ return { success: false, message: `Unsupported platform: ${platform}`, requiresSudo: false };
3392
+ }
3393
+ function isCATrusted() {
3394
+ const platform = process.platform;
3395
+ if (platform === "darwin") return isTrustedMacOS();
3396
+ if (platform === "linux") return isTrustedLinux();
3397
+ if (platform === "win32") return isTrustedWindows();
3398
+ return false;
3399
+ }
3400
+ var MACOS_LABEL = "Liminal Proxy CA";
3401
+ function installMacOS() {
3402
+ try {
3403
+ execSync3(
3404
+ `security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db "${CA_CERT_PATH}"`,
3405
+ { stdio: "pipe" }
3406
+ );
3407
+ return { success: true, message: "CA installed in login keychain (trusted for SSL)", requiresSudo: false };
3408
+ } catch (err) {
3409
+ const msg = err instanceof Error ? err.message : String(err);
3410
+ if (msg.includes("authorization") || msg.includes("permission")) {
3411
+ return {
3412
+ success: false,
3413
+ message: "Keychain access denied. You may need to unlock your keychain or run with sudo.",
3414
+ requiresSudo: true
3415
+ };
3416
+ }
3417
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: false };
3418
+ }
3419
+ }
3420
+ function removeMacOS() {
3421
+ try {
3422
+ execSync3(
3423
+ `security delete-certificate -c "${MACOS_LABEL}" ~/Library/Keychains/login.keychain-db`,
3424
+ { stdio: "pipe" }
3425
+ );
3426
+ return { success: true, message: "CA removed from login keychain", requiresSudo: false };
3427
+ } catch (err) {
3428
+ const msg = err instanceof Error ? err.message : String(err);
3429
+ if (msg.includes("could not be found")) {
3430
+ return { success: true, message: "CA was not in keychain (already removed)", requiresSudo: false };
3431
+ }
3432
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: false };
3433
+ }
3434
+ }
3435
+ function isTrustedMacOS() {
3436
+ try {
3437
+ const out = execSync3(
3438
+ `security find-certificate -c "${MACOS_LABEL}" ~/Library/Keychains/login.keychain-db`,
3439
+ { stdio: "pipe", encoding: "utf-8" }
3440
+ );
3441
+ return out.includes(MACOS_LABEL);
3442
+ } catch {
3443
+ return false;
3444
+ }
3445
+ }
3446
+ var LINUX_CERT_PATH = "/usr/local/share/ca-certificates/liminal-proxy-ca.crt";
3447
+ function installLinux() {
3448
+ try {
3449
+ copyFileSync(CA_CERT_PATH, LINUX_CERT_PATH);
3450
+ execSync3("update-ca-certificates", { stdio: "pipe" });
3451
+ return { success: true, message: "CA installed in system trust store", requiresSudo: true };
3452
+ } catch (err) {
3453
+ const msg = err instanceof Error ? err.message : String(err);
3454
+ if (msg.includes("EACCES") || msg.includes("permission")) {
3455
+ return {
3456
+ success: false,
3457
+ message: `Permission denied. Run with sudo:
3458
+ sudo liminal trust-ca`,
3459
+ requiresSudo: true
3460
+ };
3461
+ }
3462
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: true };
3463
+ }
3464
+ }
3465
+ function removeLinux() {
3466
+ try {
3467
+ if (existsSync7(LINUX_CERT_PATH)) {
3468
+ unlinkSync2(LINUX_CERT_PATH);
3469
+ execSync3("update-ca-certificates --fresh", { stdio: "pipe" });
3470
+ }
3471
+ return { success: true, message: "CA removed from system trust store", requiresSudo: true };
3472
+ } catch (err) {
3473
+ const msg = err instanceof Error ? err.message : String(err);
3474
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: true };
3475
+ }
3476
+ }
3477
+ function isTrustedLinux() {
3478
+ return existsSync7(LINUX_CERT_PATH);
3479
+ }
3480
+ function installWindows() {
3481
+ try {
3482
+ execSync3(`certutil -addstore -user -f "ROOT" "${CA_CERT_PATH}"`, { stdio: "pipe" });
3483
+ return { success: true, message: "CA installed in user certificate store", requiresSudo: false };
3484
+ } catch (err) {
3485
+ const msg = err instanceof Error ? err.message : String(err);
3486
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: false };
3487
+ }
3488
+ }
3489
+ function removeWindows() {
3490
+ try {
3491
+ execSync3(`certutil -delstore -user "ROOT" "${MACOS_LABEL}"`, { stdio: "pipe" });
3492
+ return { success: true, message: "CA removed from user certificate store", requiresSudo: false };
3493
+ } catch (err) {
3494
+ const msg = err instanceof Error ? err.message : String(err);
3495
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: false };
3496
+ }
3497
+ }
3498
+ function isTrustedWindows() {
3499
+ try {
3500
+ const out = execSync3(`certutil -verifystore -user "ROOT" "${MACOS_LABEL}"`, {
3501
+ stdio: "pipe",
3502
+ encoding: "utf-8"
3503
+ });
3504
+ return out.includes("Liminal");
3505
+ } catch {
3506
+ return false;
3507
+ }
3508
+ }
3509
+
3510
+ // src/tls/mitm-bridge.ts
3511
+ import * as tls from "tls";
3512
+
3513
+ // src/tls/cert-generator.ts
3514
+ import forge2 from "node-forge";
3515
+ var CERT_TTL_MS = 24 * 60 * 60 * 1e3;
3516
+ var MAX_CACHE_SIZE = 50;
3517
+ var CertGenerator = class {
3518
+ caCert;
3519
+ caKey;
3520
+ cache = /* @__PURE__ */ new Map();
3521
+ constructor(caCertPem, caKeyPem) {
3522
+ this.caCert = forge2.pki.certificateFromPem(caCertPem);
3523
+ this.caKey = forge2.pki.privateKeyFromPem(caKeyPem);
3524
+ }
3525
+ /**
3526
+ * Get or generate a TLS certificate for the given hostname.
3527
+ * Certificates are cached for 24 hours.
3528
+ */
3529
+ getCert(hostname) {
3530
+ const now = Date.now();
3531
+ const cached = this.cache.get(hostname);
3532
+ if (cached && cached.expiresAt > now) {
3533
+ return cached.cert;
3534
+ }
3535
+ const cert = this.generate(hostname);
3536
+ if (this.cache.size >= MAX_CACHE_SIZE) {
3537
+ const oldest = this.cache.keys().next().value;
3538
+ if (oldest) this.cache.delete(oldest);
3539
+ }
3540
+ this.cache.set(hostname, { cert, expiresAt: now + CERT_TTL_MS });
3541
+ return cert;
3542
+ }
3543
+ get cacheSize() {
3544
+ return this.cache.size;
3545
+ }
3546
+ generate(hostname) {
3547
+ const keys = forge2.pki.rsa.generateKeyPair(2048);
3548
+ const cert = forge2.pki.createCertificate();
3549
+ cert.publicKey = keys.publicKey;
3550
+ cert.serialNumber = randomSerial();
3551
+ cert.validity.notBefore = new Date(Date.now() - 24 * 60 * 60 * 1e3);
3552
+ cert.validity.notAfter = new Date(Date.now() + 24 * 60 * 60 * 1e3);
3553
+ cert.setSubject([
3554
+ { name: "commonName", value: hostname },
3555
+ { name: "organizationName", value: "Liminal Proxy (local)" }
3556
+ ]);
3557
+ cert.setIssuer(this.caCert.subject.attributes);
3558
+ cert.setExtensions([
3559
+ { name: "basicConstraints", cA: false },
3560
+ {
3561
+ name: "keyUsage",
3562
+ digitalSignature: true,
3563
+ keyEncipherment: true,
3564
+ critical: true
3565
+ },
3566
+ {
3567
+ name: "extKeyUsage",
3568
+ serverAuth: true
3569
+ },
3570
+ {
3571
+ name: "subjectAltName",
3572
+ altNames: [{ type: 2, value: hostname }]
3573
+ // DNS name
3574
+ }
3575
+ ]);
3576
+ cert.sign(this.caKey, forge2.md.sha256.create());
3577
+ return {
3578
+ certPem: forge2.pki.certificateToPem(cert),
3579
+ keyPem: forge2.pki.privateKeyToPem(keys.privateKey)
3580
+ };
3581
+ }
3582
+ };
3583
+ function randomSerial() {
3584
+ return forge2.util.bytesToHex(forge2.random.getBytesSync(16));
3585
+ }
3586
+
3587
+ // src/tls/mitm-bridge.ts
3588
+ function createMitmBridge(options) {
3589
+ const { httpServer, caCertPem, caKeyPem, logger } = options;
3590
+ const certGen = new CertGenerator(caCertPem, caKeyPem);
3591
+ return (clientSocket, hostname, _port) => {
3592
+ try {
3593
+ const { certPem, keyPem } = certGen.getCert(hostname);
3594
+ const tlsSocket = new tls.TLSSocket(clientSocket, {
3595
+ isServer: true,
3596
+ key: keyPem,
3597
+ cert: certPem
3598
+ });
3599
+ tlsSocket.on("error", (err) => {
3600
+ logger.log(`[MITM] TLS error for ${hostname}: ${err.message}`);
3601
+ tlsSocket.destroy();
3602
+ });
3603
+ httpServer.emit("connection", tlsSocket);
3604
+ logger.log(`[MITM] TLS bridge established for ${hostname} (cert cache: ${certGen.cacheSize})`);
3605
+ } catch (err) {
3606
+ const message = err instanceof Error ? err.message : String(err);
3607
+ logger.log(`[MITM] Failed to establish bridge for ${hostname}: ${message}`);
3608
+ clientSocket.destroy();
3609
+ }
3610
+ };
3611
+ }
3612
+
3613
+ // src/tls/mitm-stats.ts
3614
+ var MitmStats = class {
3615
+ _intercepted = 0;
3616
+ _passthrough = 0;
3617
+ _activeBridges = 0;
3618
+ _hosts = /* @__PURE__ */ new Set();
3619
+ _enabled = false;
3620
+ enable() {
3621
+ this._enabled = true;
3622
+ }
3623
+ recordIntercept(hostname) {
3624
+ this._intercepted++;
3625
+ this._activeBridges++;
3626
+ this._hosts.add(hostname);
3627
+ }
3628
+ recordBridgeClosed() {
3629
+ if (this._activeBridges > 0) this._activeBridges--;
3630
+ }
3631
+ recordPassthrough() {
3632
+ this._passthrough++;
3633
+ }
3634
+ snapshot() {
3635
+ return {
3636
+ intercepted: this._intercepted,
3637
+ passthrough: this._passthrough,
3638
+ activeBridges: this._activeBridges,
3639
+ hostsIntercepted: [...this._hosts],
3640
+ enabled: this._enabled
3641
+ };
3642
+ }
3643
+ };
3644
+
3645
+ // src/daemon/lifecycle.ts
3646
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
3647
+ import { fork } from "child_process";
3648
+ import { fileURLToPath } from "url";
3649
+ function writePidFile(pid) {
3650
+ writeFileSync4(PID_FILE, String(pid), "utf-8");
3651
+ }
3652
+ function readPidFile() {
3653
+ if (!existsSync8(PID_FILE)) return null;
3654
+ try {
3655
+ const content = readFileSync5(PID_FILE, "utf-8").trim();
3656
+ const pid = parseInt(content, 10);
3657
+ return isNaN(pid) ? null : pid;
3658
+ } catch {
3659
+ return null;
3660
+ }
3661
+ }
2550
3662
  function removePidFile() {
2551
3663
  try {
2552
- if (existsSync6(PID_FILE)) unlinkSync(PID_FILE);
3664
+ if (existsSync8(PID_FILE)) unlinkSync3(PID_FILE);
2553
3665
  } catch {
2554
3666
  }
2555
3667
  }
@@ -2651,37 +3763,87 @@ async function startCommand(flags) {
2651
3763
  rscBaseUrl: config.apiBaseUrl,
2652
3764
  proxyPort: config.port,
2653
3765
  compressionThreshold: config.compressionThreshold,
3766
+ aggregateThreshold: config.aggregateThreshold,
3767
+ hotFraction: config.hotFraction,
3768
+ coldFraction: config.coldFraction,
2654
3769
  compressRoles: config.compressRoles,
3770
+ compressToolResults: config.compressToolResults,
2655
3771
  learnFromResponses: config.learnFromResponses,
2656
3772
  latencyBudgetMs: config.latencyBudgetMs || void 0,
2657
3773
  upstreamBaseUrl: config.upstreamBaseUrl,
2658
3774
  anthropicUpstreamUrl: config.anthropicUpstreamUrl,
2659
3775
  enabled: config.enabled,
2660
- tools: config.tools
3776
+ tools: config.tools,
3777
+ concurrencyLimit: config.concurrencyLimit,
3778
+ concurrencyTimeoutMs: config.concurrencyTimeoutMs,
3779
+ maxSessions: config.maxSessions,
3780
+ sessionTtlMs: config.sessionTtlMs,
3781
+ latencyWarningMs: config.latencyWarningMs,
3782
+ latencyCriticalMs: config.latencyCriticalMs
2661
3783
  };
2662
- const pipeline = new RSCPipelineWrapper({
2663
- rscApiKey: config.apiKey,
2664
- rscBaseUrl: config.apiBaseUrl,
2665
- compressionThreshold: config.compressionThreshold,
2666
- learnFromResponses: config.learnFromResponses,
2667
- latencyBudgetMs: config.latencyBudgetMs || void 0
3784
+ const semaphore = new Semaphore(resolvedConfig.concurrencyLimit);
3785
+ const latencyMonitor = new LatencyMonitor({
3786
+ warningThresholdMs: resolvedConfig.latencyWarningMs,
3787
+ criticalThresholdMs: resolvedConfig.latencyCriticalMs
2668
3788
  });
2669
- pipeline.events.on("compression", (event) => {
2670
- if (event.tokensSaved > 0) {
2671
- logger.log(`[LIMINAL] Compressed: ${event.tokensSaved} tokens saved (${event.ratio.toFixed(3)} ratio)`);
3789
+ function wireSessionEvents(key, pipeline) {
3790
+ pipeline.events.on("compression", (event) => {
3791
+ if (event.tokensSaved > 0) {
3792
+ logger.log(`[LIMINAL] [${key}] Compressed: ${event.tokensSaved} tokens saved (${event.ratio.toFixed(3)} ratio)`);
3793
+ }
3794
+ });
3795
+ pipeline.events.on("compression_skipped", (event) => {
3796
+ 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)`;
3797
+ logger.log(`[LIMINAL] [${key}] Skipped: ${detail}`);
3798
+ });
3799
+ pipeline.events.on("error", (event) => {
3800
+ logger.log(`[LIMINAL] [${key}] Error: ${event.error.message}`);
3801
+ });
3802
+ pipeline.events.on("degradation", (event) => {
3803
+ logger.log(`[LIMINAL] [${key}] Circuit ${event.circuitState}: ${event.reason}`);
3804
+ });
3805
+ }
3806
+ const sessions = new SessionManager({
3807
+ pipelineConfig: {
3808
+ rscApiKey: config.apiKey,
3809
+ rscBaseUrl: config.apiBaseUrl,
3810
+ compressionThreshold: config.compressionThreshold,
3811
+ learnFromResponses: config.learnFromResponses,
3812
+ latencyBudgetMs: config.latencyBudgetMs || void 0
3813
+ },
3814
+ maxSessions: resolvedConfig.maxSessions,
3815
+ sessionTtlMs: resolvedConfig.sessionTtlMs,
3816
+ onSessionCreated: (key, pipeline) => {
3817
+ logger.log(`[SESSION] Created: ${key}`);
3818
+ wireSessionEvents(key, pipeline);
3819
+ },
3820
+ onSessionEvicted: (key) => {
3821
+ logger.log(`[SESSION] Evicted: ${key} (idle)`);
2672
3822
  }
2673
3823
  });
2674
- pipeline.events.on("compression_skipped", (event) => {
2675
- logger.log(`[LIMINAL] Skipped: ${event.reason}`);
2676
- });
2677
- pipeline.events.on("error", (event) => {
2678
- logger.log(`[LIMINAL] Error: ${event.error.message}`);
3824
+ latencyMonitor.onAlert((alert) => {
3825
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message} (${alert.activeSessions} sessions) \u2014 ${alert.suggestion}`);
2679
3826
  });
2680
- pipeline.events.on("degradation", (event) => {
2681
- logger.log(`[LIMINAL] Circuit ${event.circuitState}: ${event.reason}`);
3827
+ const mitmStats = new MitmStats();
3828
+ const deps = { sessions, semaphore, latencyMonitor, mitmStats, config: resolvedConfig, logger };
3829
+ const handler = createRequestHandler(deps);
3830
+ let mitmHandler;
3831
+ const connectHandler = createConnectHandler({
3832
+ logger,
3833
+ onIntercept: (socket, hostname, port) => {
3834
+ if (mitmHandler) {
3835
+ mitmStats.recordIntercept(hostname);
3836
+ mitmHandler(socket, hostname, port);
3837
+ } else {
3838
+ logger.log(`[MITM] No bridge available for ${hostname} \u2014 falling back to passthrough`);
3839
+ mitmStats.recordPassthrough();
3840
+ }
3841
+ },
3842
+ onPassthrough: () => {
3843
+ mitmStats.recordPassthrough();
3844
+ }
2682
3845
  });
2683
- const handler = createRequestHandler(pipeline, resolvedConfig, logger);
2684
- const server = new ProxyServer(config.port, handler);
3846
+ const server = new ProxyServer(config.port, handler, connectHandler);
2685
3847
  setupSignalHandlers(server, logger);
2686
3848
  try {
2687
3849
  const actualPort = await server.start();
@@ -2691,15 +3853,43 @@ async function startCommand(flags) {
2691
3853
  logger.log(`[DAEMON] Upstream (Anthropic): ${config.anthropicUpstreamUrl}`);
2692
3854
  logger.log(`[DAEMON] Liminal API: ${config.apiBaseUrl}`);
2693
3855
  logger.log(`[DAEMON] PID: ${process.pid}`);
3856
+ logger.log(`[DAEMON] Max sessions: ${resolvedConfig.maxSessions}, Concurrency limit: ${resolvedConfig.concurrencyLimit}`);
3857
+ const caReady = hasCA() && isCATrusted();
3858
+ if (caReady) {
3859
+ const httpServer = server.getHttpServer();
3860
+ const ca = loadCA();
3861
+ if (httpServer && ca) {
3862
+ mitmHandler = createMitmBridge({
3863
+ httpServer,
3864
+ caCertPem: ca.certPem,
3865
+ caKeyPem: ca.keyPem,
3866
+ logger
3867
+ });
3868
+ mitmStats.enable();
3869
+ logger.log("[MITM] TLS bridge active \u2014 intercepting LLM API calls");
3870
+ }
3871
+ }
3872
+ logger.log(`[DAEMON] CONNECT handler: active | MITM: ${caReady ? "ready (CA trusted)" : "passthrough only (run liminal trust-ca)"}`);
2694
3873
  if (isForeground && !isForked) {
2695
3874
  printBanner();
2696
3875
  console.log(` Liminal proxy running on http://127.0.0.1:${actualPort}/v1`);
2697
3876
  console.log(` Upstream: ${config.upstreamBaseUrl}`);
3877
+ console.log(` Max sessions: ${resolvedConfig.maxSessions} | Concurrency: ${resolvedConfig.concurrencyLimit}`);
3878
+ if (caReady) {
3879
+ console.log(" MITM: active (Cursor interception ready)");
3880
+ }
2698
3881
  console.log();
2699
3882
  console.log(" Point your AI tool's base URL here. Press Ctrl+C to stop.");
2700
3883
  console.log();
2701
3884
  }
2702
- const healthy = await pipeline.healthCheck();
3885
+ const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
3886
+ const probe = new RSCPipelineWrapper2({
3887
+ rscApiKey: config.apiKey,
3888
+ rscBaseUrl: config.apiBaseUrl,
3889
+ compressionThreshold: config.compressionThreshold,
3890
+ learnFromResponses: false
3891
+ });
3892
+ const healthy = await probe.healthCheck();
2703
3893
  if (healthy) {
2704
3894
  logger.log("[DAEMON] Liminal API health check: OK");
2705
3895
  } else {
@@ -2766,19 +3956,50 @@ async function statusCommand() {
2766
3956
  const data = await res.json();
2767
3957
  const uptime = formatUptime(data.uptime_ms);
2768
3958
  console.log(`Liminal Daemon: running (PID ${state.pid}, port ${port})`);
2769
- console.log(`Circuit: ${data.circuit_state}`);
2770
- console.log(`Session: ${data.session_id}`);
3959
+ console.log(`Status: ${data.status} (${data.version})`);
2771
3960
  console.log(`Uptime: ${uptime}`);
2772
- if (data.session) {
2773
- const s = data.session;
2774
- const savingsPercent = s.tokens_processed > 0 ? (s.tokens_saved / s.tokens_processed * 100).toFixed(1) : "0.0";
3961
+ console.log();
3962
+ const c = data.concurrency;
3963
+ console.log(`Sessions: ${c.active_sessions} active (max ${config.maxSessions})`);
3964
+ console.log(`Semaphore: ${c.semaphore_available}/${c.max_concurrent_rsc_calls} available` + (c.semaphore_waiting > 0 ? ` (${c.semaphore_waiting} waiting)` : ""));
3965
+ const globalP95 = data.latency.global_p95_ms;
3966
+ if (globalP95 !== null) {
3967
+ const latencyFlag = globalP95 >= config.latencyCriticalMs ? " CRITICAL" : globalP95 >= config.latencyWarningMs ? " WARNING" : "";
3968
+ console.log(`Latency: p95 ${globalP95.toFixed(0)}ms${latencyFlag}`);
3969
+ }
3970
+ if (data.mitm) {
3971
+ const m = data.mitm;
3972
+ if (m.enabled) {
3973
+ console.log(`MITM: active (${m.intercepted} intercepted, ${m.passthrough} passthrough, ${m.activeBridges} active)`);
3974
+ if (m.hostsIntercepted.length > 0) {
3975
+ console.log(` Hosts: ${m.hostsIntercepted.join(", ")}`);
3976
+ }
3977
+ } else {
3978
+ console.log('MITM: disabled (run "liminal trust-ca" to enable)');
3979
+ }
3980
+ }
3981
+ if (data.sessions.length > 0) {
3982
+ console.log();
3983
+ console.log("\u2500\u2500\u2500 Sessions \u2500\u2500\u2500");
3984
+ for (const s of data.sessions) {
3985
+ const savingsPercent = s.tokens_processed > 0 ? (s.tokens_saved / s.tokens_processed * 100).toFixed(1) : "0.0";
3986
+ const activeAgo = formatUptime(s.last_active_ago_ms);
3987
+ const p95 = s.p95_latency_ms !== null ? `${s.p95_latency_ms.toFixed(0)}ms` : "-";
3988
+ console.log();
3989
+ console.log(` ${s.connector} [${s.session_key}]`);
3990
+ console.log(` Circuit: ${s.circuit_state}`);
3991
+ console.log(` Tokens: ${s.tokens_processed.toLocaleString()} processed, ${s.tokens_saved.toLocaleString()} saved (${savingsPercent}%)`);
3992
+ console.log(` Calls: ${s.calls_total} total (${s.calls_compressed} compressed, ${s.calls_failed} failed)`);
3993
+ console.log(` Latency: p95 ${p95}`);
3994
+ console.log(` Active: ${activeAgo} ago`);
3995
+ }
3996
+ } else {
2775
3997
  console.log();
2776
- console.log(`Tokens: ${s.tokens_processed.toLocaleString()} processed, ${s.tokens_saved.toLocaleString()} saved (${savingsPercent}%)`);
2777
- console.log(`Calls: ${s.calls_total} total (${s.calls_compressed} compressed, ${s.calls_skipped} skipped, ${s.calls_failed} failed)`);
3998
+ console.log("No active sessions.");
2778
3999
  }
2779
4000
  } catch {
2780
4001
  console.log(`Liminal Daemon: running (PID ${state.pid}, port ${port})`);
2781
- console.log("Circuit: unknown (could not reach /health)");
4002
+ console.log("Status: unknown (could not reach /health)");
2782
4003
  }
2783
4004
  }
2784
4005
  function formatUptime(ms) {
@@ -2930,17 +4151,17 @@ async function configCommand(flags) {
2930
4151
  }
2931
4152
 
2932
4153
  // src/commands/logs.ts
2933
- import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync2, createReadStream } from "fs";
4154
+ import { readFileSync as readFileSync6, existsSync as existsSync9, statSync as statSync2, createReadStream } from "fs";
2934
4155
  import { watchFile, unwatchFile } from "fs";
2935
4156
  async function logsCommand(flags) {
2936
4157
  const follow = flags.has("follow") || flags.has("f");
2937
4158
  const linesFlag = flags.get("lines") ?? flags.get("n");
2938
4159
  const lines = typeof linesFlag === "string" ? parseInt(linesFlag, 10) : 50;
2939
- if (!existsSync7(LOG_FILE)) {
4160
+ if (!existsSync9(LOG_FILE)) {
2940
4161
  console.log('No log file found. Start the daemon with "liminal start" to generate logs.');
2941
4162
  return;
2942
4163
  }
2943
- const content = readFileSync5(LOG_FILE, "utf-8");
4164
+ const content = readFileSync6(LOG_FILE, "utf-8");
2944
4165
  const allLines = content.split("\n");
2945
4166
  const tail = allLines.slice(-lines - 1);
2946
4167
  process.stdout.write(tail.join("\n"));
@@ -2966,16 +4187,16 @@ async function logsCommand(flags) {
2966
4187
  }
2967
4188
 
2968
4189
  // src/commands/uninstall.ts
2969
- import { existsSync as existsSync8, rmSync, readFileSync as readFileSync6 } from "fs";
4190
+ import { existsSync as existsSync10, rmSync, readFileSync as readFileSync7 } from "fs";
2970
4191
  var BOLD2 = "\x1B[1m";
2971
4192
  var DIM2 = "\x1B[2m";
2972
4193
  var GREEN2 = "\x1B[32m";
2973
4194
  var YELLOW2 = "\x1B[33m";
2974
4195
  var RESET2 = "\x1B[0m";
2975
4196
  function loadConfiguredTools() {
2976
- if (!existsSync8(CONFIG_FILE)) return [];
4197
+ if (!existsSync10(CONFIG_FILE)) return [];
2977
4198
  try {
2978
- const raw = readFileSync6(CONFIG_FILE, "utf-8");
4199
+ const raw = readFileSync7(CONFIG_FILE, "utf-8");
2979
4200
  const config = JSON.parse(raw);
2980
4201
  if (Array.isArray(config.tools)) return config.tools;
2981
4202
  } catch {
@@ -3063,7 +4284,7 @@ async function uninstallCommand() {
3063
4284
  }
3064
4285
  }
3065
4286
  }
3066
- if (existsSync8(LIMINAL_DIR)) {
4287
+ if (existsSync10(LIMINAL_DIR)) {
3067
4288
  console.log();
3068
4289
  const removeData = await selectPrompt({
3069
4290
  message: "Remove ~/.liminal/ directory? (config, logs, PID file)",
@@ -3094,6 +4315,217 @@ async function uninstallCommand() {
3094
4315
  console.log();
3095
4316
  }
3096
4317
 
4318
+ // src/commands/trust-ca.ts
4319
+ async function trustCACommand() {
4320
+ printBanner();
4321
+ if (isCATrusted()) {
4322
+ console.log(" Liminal CA is already trusted.");
4323
+ const info = getCAInfo();
4324
+ if (info) {
4325
+ console.log(` Fingerprint: ${info.fingerprint}`);
4326
+ console.log(` Valid until: ${info.validTo.toLocaleDateString()}`);
4327
+ }
4328
+ return;
4329
+ }
4330
+ if (!hasCA()) {
4331
+ console.log(" Generating CA certificate...");
4332
+ ensureCA();
4333
+ console.log(" Created ~/.liminal/ca.pem");
4334
+ console.log();
4335
+ }
4336
+ console.log(" Installing Liminal CA certificate");
4337
+ console.log();
4338
+ console.log(" This allows Liminal to transparently compress LLM API");
4339
+ console.log(" traffic from Cursor and other Electron-based editors.");
4340
+ console.log();
4341
+ console.log(" The certificate is scoped to your user account and only");
4342
+ console.log(" used by the local Liminal proxy on 127.0.0.1.");
4343
+ console.log();
4344
+ console.log(` Certificate: ${CA_CERT_PATH}`);
4345
+ console.log();
4346
+ const result = installCA();
4347
+ if (result.success) {
4348
+ console.log(` ${result.message}`);
4349
+ console.log();
4350
+ const info = getCAInfo();
4351
+ if (info) {
4352
+ console.log(` Fingerprint: ${info.fingerprint}`);
4353
+ console.log(` Valid until: ${info.validTo.toLocaleDateString()}`);
4354
+ }
4355
+ console.log();
4356
+ console.log(" You can now use Liminal with Cursor:");
4357
+ console.log(" liminal start");
4358
+ console.log(" cursor --proxy-server=http://127.0.0.1:3141 --disable-http2");
4359
+ } else {
4360
+ console.error(` Failed: ${result.message}`);
4361
+ if (result.requiresSudo) {
4362
+ console.log();
4363
+ console.log(" Try running with elevated permissions:");
4364
+ console.log(" sudo liminal trust-ca");
4365
+ }
4366
+ process.exit(1);
4367
+ }
4368
+ }
4369
+
4370
+ // src/commands/untrust-ca.ts
4371
+ async function untrustCACommand() {
4372
+ printBanner();
4373
+ if (!hasCA() && !isCATrusted()) {
4374
+ console.log(" No Liminal CA found (nothing to remove).");
4375
+ return;
4376
+ }
4377
+ if (isCATrusted()) {
4378
+ console.log(" Removing CA from system trust store...");
4379
+ const result = removeCA2();
4380
+ if (result.success) {
4381
+ console.log(` ${result.message}`);
4382
+ } else {
4383
+ console.error(` ${result.message}`);
4384
+ if (result.requiresSudo) {
4385
+ console.log();
4386
+ console.log(" Try running with elevated permissions:");
4387
+ console.log(" sudo liminal untrust-ca");
4388
+ }
4389
+ process.exit(1);
4390
+ }
4391
+ }
4392
+ if (hasCA()) {
4393
+ console.log(" Removing CA certificate files...");
4394
+ removeCA();
4395
+ console.log(" Removed ~/.liminal/ca.pem and ca-key.pem");
4396
+ }
4397
+ console.log();
4398
+ console.log(" Liminal CA fully removed.");
4399
+ console.log(" Cursor MITM interception is no longer available.");
4400
+ }
4401
+
4402
+ // src/commands/setup-cursor.ts
4403
+ import { existsSync as existsSync11 } from "fs";
4404
+ import { homedir as homedir5 } from "os";
4405
+ import { join as join6 } from "path";
4406
+ import { execSync as execSync4, spawn } from "child_process";
4407
+ function findCursorBinary() {
4408
+ const platform = process.platform;
4409
+ if (platform === "darwin") {
4410
+ const cliPath = "/usr/local/bin/cursor";
4411
+ if (existsSync11(cliPath)) return cliPath;
4412
+ const appPath = "/Applications/Cursor.app";
4413
+ if (existsSync11(appPath)) {
4414
+ return "open";
4415
+ }
4416
+ return null;
4417
+ }
4418
+ if (platform === "win32") {
4419
+ const localAppData = process.env.LOCALAPPDATA || join6(homedir5(), "AppData", "Local");
4420
+ const exePath = join6(localAppData, "Programs", "Cursor", "Cursor.exe");
4421
+ if (existsSync11(exePath)) return exePath;
4422
+ return null;
4423
+ }
4424
+ const linuxPath = "/usr/bin/cursor";
4425
+ if (existsSync11(linuxPath)) return linuxPath;
4426
+ try {
4427
+ return execSync4("which cursor", { encoding: "utf-8" }).trim();
4428
+ } catch {
4429
+ return null;
4430
+ }
4431
+ }
4432
+ async function setupCursorCommand(flags) {
4433
+ printBanner();
4434
+ const launch = !flags.has("no-launch");
4435
+ console.log(" [1/4] Checking Cursor installation...");
4436
+ const cursorBin = findCursorBinary();
4437
+ if (!cursorBin) {
4438
+ console.error(" Cursor not found. Install Cursor from https://cursor.com");
4439
+ process.exit(1);
4440
+ }
4441
+ console.log(` Found: ${cursorBin}`);
4442
+ console.log();
4443
+ console.log(" [2/4] Checking CA certificate...");
4444
+ if (!hasCA()) {
4445
+ console.log(" Generating CA certificate...");
4446
+ ensureCA();
4447
+ console.log(` Created ${CA_CERT_PATH}`);
4448
+ }
4449
+ if (!isCATrusted()) {
4450
+ console.log(" Installing CA into system trust store...");
4451
+ console.log();
4452
+ console.log(" This allows Liminal to transparently compress LLM API");
4453
+ console.log(" traffic from Cursor. The certificate is scoped to your");
4454
+ console.log(" user account and only used locally.");
4455
+ console.log();
4456
+ const result = installCA();
4457
+ if (!result.success) {
4458
+ console.error(` CA install failed: ${result.message}`);
4459
+ if (result.requiresSudo) {
4460
+ console.log(" Try: sudo liminal setup cursor");
4461
+ }
4462
+ process.exit(1);
4463
+ }
4464
+ console.log(` ${result.message}`);
4465
+ } else {
4466
+ console.log(" CA already trusted");
4467
+ }
4468
+ const info = getCAInfo();
4469
+ if (info) {
4470
+ console.log(` Fingerprint: ${info.fingerprint}`);
4471
+ }
4472
+ console.log();
4473
+ console.log(" [3/4] Checking Liminal proxy...");
4474
+ if (!isConfigured()) {
4475
+ console.error(' Liminal is not configured. Run "liminal init" first.');
4476
+ process.exit(1);
4477
+ }
4478
+ const state = isDaemonRunning();
4479
+ const config = loadConfig();
4480
+ const port = config.port;
4481
+ if (!state.running) {
4482
+ console.log(" Proxy is not running.");
4483
+ console.log();
4484
+ console.log(" Start the proxy first, then re-run this command:");
4485
+ console.log(" liminal start -d");
4486
+ console.log(" liminal setup cursor");
4487
+ console.log();
4488
+ console.log(" Or start in foreground + launch Cursor manually:");
4489
+ console.log(` liminal start & cursor --proxy-server=http://127.0.0.1:${port}`);
4490
+ process.exit(1);
4491
+ }
4492
+ console.log(` Proxy running on port ${port} (PID ${state.pid})`);
4493
+ console.log();
4494
+ const proxyUrl = `http://127.0.0.1:${port}`;
4495
+ if (launch) {
4496
+ console.log(" [4/4] Launching Cursor with proxy...");
4497
+ console.log(` --proxy-server=${proxyUrl}`);
4498
+ console.log();
4499
+ launchCursor(cursorBin, proxyUrl);
4500
+ console.log(" Cursor launched with Liminal compression active.");
4501
+ console.log();
4502
+ console.log(" All LLM API calls will be transparently compressed.");
4503
+ console.log(' Use "liminal status" to monitor compression stats.');
4504
+ console.log(' Use "liminal logs -f" to watch live traffic.');
4505
+ } else {
4506
+ console.log(" [4/4] Ready! Launch Cursor with:");
4507
+ console.log();
4508
+ console.log(` cursor --proxy-server=${proxyUrl}`);
4509
+ console.log();
4510
+ console.log(" Or create a shell alias:");
4511
+ console.log(` alias cursor-liminal='cursor --proxy-server=${proxyUrl}'`);
4512
+ }
4513
+ }
4514
+ function launchCursor(bin, proxyUrl) {
4515
+ const args = [`--proxy-server=${proxyUrl}`];
4516
+ if (bin === "open" && process.platform === "darwin") {
4517
+ spawn("open", ["-a", "Cursor", "--args", ...args], {
4518
+ detached: true,
4519
+ stdio: "ignore"
4520
+ }).unref();
4521
+ } else {
4522
+ spawn(bin, args, {
4523
+ detached: true,
4524
+ stdio: "ignore"
4525
+ }).unref();
4526
+ }
4527
+ }
4528
+
3097
4529
  // src/bin.ts
3098
4530
  var USAGE = `
3099
4531
  liminal v${VERSION} \u2014 Transparent LLM context compression proxy
@@ -3108,6 +4540,9 @@ var USAGE = `
3108
4540
  liminal summary Detailed session metrics
3109
4541
  liminal config [--set k=v] [--get k] View or edit configuration
3110
4542
  liminal logs [--follow] [--lines N] View proxy logs
4543
+ liminal setup cursor [--no-launch] Set up and launch Cursor with Liminal
4544
+ liminal trust-ca Install CA cert (for Cursor MITM)
4545
+ liminal untrust-ca Remove CA cert
3111
4546
  liminal uninstall Remove Liminal configuration
3112
4547
 
3113
4548
  Options:
@@ -3120,7 +4555,7 @@ var USAGE = `
3120
4555
  3. Connect your AI tools:
3121
4556
  Claude Code: export ANTHROPIC_BASE_URL=http://localhost:3141
3122
4557
  Codex: export OPENAI_BASE_URL=http://localhost:3141/v1
3123
- Cursor: Settings > Models > Base URL > http://localhost:3141/v1
4558
+ Cursor: liminal trust-ca && cursor --proxy-server=http://localhost:3141
3124
4559
  `;
3125
4560
  function parseArgs(argv) {
3126
4561
  const command = argv[2] ?? "";
@@ -3186,6 +4621,23 @@ async function main() {
3186
4621
  case "logs":
3187
4622
  await logsCommand(flags);
3188
4623
  break;
4624
+ case "setup": {
4625
+ const subcommand = process.argv[3];
4626
+ if (subcommand === "cursor") {
4627
+ await setupCursorCommand(flags);
4628
+ } else {
4629
+ console.error(`Unknown setup target: ${subcommand ?? "(none)"}`);
4630
+ console.error("Available: liminal setup cursor");
4631
+ process.exit(1);
4632
+ }
4633
+ break;
4634
+ }
4635
+ case "trust-ca":
4636
+ await trustCACommand();
4637
+ break;
4638
+ case "untrust-ca":
4639
+ await untrustCACommand();
4640
+ break;
3189
4641
  case "uninstall":
3190
4642
  await uninstallCommand();
3191
4643
  break;