@nxuss/lemma 0.5.2 → 0.5.4

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dashboard/dist/assets/index-0N21ZtpO.js +481 -0
  3. package/dashboard/dist/assets/index-0N21ZtpO.js.map +1 -0
  4. package/dashboard/dist/assets/index-DUOrThix.css +1 -0
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  7. package/dist/cjs/cli/lemma-proxy.js +365 -113
  8. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  9. package/dist/cjs/config/index.d.ts.map +1 -1
  10. package/dist/cjs/config/index.js +2 -1
  11. package/dist/cjs/config/index.js.map +1 -1
  12. package/dist/cjs/errors/LemmaError.d.ts +52 -0
  13. package/dist/cjs/errors/LemmaError.d.ts.map +1 -0
  14. package/dist/cjs/errors/LemmaError.js +84 -0
  15. package/dist/cjs/errors/LemmaError.js.map +1 -0
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js +24 -22
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/subconscious/SemanticCache.d.ts +4 -4
  20. package/dist/cjs/subconscious/SemanticCache.d.ts.map +1 -1
  21. package/dist/cjs/subconscious/SemanticCache.js +14 -10
  22. package/dist/cjs/subconscious/SemanticCache.js.map +1 -1
  23. package/dist/cjs/types/index.d.ts +8 -8
  24. package/dist/cjs/types/index.d.ts.map +1 -1
  25. package/dist/cjs/utils/SavingsLedger.d.ts +2 -0
  26. package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -1
  27. package/dist/cjs/utils/SavingsLedger.js +49 -0
  28. package/dist/cjs/utils/SavingsLedger.js.map +1 -1
  29. package/dist/cjs/utils/logger.d.ts +27 -6
  30. package/dist/cjs/utils/logger.d.ts.map +1 -1
  31. package/dist/cjs/utils/logger.js +96 -28
  32. package/dist/cjs/utils/logger.js.map +1 -1
  33. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  34. package/dist/esm/cli/lemma-proxy.js +365 -113
  35. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  36. package/dist/esm/config/index.d.ts.map +1 -1
  37. package/dist/esm/config/index.js +2 -1
  38. package/dist/esm/config/index.js.map +1 -1
  39. package/dist/esm/errors/LemmaError.d.ts +52 -0
  40. package/dist/esm/errors/LemmaError.d.ts.map +1 -0
  41. package/dist/esm/errors/LemmaError.js +75 -0
  42. package/dist/esm/errors/LemmaError.js.map +1 -0
  43. package/dist/esm/index.d.ts.map +1 -1
  44. package/dist/esm/index.js +24 -22
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/subconscious/SemanticCache.d.ts +4 -4
  47. package/dist/esm/subconscious/SemanticCache.d.ts.map +1 -1
  48. package/dist/esm/subconscious/SemanticCache.js +14 -10
  49. package/dist/esm/subconscious/SemanticCache.js.map +1 -1
  50. package/dist/esm/types/index.d.ts +8 -8
  51. package/dist/esm/types/index.d.ts.map +1 -1
  52. package/dist/esm/utils/SavingsLedger.d.ts +2 -0
  53. package/dist/esm/utils/SavingsLedger.d.ts.map +1 -1
  54. package/dist/esm/utils/SavingsLedger.js +46 -0
  55. package/dist/esm/utils/SavingsLedger.js.map +1 -1
  56. package/dist/esm/utils/logger.d.ts +27 -6
  57. package/dist/esm/utils/logger.d.ts.map +1 -1
  58. package/dist/esm/utils/logger.js +93 -28
  59. package/dist/esm/utils/logger.js.map +1 -1
  60. package/package.json +28 -24
  61. package/dashboard/dist/assets/index-CIlkFwDs.js +0 -490
  62. package/dashboard/dist/assets/index-CIlkFwDs.js.map +0 -1
  63. package/dashboard/dist/assets/index-DNqq3gA9.css +0 -1
@@ -32,7 +32,7 @@ function getVersion() {
32
32
  catch { }
33
33
  }
34
34
  }
35
- return '0.5.2';
35
+ return '0.5.3';
36
36
  }
37
37
  const VERSION = getVersion();
38
38
  const ComplexityRouter_1 = __importDefault(require("../proxy/ComplexityRouter"));
@@ -86,13 +86,20 @@ async function readJson(file, fallback) {
86
86
  catch { }
87
87
  return fallback;
88
88
  }
89
+ const writeQueues = new Map();
89
90
  async function writeJson(file, data) {
90
- try {
91
- const tempFile = file + '.tmp';
92
- await fs_1.default.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
93
- await fs_1.default.promises.rename(tempFile, file);
94
- }
95
- catch { }
91
+ const chain = writeQueues.get(file) || Promise.resolve();
92
+ const writeOperation = async () => {
93
+ try {
94
+ const tempFile = `${file}.${crypto_1.default.randomUUID ? crypto_1.default.randomUUID() : Math.random().toString(36).substring(2, 15)}.tmp`;
95
+ await fs_1.default.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
96
+ await fs_1.default.promises.rename(tempFile, file);
97
+ }
98
+ catch { }
99
+ };
100
+ const nextChain = chain.then(writeOperation).catch(() => { });
101
+ writeQueues.set(file, nextChain);
102
+ return nextChain;
96
103
  }
97
104
  function projectHash(name) {
98
105
  return crypto_1.default.createHash('sha1').update(name).digest('hex').slice(0, 12);
@@ -386,12 +393,21 @@ function startBackgroundClipboardWatcher() {
386
393
  }
387
394
  }
388
395
  // ── License helpers ────────────────────────────────────────────────────────────
396
+ let isProCached = null;
397
+ let lastLicenseCheck = 0;
398
+ const LICENSE_CACHE_TTL = 10000; // 10 seconds cache
389
399
  async function loadLicense() {
390
400
  return readJson(LICENSE_FILE, { isPro: false });
391
401
  }
392
402
  async function isPro() {
403
+ const now = Date.now();
404
+ if (isProCached !== null && (now - lastLicenseCheck) < LICENSE_CACHE_TTL) {
405
+ return isProCached;
406
+ }
393
407
  const l = await loadLicense();
394
- return !!(l && l.isPro);
408
+ isProCached = !!(l && l.isPro);
409
+ lastLicenseCheck = now;
410
+ return isProCached;
395
411
  }
396
412
  async function validateKeyRemote(key) {
397
413
  return new Promise((resolve, reject) => {
@@ -454,16 +470,21 @@ async function recordStat(stats, project, fromCache, latencyMs, provider, tokens
454
470
  s.total++;
455
471
  s.totalLatency += latencyMs;
456
472
  fromCache ? s.hits++ : s.misses++;
457
- s.totalTokensSaved += (fromCache ? (tokensSaved || 2000) : (tokensSaved || 0));
473
+ const tkSaved = fromCache ? (tokensSaved || 2000) : (tokensSaved || 0);
474
+ s.totalTokensSaved += tkSaved;
458
475
  if (!s.providers[provider])
459
476
  s.providers[provider] = { hits: 0, misses: 0 };
460
477
  fromCache ? s.providers[provider].hits++ : s.providers[provider].misses++;
478
+ if (fromCache && tkSaved > 0) {
479
+ // Automatically report cache hits directly to the server session ledger for absolute sync consistency
480
+ SavingsLedger_1.savingsLedger.recordTokens('cache', tkSaved);
481
+ }
461
482
  logEvent({
462
483
  type: fromCache ? 'cache:hit' : 'cache:miss',
463
484
  project,
464
485
  latency: latencyMs,
465
486
  provider,
466
- tokens: fromCache ? (tokensSaved || 2000) : (tokensSaved || 0)
487
+ tokens: tkSaved
467
488
  });
468
489
  await writeJson(STATS_FILE, stats);
469
490
  }
@@ -484,7 +505,15 @@ function cacheGet(provider, prompt) {
484
505
  return e;
485
506
  }
486
507
  function cacheSet(provider, prompt, data) {
487
- CACHE.set(cacheKey(provider, prompt), { input: prompt, data, createdAt: Date.now(), ttl: TTL_MS });
508
+ const MAX_CACHE_SIZE = 5000;
509
+ const k = cacheKey(provider, prompt);
510
+ if (!CACHE.has(k) && CACHE.size >= MAX_CACHE_SIZE) {
511
+ // Evict oldest cache entry (FIFO eviction)
512
+ const oldestKey = CACHE.keys().next().value;
513
+ if (oldestKey)
514
+ CACHE.delete(oldestKey);
515
+ }
516
+ CACHE.set(k, { input: prompt, data, createdAt: Date.now(), ttl: TTL_MS, provider });
488
517
  }
489
518
  // ── Semantic Search (Pro) ──────────────────────────────────────────────────────
490
519
  async function getEmbedding(text) {
@@ -529,7 +558,7 @@ async function semanticGet(provider, prompt) {
529
558
  distance = distances[0][i];
530
559
  }
531
560
  const similarity = Math.max(0, 1 - distance);
532
- const THRESHOLD = 0.7;
561
+ const THRESHOLD = 0.82; // raised from 0.7 to 0.82 to prevent false positives
533
562
  if (similarity >= THRESHOLD) {
534
563
  return {
535
564
  data: JSON.parse(metadata.response),
@@ -756,6 +785,16 @@ async function relayMissStream(res, body, provider, tier) {
756
785
  req.end();
757
786
  });
758
787
  }
788
+ class WriteQueue {
789
+ constructor() {
790
+ this.queue = Promise.resolve();
791
+ }
792
+ async enqueue(fn) {
793
+ this.queue = this.queue.then(fn).catch(() => { });
794
+ return this.queue;
795
+ }
796
+ }
797
+ const usageWriteQueue = new WriteQueue();
759
798
  // ── Proxy Server class ─────────────────────────────────────────────────────────
760
799
  class LemmaServer {
761
800
  constructor(port, projectName, openBrowser = true) {
@@ -818,6 +857,18 @@ class LemmaServer {
818
857
  const hitRate = stats.total > 0 ? stats.hits / stats.total : 0;
819
858
  const avgLat = stats.total > 0 ? stats.totalLatency / stats.total : 0;
820
859
  const secretsMasked = EVENT_LOG.filter(e => e.type === 'privacy:mask').length;
860
+ const ledgerSnap = SavingsLedger_1.savingsLedger.getSnapshot();
861
+ let activeAgents = 1; // Always include the Gateway itself
862
+ let totalAgents = 1;
863
+ // Query the stack orchestration API to merge active agents
864
+ try {
865
+ const stackMetrics = await axios_1.default.get('http://127.0.0.1:8083/api/metrics', { timeout: 100 });
866
+ if (stackMetrics.data) {
867
+ activeAgents += stackMetrics.data.activeAgents || 0;
868
+ totalAgents += stackMetrics.data.totalAgents || 0;
869
+ }
870
+ }
871
+ catch { }
821
872
  res.json({
822
873
  timestamp: new Date().toISOString(),
823
874
  totalRequests: stats.total,
@@ -825,30 +876,140 @@ class LemmaServer {
825
876
  cacheMisses: stats.misses,
826
877
  hitRate,
827
878
  averageLatency: avgLat,
828
- tokensSaved: stats.totalTokensSaved,
829
- costSaved: (stats.totalTokensSaved / 1000) * 0.02,
879
+ tokensSaved: ledgerSnap.total.tokensSaved,
880
+ costSaved: ledgerSnap.total.costSaved,
830
881
  secretsMasked: secretsMasked,
831
- activeAgents: 1,
832
- totalAgents: 1,
882
+ activeAgents,
883
+ totalAgents,
833
884
  uptime: process.uptime(),
834
885
  tier: (await isPro()) ? 'pro' : 'free'
835
886
  });
836
887
  });
837
888
  this.app.get('/api/agents', async (req, res) => {
889
+ // Load statistics for the Gateway itself
838
890
  const stats = await loadStats();
839
- res.json({ agents: [
840
- { id: 'lemma-gateway', name: 'Lemma Gateway', status: 'active', capabilities: ['Privacy Firewall', 'Semantic Cache', 'Complexity Router'], tasksCompleted: stats[this.projectName]?.total || 0, lastSeen: new Date().toISOString(), errorCount: 0, cacheHitRate: 0.85 }
841
- ] });
891
+ const statsProj = stats[this.projectName] || { total: 0, hits: 0 };
892
+ const total = statsProj.total || 0;
893
+ const hits = statsProj.hits || 0;
894
+ const gatewayAgent = {
895
+ id: 'lemma-gateway',
896
+ name: 'Lemma Gateway',
897
+ status: 'active',
898
+ capabilities: ['Privacy Firewall', 'Semantic Cache', 'Complexity Router'],
899
+ tasksCompleted: total,
900
+ lastSeen: new Date().toISOString(),
901
+ errorCount: 0,
902
+ cacheHitRate: total > 0 ? hits / total : 0
903
+ };
904
+ let agentsList = [gatewayAgent];
905
+ // Query the stack orchestration API to merge dynamic websocket agents if running
906
+ try {
907
+ const stackAgentsResponse = await axios_1.default.get('http://127.0.0.1:8083/api/agents', { timeout: 100 });
908
+ if (stackAgentsResponse.data && Array.isArray(stackAgentsResponse.data.agents)) {
909
+ const stackAgents = stackAgentsResponse.data.agents.filter((a) => a.id !== 'lemma-gateway');
910
+ agentsList = [...agentsList, ...stackAgents];
911
+ }
912
+ }
913
+ catch { }
914
+ res.json({ agents: agentsList });
842
915
  });
843
- this.app.get('/api/events', (req, res) => {
916
+ this.app.get('/api/events', async (req, res) => {
844
917
  const page = parseInt(req.query.page) || 1;
845
918
  const limit = parseInt(req.query.limit) || 50;
846
919
  const start = (page - 1) * limit;
847
- const events = EVENT_LOG.slice().reverse().slice(start, start + limit);
848
- res.json({ events, pagination: { total: EVENT_LOG.length, page, limit } });
920
+ // Get local proxy events (normalized schema matching what dashboard expects)
921
+ const localEvents = EVENT_LOG.map(e => ({
922
+ id: e.id,
923
+ timestamp: new Date(e.timestamp).toISOString(),
924
+ type: e.type, // e.g., 'cache:hit' or 'cache:miss' which client normalizes to 'cache_hit' or 'cache_miss'
925
+ latency: e.latency,
926
+ tokensSaved: e.tokens,
927
+ costSaved: e.tokens ? (e.tokens / 1000) * 0.002 : 0,
928
+ metadata: {
929
+ provider: e.provider,
930
+ project: e.project
931
+ }
932
+ }));
933
+ let mergedEvents = [...localEvents];
934
+ // Query the stack orchestration API if running to merge stack events
935
+ try {
936
+ const stackEventsResponse = await axios_1.default.get(`http://127.0.0.1:8083/api/events?page=1&limit=100`, { timeout: 100 });
937
+ if (stackEventsResponse.data && Array.isArray(stackEventsResponse.data.events)) {
938
+ mergedEvents = [...mergedEvents, ...stackEventsResponse.data.events];
939
+ }
940
+ }
941
+ catch { }
942
+ // Sort merged events chronologically (newest first)
943
+ mergedEvents.sort((a, b) => {
944
+ const tA = new Date(a.timestamp).getTime();
945
+ const tB = new Date(b.timestamp).getTime();
946
+ return tB - tA;
947
+ });
948
+ const total = mergedEvents.length;
949
+ const paginatedEvents = mergedEvents.slice(start, start + limit);
950
+ res.json({
951
+ events: paginatedEvents,
952
+ pagination: {
953
+ total,
954
+ page,
955
+ limit,
956
+ hasMore: start + limit < total
957
+ }
958
+ });
849
959
  });
850
- this.app.get('/api/cache-stats', (req, res) => {
851
- res.json({ topQueries: [] });
960
+ this.app.get('/api/cache-stats', async (req, res) => {
961
+ let topQueries = [];
962
+ let totalCached = CACHE.size;
963
+ // Try to query ChromaDB for semantic cache count
964
+ try {
965
+ const collection = await chroma.getOrCreateCollection({
966
+ name: 'lemma-cache',
967
+ embeddingFunction: dummyEmbeddingFunction,
968
+ metadata: { "hnsw:space": "cosine" }
969
+ });
970
+ const count = await collection.count();
971
+ totalCached += count;
972
+ }
973
+ catch { }
974
+ // Build top queries from in-memory CACHE
975
+ for (const [key, entry] of CACHE.entries()) {
976
+ const hitCount = EVENT_LOG.filter(e => e.type === 'cache:hit' && e.provider === entry.provider).length || 1;
977
+ const tokens = 2000;
978
+ topQueries.push({
979
+ queryHash: key.substring(0, 16),
980
+ query: entry.input,
981
+ hitCount,
982
+ totalTokens: tokens * hitCount,
983
+ costSaved: (tokens * hitCount / 1000) * 0.002,
984
+ lastHit: new Date(entry.createdAt).toISOString()
985
+ });
986
+ }
987
+ // Try to query the stack orchestration API if running to append stack cache stats
988
+ try {
989
+ const stackCache = await axios_1.default.get('http://127.0.0.1:8083/api/cache-stats', { timeout: 100 });
990
+ if (stackCache.data) {
991
+ totalCached += stackCache.data.totalCached || 0;
992
+ if (Array.isArray(stackCache.data.topQueries)) {
993
+ const stackTop = stackCache.data.topQueries.filter((q) => !topQueries.some(t => t.query === q.query));
994
+ topQueries = [...topQueries, ...stackTop];
995
+ }
996
+ }
997
+ }
998
+ catch { }
999
+ // Sort by hitCount desc
1000
+ topQueries.sort((a, b) => b.hitCount - a.hitCount);
1001
+ // Latency percentiles from EVENT_LOG
1002
+ const latencies = EVENT_LOG.filter(e => e.latency).map(e => e.latency).sort((a, b) => a - b);
1003
+ const latencyPercentiles = {
1004
+ p50: latencies[Math.floor(latencies.length * 0.5)] || 0,
1005
+ p95: latencies[Math.floor(latencies.length * 0.95)] || 0,
1006
+ p99: latencies[Math.floor(latencies.length * 0.99)] || 0,
1007
+ };
1008
+ res.json({
1009
+ topQueries: topQueries.slice(0, 10),
1010
+ latencyPercentiles,
1011
+ totalCached
1012
+ });
852
1013
  });
853
1014
  this.app.get('/api/search', async (req, res) => {
854
1015
  const query = req.query.q;
@@ -885,24 +1046,57 @@ class LemmaServer {
885
1046
  res.status(500).json({ error: e.message });
886
1047
  }
887
1048
  });
888
- this.app.post('/api/cache/clear', (req, res) => {
889
- const count = CACHE.size;
890
- CACHE.clear();
891
- EVENT_LOG.length = 0; // Clear live event log
892
- SavingsLedger_1.savingsLedger.reset(); // Reset savings counters
893
- // Reset and persist statistics for the current project
894
- if (this.stats[this.projectName]) {
895
- this.stats[this.projectName] = {
896
- total: 0,
897
- hits: 0,
898
- misses: 0,
899
- totalLatency: 0,
900
- totalTokensSaved: 0,
901
- providers: {}
902
- };
903
- writeJson(STATS_FILE, this.stats).catch(() => { });
1049
+ this.app.post('/api/cache/clear', async (req, res) => {
1050
+ const { provider } = req.body || {};
1051
+ let clearedCount = 0;
1052
+ if (!provider) {
1053
+ // Global clear
1054
+ clearedCount = CACHE.size;
1055
+ CACHE.clear();
1056
+ EVENT_LOG.length = 0; // Clear live event log
1057
+ SavingsLedger_1.savingsLedger.reset(); // Reset savings counters
1058
+ // Reset and persist statistics for the current project
1059
+ if (this.stats[this.projectName]) {
1060
+ this.stats[this.projectName] = {
1061
+ total: 0,
1062
+ hits: 0,
1063
+ misses: 0,
1064
+ totalLatency: 0,
1065
+ totalTokensSaved: 0,
1066
+ providers: {}
1067
+ };
1068
+ writeJson(STATS_FILE, this.stats).catch(() => { });
1069
+ }
1070
+ // Clear Chroma DB collection
1071
+ try {
1072
+ await chroma.deleteCollection({ name: 'lemma-cache' }).catch(() => { });
1073
+ }
1074
+ catch (e) {
1075
+ console.error(`[CacheClear] Failed to clear Chroma collection: ${e.message}`);
1076
+ }
904
1077
  }
905
- res.json({ success: true, clearedEntries: count });
1078
+ else {
1079
+ // Granular clear
1080
+ for (const [k, e] of CACHE.entries()) {
1081
+ if (e.provider === provider) {
1082
+ CACHE.delete(k);
1083
+ clearedCount++;
1084
+ }
1085
+ }
1086
+ // Selective delete from Chroma DB collection using provider metadata filter
1087
+ try {
1088
+ const collection = await chroma.getOrCreateCollection({
1089
+ name: 'lemma-cache',
1090
+ embeddingFunction: dummyEmbeddingFunction,
1091
+ metadata: { "hnsw:space": "cosine" }
1092
+ });
1093
+ await collection.delete({ where: { provider } });
1094
+ }
1095
+ catch (e) {
1096
+ console.error(`[CacheClear] Failed to selectively delete provider ${provider} from Chroma: ${e.message}`);
1097
+ }
1098
+ }
1099
+ res.json({ success: true, clearedEntries: clearedCount });
906
1100
  });
907
1101
  // Savings breakdown by source for the dashboard
908
1102
  this.app.get('/api/savings-breakdown', (req, res) => {
@@ -946,89 +1140,35 @@ class LemmaServer {
946
1140
  logEvent({ type: 'privacy:mask', count: Object.keys(tokenMap).length, project: this.projectName });
947
1141
  }
948
1142
  const originalModel = req.body.model || 'gpt-4o';
949
- const routingDecision = complexityRouter.evaluate(prompt, originalModel);
950
- const model = routingDecision.model;
951
- if (req.body.model)
952
- req.body.model = model;
953
- // Record complexity routing savings when model is downgraded
954
- if (routingDecision.complexity === 'low' && model !== originalModel) {
955
- // Estimated token savings: cheap model costs ~5x less, so avg prompt of ~800 tokens saves ~640 tokens
956
- const estimatedPromptTokens = Math.floor(prompt.length / 4);
957
- const routingSavings = Math.floor(estimatedPromptTokens * 0.8); // conservative 80% cost reduction
958
- SavingsLedger_1.savingsLedger.recordTokens('complexityRouting', routingSavings);
959
- }
960
- // Apply the Agent Multiplexer to local-verify the prompt in parallel
961
- const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
962
- // Resolve conversation session key and retrieve/create the fileHistory map
963
- let fileHistory;
964
- if (req.body.messages && req.body.messages.length > 0) {
965
- try {
966
- const firstMsg = req.body.messages[0];
967
- const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
968
- const sessionKey = crypto_1.default.createHash('sha1').update(content).digest('hex');
969
- if (!this.sessions.has(sessionKey)) {
970
- this.sessions.set(sessionKey, { fileHistory: new Map() });
971
- }
972
- fileHistory = this.sessions.get(sessionKey).fileHistory;
973
- }
974
- catch { }
975
- }
976
- // Compress long TS/JS codebase file blocks in the enriched prompt using the Context Squeezer AST Tree-Shaker!
977
- // The query context is the user's primary prompt to ensure we keep relevant methods!
978
- const squeezeResult = (0, ContextSqueezer_1.squeezePrompt)(enrichedPrompt, prompt, fileHistory);
979
- const squeezedPrompt = squeezeResult.squeezed;
980
- const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
981
- if (compressionSavings > 0) {
982
- SavingsLedger_1.savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
983
- console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
984
- }
985
- if (provider === 'openai' || provider === 'anthropic') {
986
- if (req.body.messages && req.body.messages.length > 0) {
987
- injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
988
- // Apply conversational history pruning to save tokens on historical messages!
989
- const pruneResult = (0, ContextSqueezer_1.pruneHistoryMessages)(req.body.messages);
990
- req.body.messages = pruneResult.messages;
991
- historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
992
- if (pruneResult.tokensSavedEstimate > 0) {
993
- SavingsLedger_1.savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
994
- }
995
- }
996
- }
997
- else if (provider === 'gemini') {
998
- if (req.body.contents && req.body.contents.length > 0) {
999
- const lastContent = req.body.contents[req.body.contents.length - 1];
1000
- if (lastContent.parts && lastContent.parts.length > 0) {
1001
- lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
1002
- }
1003
- }
1004
- }
1143
+ // ── 1. Cache-First Hot Path ──
1144
+ // Perform exact cache check immediately (resolves in <1ms)
1005
1145
  const hit = cacheGet(provider, prompt);
1006
1146
  if (hit) {
1007
- const cacheTokensSaved = 2000 + historyTokensSaved;
1008
- SavingsLedger_1.savingsLedger.recordTokens('cache', cacheTokensSaved);
1147
+ const cacheTokensSaved = 2000;
1009
1148
  await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, cacheTokensSaved);
1010
1149
  const unmaskedData = semanticScrubber.unmask(hit.data, tokenMap);
1011
1150
  if (isStream)
1012
- return simulateHitStream(res, unmaskedData, provider, model, tier, 1.0);
1151
+ return simulateHitStream(res, unmaskedData, provider, originalModel, tier, 1.0);
1013
1152
  res.setHeader('X-Lemma-Cache', 'HIT');
1014
1153
  res.setHeader('X-Lemma-Similarity', '1.000');
1015
1154
  res.setHeader('X-Lemma-Tier', tier);
1016
1155
  return res.json(unmaskedData);
1017
1156
  }
1157
+ // Perform semantic cache check (resolves in ~15ms)
1018
1158
  if (pro) {
1019
1159
  const semHit = await semanticGet(provider, prompt);
1020
1160
  if (semHit) {
1021
1161
  if (semHit.similarity >= 0.90) {
1022
- await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000 + historyTokensSaved);
1162
+ await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
1023
1163
  const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
1024
1164
  if (isStream)
1025
- return simulateHitStream(res, unmaskedData, provider, model, tier, semHit.similarity);
1165
+ return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
1026
1166
  res.setHeader('X-Lemma-Cache', 'HIT');
1027
1167
  res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
1028
1168
  res.setHeader('X-Lemma-Tier', tier);
1029
1169
  return res.json(unmaskedData);
1030
1170
  }
1031
- else if (semHit.similarity >= 0.70) {
1171
+ else if (semHit.similarity >= 0.82) { // raised from 0.70 to 0.82 to prevent false positives
1032
1172
  console.log(`💡 [CARS] Near Cache Hit (Similarity: ${(semHit.similarity * 100).toFixed(1)}%). Synthesizing answer locally via Ollama...`);
1033
1173
  let cachedContent = '';
1034
1174
  if (provider === 'openai') {
@@ -1066,10 +1206,10 @@ Adjusted Answer:`;
1066
1206
  else if (provider === 'anthropic') {
1067
1207
  synthesizedData.content[0].text = adjustedText;
1068
1208
  }
1069
- await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000 + historyTokensSaved);
1209
+ await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
1070
1210
  const unmaskedData = semanticScrubber.unmask(synthesizedData, tokenMap);
1071
1211
  if (isStream) {
1072
- return simulateHitStream(res, unmaskedData, provider, model, tier, semHit.similarity);
1212
+ return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
1073
1213
  }
1074
1214
  res.setHeader('X-Lemma-Cache', 'HIT-CARS');
1075
1215
  res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
@@ -1086,22 +1226,86 @@ Adjusted Answer:`;
1086
1226
  const cloudHit = await cloudSync.get(prompt);
1087
1227
  if (cloudHit) {
1088
1228
  cacheSet(provider, prompt, cloudHit.data);
1089
- await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000 + historyTokensSaved);
1229
+ await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
1090
1230
  const unmaskedData = semanticScrubber.unmask(cloudHit.data, tokenMap);
1091
1231
  if (isStream)
1092
- return simulateHitStream(res, unmaskedData, provider, model, tier, cloudHit.similarity || 0.95);
1232
+ return simulateHitStream(res, unmaskedData, provider, originalModel, tier, cloudHit.similarity || 0.95);
1093
1233
  res.setHeader('X-Lemma-Cache', 'HIT');
1094
1234
  res.setHeader('X-Lemma-Similarity', (cloudHit.similarity || 0.95).toFixed(3));
1095
1235
  res.setHeader('X-Lemma-Tier', tier);
1096
1236
  return res.json(unmaskedData);
1097
1237
  }
1098
1238
  }
1239
+ // ── 2. Cache Miss Path ──
1240
+ const routingDecision = complexityRouter.evaluate(prompt, originalModel);
1241
+ const model = routingDecision.model;
1242
+ if (req.body.model)
1243
+ req.body.model = model;
1244
+ // Record complexity routing savings when model is downgraded
1245
+ if (routingDecision.complexity === 'low' && model !== originalModel) {
1246
+ const estimatedPromptTokens = Math.floor(prompt.length / 4);
1247
+ const routingSavings = Math.floor(estimatedPromptTokens * 0.8);
1248
+ SavingsLedger_1.savingsLedger.recordTokens('complexityRouting', routingSavings);
1249
+ }
1250
+ // Apply the Agent Multiplexer to local-verify the prompt in parallel
1251
+ const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
1252
+ // Resolve conversation session key and retrieve/create the fileHistory map
1253
+ let fileHistory;
1254
+ if (req.body.messages && req.body.messages.length > 0) {
1255
+ try {
1256
+ const firstMsg = req.body.messages[0];
1257
+ const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
1258
+ const sessionKey = crypto_1.default.createHash('sha1').update(content).digest('hex');
1259
+ if (!this.sessions.has(sessionKey)) {
1260
+ // Enforce 500 active sessions cap to prevent memory leak (FIFO eviction)
1261
+ const MAX_SESSIONS = 500;
1262
+ if (this.sessions.size >= MAX_SESSIONS) {
1263
+ const oldestKey = this.sessions.keys().next().value;
1264
+ if (oldestKey)
1265
+ this.sessions.delete(oldestKey);
1266
+ }
1267
+ this.sessions.set(sessionKey, { fileHistory: new Map() });
1268
+ }
1269
+ fileHistory = this.sessions.get(sessionKey).fileHistory;
1270
+ }
1271
+ catch { }
1272
+ }
1273
+ // Compress long TS/JS codebase file blocks in the enriched prompt
1274
+ const squeezeResult = (0, ContextSqueezer_1.squeezePrompt)(enrichedPrompt, prompt, fileHistory);
1275
+ const squeezedPrompt = squeezeResult.squeezed;
1276
+ const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
1277
+ if (compressionSavings > 0) {
1278
+ SavingsLedger_1.savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
1279
+ console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
1280
+ }
1281
+ if (provider === 'openai' || provider === 'anthropic') {
1282
+ if (req.body.messages && req.body.messages.length > 0) {
1283
+ injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
1284
+ // Apply conversational history pruning
1285
+ const pruneResult = (0, ContextSqueezer_1.pruneHistoryMessages)(req.body.messages);
1286
+ req.body.messages = pruneResult.messages;
1287
+ historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
1288
+ if (pruneResult.tokensSavedEstimate > 0) {
1289
+ SavingsLedger_1.savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
1290
+ }
1291
+ }
1292
+ }
1293
+ else if (provider === 'gemini') {
1294
+ if (req.body.contents && req.body.contents.length > 0) {
1295
+ const lastContent = req.body.contents[req.body.contents.length - 1];
1296
+ if (lastContent.parts && lastContent.parts.length > 0) {
1297
+ lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
1298
+ }
1299
+ }
1300
+ }
1099
1301
  if (isStream) {
1100
1302
  await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
1101
1303
  if (!pro) {
1102
- const u = await loadUsage();
1103
- u.count++;
1104
- await writeJson(USAGE_FILE, u);
1304
+ await usageWriteQueue.enqueue(async () => {
1305
+ const u = await loadUsage();
1306
+ u.count++;
1307
+ await writeJson(USAGE_FILE, u);
1308
+ });
1105
1309
  }
1106
1310
  return relayMissStream(res, req.body, provider, tier);
1107
1311
  }
@@ -1113,9 +1317,11 @@ Adjusted Answer:`;
1113
1317
  await cloudSync.set(prompt, data);
1114
1318
  }
1115
1319
  if (!pro) {
1116
- const u = await loadUsage();
1117
- u.count++;
1118
- await writeJson(USAGE_FILE, u);
1320
+ await usageWriteQueue.enqueue(async () => {
1321
+ const u = await loadUsage();
1322
+ u.count++;
1323
+ await writeJson(USAGE_FILE, u);
1324
+ });
1119
1325
  }
1120
1326
  await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
1121
1327
  return res.json(semanticScrubber.unmask(data, tokenMap));
@@ -1126,6 +1332,43 @@ Adjusted Answer:`;
1126
1332
  }
1127
1333
  async callUpstream(body, provider) {
1128
1334
  let apiKey = provider === 'openai' ? process.env.OPENAI_API_KEY : (provider === 'anthropic' ? process.env.ANTHROPIC_API_KEY : process.env.GEMINI_API_KEY);
1335
+ // If the API key is an OpenAI key (sk-proj-) but requested for Anthropic or Gemini,
1336
+ // multiplex it internally to OpenAI gpt-4o-mini to allow live, cheap simulated provider routing.
1337
+ const isMockedProvider = apiKey && apiKey.startsWith('sk-') && provider !== 'openai';
1338
+ if (isMockedProvider) {
1339
+ console.log(`\n\x1b[36m🔀 [Gateway Router] Multiplexing B2B ${provider.toUpperCase()} request to OpenAI using key: ...${apiKey.substring(8, 14)}...\x1b[0m`);
1340
+ const payload = {
1341
+ model: 'gpt-4o-mini',
1342
+ messages: body.messages || [{ role: 'user', content: extractPrompt(body, provider) || 'Hello' }],
1343
+ stream: false // keep stream false for simulation simplicity
1344
+ };
1345
+ const baseURL = 'https://api.openai.com/v1/chat/completions';
1346
+ const resp = await axios_1.default.post(baseURL, payload, {
1347
+ headers: {
1348
+ Authorization: `Bearer ${apiKey}`,
1349
+ 'Content-Type': 'application/json'
1350
+ }
1351
+ });
1352
+ if (provider === 'anthropic') {
1353
+ const text = resp.data.choices?.[0]?.message?.content || '';
1354
+ return {
1355
+ id: `msg-${Date.now()}`,
1356
+ type: 'message',
1357
+ role: 'assistant',
1358
+ content: [{ type: 'text', text }],
1359
+ model: body.model || 'claude-3-5-sonnet'
1360
+ };
1361
+ }
1362
+ else if (provider === 'gemini') {
1363
+ const text = resp.data.choices?.[0]?.message?.content || '';
1364
+ return {
1365
+ candidates: [{
1366
+ content: { parts: [{ text }] },
1367
+ finishReason: 'STOP'
1368
+ }]
1369
+ };
1370
+ }
1371
+ }
1129
1372
  if (!apiKey) {
1130
1373
  console.log(`\n\x1b[33m⚠️ No API Key found for ${provider.toUpperCase()}. Falling back to local Ollama (Zero-Cost Mode)!\x1b[0m`);
1131
1374
  // If the requested model is a cloud model (e.g. gpt-4o, claude), map it to a local model like llama3
@@ -1152,6 +1395,15 @@ Adjusted Answer:`;
1152
1395
  }
1153
1396
  async start() {
1154
1397
  this.stats = await loadStats();
1398
+ // Start active cache eviction sweep every hour
1399
+ setInterval(() => {
1400
+ const now = Date.now();
1401
+ for (const [k, e] of CACHE.entries()) {
1402
+ if (e.ttl > 0 && now - e.createdAt > e.ttl) {
1403
+ CACHE.delete(k);
1404
+ }
1405
+ }
1406
+ }, 3600000).unref();
1155
1407
  const server = http_1.default.createServer(this.app);
1156
1408
  server.on('error', (e) => {
1157
1409
  if (e.code === 'EADDRINUSE') {