@nxuss/lemma 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +187 -98
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/config/index.d.ts.map +1 -1
- package/dist/cjs/config/index.js +2 -1
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/errors/LemmaError.d.ts +52 -0
- package/dist/cjs/errors/LemmaError.d.ts.map +1 -0
- package/dist/cjs/errors/LemmaError.js +84 -0
- package/dist/cjs/errors/LemmaError.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +24 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/subconscious/SemanticCache.d.ts +4 -4
- package/dist/cjs/subconscious/SemanticCache.d.ts.map +1 -1
- package/dist/cjs/subconscious/SemanticCache.js +14 -10
- package/dist/cjs/subconscious/SemanticCache.js.map +1 -1
- package/dist/cjs/types/index.d.ts +8 -8
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/utils/logger.d.ts +27 -6
- package/dist/cjs/utils/logger.d.ts.map +1 -1
- package/dist/cjs/utils/logger.js +96 -28
- package/dist/cjs/utils/logger.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +187 -98
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/config/index.d.ts.map +1 -1
- package/dist/esm/config/index.js +2 -1
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/errors/LemmaError.d.ts +52 -0
- package/dist/esm/errors/LemmaError.d.ts.map +1 -0
- package/dist/esm/errors/LemmaError.js +75 -0
- package/dist/esm/errors/LemmaError.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +24 -22
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/subconscious/SemanticCache.d.ts +4 -4
- package/dist/esm/subconscious/SemanticCache.d.ts.map +1 -1
- package/dist/esm/subconscious/SemanticCache.js +14 -10
- package/dist/esm/subconscious/SemanticCache.js.map +1 -1
- package/dist/esm/types/index.d.ts +8 -8
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/utils/logger.d.ts +27 -6
- package/dist/esm/utils/logger.d.ts.map +1 -1
- package/dist/esm/utils/logger.js +93 -28
- package/dist/esm/utils/logger.js.map +1 -1
- package/package.json +27 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA8NA,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA0I3D;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAyCtD;AAuxCD,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
|
|
@@ -24,7 +24,7 @@ function getVersion() {
|
|
|
24
24
|
catch { }
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
return '0.5.
|
|
27
|
+
return '0.5.3';
|
|
28
28
|
}
|
|
29
29
|
const VERSION = getVersion();
|
|
30
30
|
import ComplexityRouter from '../proxy/ComplexityRouter';
|
|
@@ -78,13 +78,20 @@ async function readJson(file, fallback) {
|
|
|
78
78
|
catch { }
|
|
79
79
|
return fallback;
|
|
80
80
|
}
|
|
81
|
+
const writeQueues = new Map();
|
|
81
82
|
async function writeJson(file, data) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
const chain = writeQueues.get(file) || Promise.resolve();
|
|
84
|
+
const writeOperation = async () => {
|
|
85
|
+
try {
|
|
86
|
+
const tempFile = `${file}.${crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15)}.tmp`;
|
|
87
|
+
await fs.promises.writeFile(tempFile, JSON.stringify(data, null, 2));
|
|
88
|
+
await fs.promises.rename(tempFile, file);
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
};
|
|
92
|
+
const nextChain = chain.then(writeOperation).catch(() => { });
|
|
93
|
+
writeQueues.set(file, nextChain);
|
|
94
|
+
return nextChain;
|
|
88
95
|
}
|
|
89
96
|
function projectHash(name) {
|
|
90
97
|
return crypto.createHash('sha1').update(name).digest('hex').slice(0, 12);
|
|
@@ -378,12 +385,21 @@ export function startBackgroundClipboardWatcher() {
|
|
|
378
385
|
}
|
|
379
386
|
}
|
|
380
387
|
// ── License helpers ────────────────────────────────────────────────────────────
|
|
388
|
+
let isProCached = null;
|
|
389
|
+
let lastLicenseCheck = 0;
|
|
390
|
+
const LICENSE_CACHE_TTL = 10000; // 10 seconds cache
|
|
381
391
|
async function loadLicense() {
|
|
382
392
|
return readJson(LICENSE_FILE, { isPro: false });
|
|
383
393
|
}
|
|
384
394
|
async function isPro() {
|
|
395
|
+
const now = Date.now();
|
|
396
|
+
if (isProCached !== null && (now - lastLicenseCheck) < LICENSE_CACHE_TTL) {
|
|
397
|
+
return isProCached;
|
|
398
|
+
}
|
|
385
399
|
const l = await loadLicense();
|
|
386
|
-
|
|
400
|
+
isProCached = !!(l && l.isPro);
|
|
401
|
+
lastLicenseCheck = now;
|
|
402
|
+
return isProCached;
|
|
387
403
|
}
|
|
388
404
|
async function validateKeyRemote(key) {
|
|
389
405
|
return new Promise((resolve, reject) => {
|
|
@@ -476,7 +492,15 @@ function cacheGet(provider, prompt) {
|
|
|
476
492
|
return e;
|
|
477
493
|
}
|
|
478
494
|
function cacheSet(provider, prompt, data) {
|
|
479
|
-
|
|
495
|
+
const MAX_CACHE_SIZE = 5000;
|
|
496
|
+
const k = cacheKey(provider, prompt);
|
|
497
|
+
if (!CACHE.has(k) && CACHE.size >= MAX_CACHE_SIZE) {
|
|
498
|
+
// Evict oldest cache entry (FIFO eviction)
|
|
499
|
+
const oldestKey = CACHE.keys().next().value;
|
|
500
|
+
if (oldestKey)
|
|
501
|
+
CACHE.delete(oldestKey);
|
|
502
|
+
}
|
|
503
|
+
CACHE.set(k, { input: prompt, data, createdAt: Date.now(), ttl: TTL_MS, provider });
|
|
480
504
|
}
|
|
481
505
|
// ── Semantic Search (Pro) ──────────────────────────────────────────────────────
|
|
482
506
|
async function getEmbedding(text) {
|
|
@@ -521,7 +545,7 @@ async function semanticGet(provider, prompt) {
|
|
|
521
545
|
distance = distances[0][i];
|
|
522
546
|
}
|
|
523
547
|
const similarity = Math.max(0, 1 - distance);
|
|
524
|
-
const THRESHOLD = 0.7
|
|
548
|
+
const THRESHOLD = 0.82; // raised from 0.7 to 0.82 to prevent false positives
|
|
525
549
|
if (similarity >= THRESHOLD) {
|
|
526
550
|
return {
|
|
527
551
|
data: JSON.parse(metadata.response),
|
|
@@ -748,6 +772,16 @@ async function relayMissStream(res, body, provider, tier) {
|
|
|
748
772
|
req.end();
|
|
749
773
|
});
|
|
750
774
|
}
|
|
775
|
+
class WriteQueue {
|
|
776
|
+
constructor() {
|
|
777
|
+
this.queue = Promise.resolve();
|
|
778
|
+
}
|
|
779
|
+
async enqueue(fn) {
|
|
780
|
+
this.queue = this.queue.then(fn).catch(() => { });
|
|
781
|
+
return this.queue;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const usageWriteQueue = new WriteQueue();
|
|
751
785
|
// ── Proxy Server class ─────────────────────────────────────────────────────────
|
|
752
786
|
class LemmaServer {
|
|
753
787
|
constructor(port, projectName, openBrowser = true) {
|
|
@@ -877,24 +911,57 @@ class LemmaServer {
|
|
|
877
911
|
res.status(500).json({ error: e.message });
|
|
878
912
|
}
|
|
879
913
|
});
|
|
880
|
-
this.app.post('/api/cache/clear', (req, res) => {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
914
|
+
this.app.post('/api/cache/clear', async (req, res) => {
|
|
915
|
+
const { provider } = req.body || {};
|
|
916
|
+
let clearedCount = 0;
|
|
917
|
+
if (!provider) {
|
|
918
|
+
// Global clear
|
|
919
|
+
clearedCount = CACHE.size;
|
|
920
|
+
CACHE.clear();
|
|
921
|
+
EVENT_LOG.length = 0; // Clear live event log
|
|
922
|
+
savingsLedger.reset(); // Reset savings counters
|
|
923
|
+
// Reset and persist statistics for the current project
|
|
924
|
+
if (this.stats[this.projectName]) {
|
|
925
|
+
this.stats[this.projectName] = {
|
|
926
|
+
total: 0,
|
|
927
|
+
hits: 0,
|
|
928
|
+
misses: 0,
|
|
929
|
+
totalLatency: 0,
|
|
930
|
+
totalTokensSaved: 0,
|
|
931
|
+
providers: {}
|
|
932
|
+
};
|
|
933
|
+
writeJson(STATS_FILE, this.stats).catch(() => { });
|
|
934
|
+
}
|
|
935
|
+
// Clear Chroma DB collection
|
|
936
|
+
try {
|
|
937
|
+
await chroma.deleteCollection({ name: 'lemma-cache' }).catch(() => { });
|
|
938
|
+
}
|
|
939
|
+
catch (e) {
|
|
940
|
+
console.error(`[CacheClear] Failed to clear Chroma collection: ${e.message}`);
|
|
941
|
+
}
|
|
896
942
|
}
|
|
897
|
-
|
|
943
|
+
else {
|
|
944
|
+
// Granular clear
|
|
945
|
+
for (const [k, e] of CACHE.entries()) {
|
|
946
|
+
if (e.provider === provider) {
|
|
947
|
+
CACHE.delete(k);
|
|
948
|
+
clearedCount++;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// Selective delete from Chroma DB collection using provider metadata filter
|
|
952
|
+
try {
|
|
953
|
+
const collection = await chroma.getOrCreateCollection({
|
|
954
|
+
name: 'lemma-cache',
|
|
955
|
+
embeddingFunction: dummyEmbeddingFunction,
|
|
956
|
+
metadata: { "hnsw:space": "cosine" }
|
|
957
|
+
});
|
|
958
|
+
await collection.delete({ where: { provider } });
|
|
959
|
+
}
|
|
960
|
+
catch (e) {
|
|
961
|
+
console.error(`[CacheClear] Failed to selectively delete provider ${provider} from Chroma: ${e.message}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
res.json({ success: true, clearedEntries: clearedCount });
|
|
898
965
|
});
|
|
899
966
|
// Savings breakdown by source for the dashboard
|
|
900
967
|
this.app.get('/api/savings-breakdown', (req, res) => {
|
|
@@ -938,89 +1005,36 @@ class LemmaServer {
|
|
|
938
1005
|
logEvent({ type: 'privacy:mask', count: Object.keys(tokenMap).length, project: this.projectName });
|
|
939
1006
|
}
|
|
940
1007
|
const originalModel = req.body.model || 'gpt-4o';
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (req.body.model)
|
|
944
|
-
req.body.model = model;
|
|
945
|
-
// Record complexity routing savings when model is downgraded
|
|
946
|
-
if (routingDecision.complexity === 'low' && model !== originalModel) {
|
|
947
|
-
// Estimated token savings: cheap model costs ~5x less, so avg prompt of ~800 tokens saves ~640 tokens
|
|
948
|
-
const estimatedPromptTokens = Math.floor(prompt.length / 4);
|
|
949
|
-
const routingSavings = Math.floor(estimatedPromptTokens * 0.8); // conservative 80% cost reduction
|
|
950
|
-
savingsLedger.recordTokens('complexityRouting', routingSavings);
|
|
951
|
-
}
|
|
952
|
-
// Apply the Agent Multiplexer to local-verify the prompt in parallel
|
|
953
|
-
const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
|
|
954
|
-
// Resolve conversation session key and retrieve/create the fileHistory map
|
|
955
|
-
let fileHistory;
|
|
956
|
-
if (req.body.messages && req.body.messages.length > 0) {
|
|
957
|
-
try {
|
|
958
|
-
const firstMsg = req.body.messages[0];
|
|
959
|
-
const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
|
|
960
|
-
const sessionKey = crypto.createHash('sha1').update(content).digest('hex');
|
|
961
|
-
if (!this.sessions.has(sessionKey)) {
|
|
962
|
-
this.sessions.set(sessionKey, { fileHistory: new Map() });
|
|
963
|
-
}
|
|
964
|
-
fileHistory = this.sessions.get(sessionKey).fileHistory;
|
|
965
|
-
}
|
|
966
|
-
catch { }
|
|
967
|
-
}
|
|
968
|
-
// Compress long TS/JS codebase file blocks in the enriched prompt using the Context Squeezer AST Tree-Shaker!
|
|
969
|
-
// The query context is the user's primary prompt to ensure we keep relevant methods!
|
|
970
|
-
const squeezeResult = squeezePrompt(enrichedPrompt, prompt, fileHistory);
|
|
971
|
-
const squeezedPrompt = squeezeResult.squeezed;
|
|
972
|
-
const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
|
|
973
|
-
if (compressionSavings > 0) {
|
|
974
|
-
savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
|
|
975
|
-
console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
|
|
976
|
-
}
|
|
977
|
-
if (provider === 'openai' || provider === 'anthropic') {
|
|
978
|
-
if (req.body.messages && req.body.messages.length > 0) {
|
|
979
|
-
injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
|
|
980
|
-
// Apply conversational history pruning to save tokens on historical messages!
|
|
981
|
-
const pruneResult = pruneHistoryMessages(req.body.messages);
|
|
982
|
-
req.body.messages = pruneResult.messages;
|
|
983
|
-
historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
|
|
984
|
-
if (pruneResult.tokensSavedEstimate > 0) {
|
|
985
|
-
savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
else if (provider === 'gemini') {
|
|
990
|
-
if (req.body.contents && req.body.contents.length > 0) {
|
|
991
|
-
const lastContent = req.body.contents[req.body.contents.length - 1];
|
|
992
|
-
if (lastContent.parts && lastContent.parts.length > 0) {
|
|
993
|
-
lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
1008
|
+
// ── 1. Cache-First Hot Path ──
|
|
1009
|
+
// Perform exact cache check immediately (resolves in <1ms)
|
|
997
1010
|
const hit = cacheGet(provider, prompt);
|
|
998
1011
|
if (hit) {
|
|
999
|
-
const cacheTokensSaved = 2000
|
|
1012
|
+
const cacheTokensSaved = 2000;
|
|
1000
1013
|
savingsLedger.recordTokens('cache', cacheTokensSaved);
|
|
1001
1014
|
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, cacheTokensSaved);
|
|
1002
1015
|
const unmaskedData = semanticScrubber.unmask(hit.data, tokenMap);
|
|
1003
1016
|
if (isStream)
|
|
1004
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1017
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, 1.0);
|
|
1005
1018
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
1006
1019
|
res.setHeader('X-Lemma-Similarity', '1.000');
|
|
1007
1020
|
res.setHeader('X-Lemma-Tier', tier);
|
|
1008
1021
|
return res.json(unmaskedData);
|
|
1009
1022
|
}
|
|
1023
|
+
// Perform semantic cache check (resolves in ~15ms)
|
|
1010
1024
|
if (pro) {
|
|
1011
1025
|
const semHit = await semanticGet(provider, prompt);
|
|
1012
1026
|
if (semHit) {
|
|
1013
1027
|
if (semHit.similarity >= 0.90) {
|
|
1014
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1028
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
1015
1029
|
const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
|
|
1016
1030
|
if (isStream)
|
|
1017
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1031
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
|
|
1018
1032
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
1019
1033
|
res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
|
|
1020
1034
|
res.setHeader('X-Lemma-Tier', tier);
|
|
1021
1035
|
return res.json(unmaskedData);
|
|
1022
1036
|
}
|
|
1023
|
-
else if (semHit.similarity >= 0.
|
|
1037
|
+
else if (semHit.similarity >= 0.82) { // raised from 0.70 to 0.82 to prevent false positives
|
|
1024
1038
|
console.log(`💡 [CARS] Near Cache Hit (Similarity: ${(semHit.similarity * 100).toFixed(1)}%). Synthesizing answer locally via Ollama...`);
|
|
1025
1039
|
let cachedContent = '';
|
|
1026
1040
|
if (provider === 'openai') {
|
|
@@ -1058,10 +1072,10 @@ Adjusted Answer:`;
|
|
|
1058
1072
|
else if (provider === 'anthropic') {
|
|
1059
1073
|
synthesizedData.content[0].text = adjustedText;
|
|
1060
1074
|
}
|
|
1061
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1075
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
1062
1076
|
const unmaskedData = semanticScrubber.unmask(synthesizedData, tokenMap);
|
|
1063
1077
|
if (isStream) {
|
|
1064
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1078
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, semHit.similarity);
|
|
1065
1079
|
}
|
|
1066
1080
|
res.setHeader('X-Lemma-Cache', 'HIT-CARS');
|
|
1067
1081
|
res.setHeader('X-Lemma-Similarity', semHit.similarity.toFixed(3));
|
|
@@ -1078,22 +1092,86 @@ Adjusted Answer:`;
|
|
|
1078
1092
|
const cloudHit = await cloudSync.get(prompt);
|
|
1079
1093
|
if (cloudHit) {
|
|
1080
1094
|
cacheSet(provider, prompt, cloudHit.data);
|
|
1081
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000
|
|
1095
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, 2000);
|
|
1082
1096
|
const unmaskedData = semanticScrubber.unmask(cloudHit.data, tokenMap);
|
|
1083
1097
|
if (isStream)
|
|
1084
|
-
return simulateHitStream(res, unmaskedData, provider,
|
|
1098
|
+
return simulateHitStream(res, unmaskedData, provider, originalModel, tier, cloudHit.similarity || 0.95);
|
|
1085
1099
|
res.setHeader('X-Lemma-Cache', 'HIT');
|
|
1086
1100
|
res.setHeader('X-Lemma-Similarity', (cloudHit.similarity || 0.95).toFixed(3));
|
|
1087
1101
|
res.setHeader('X-Lemma-Tier', tier);
|
|
1088
1102
|
return res.json(unmaskedData);
|
|
1089
1103
|
}
|
|
1090
1104
|
}
|
|
1105
|
+
// ── 2. Cache Miss Path ──
|
|
1106
|
+
const routingDecision = complexityRouter.evaluate(prompt, originalModel);
|
|
1107
|
+
const model = routingDecision.model;
|
|
1108
|
+
if (req.body.model)
|
|
1109
|
+
req.body.model = model;
|
|
1110
|
+
// Record complexity routing savings when model is downgraded
|
|
1111
|
+
if (routingDecision.complexity === 'low' && model !== originalModel) {
|
|
1112
|
+
const estimatedPromptTokens = Math.floor(prompt.length / 4);
|
|
1113
|
+
const routingSavings = Math.floor(estimatedPromptTokens * 0.8);
|
|
1114
|
+
savingsLedger.recordTokens('complexityRouting', routingSavings);
|
|
1115
|
+
}
|
|
1116
|
+
// Apply the Agent Multiplexer to local-verify the prompt in parallel
|
|
1117
|
+
const enrichedPrompt = await agentMultiplexer.multiplexPrompt(prompt, model);
|
|
1118
|
+
// Resolve conversation session key and retrieve/create the fileHistory map
|
|
1119
|
+
let fileHistory;
|
|
1120
|
+
if (req.body.messages && req.body.messages.length > 0) {
|
|
1121
|
+
try {
|
|
1122
|
+
const firstMsg = req.body.messages[0];
|
|
1123
|
+
const content = typeof firstMsg.content === 'string' ? firstMsg.content : JSON.stringify(firstMsg.content);
|
|
1124
|
+
const sessionKey = crypto.createHash('sha1').update(content).digest('hex');
|
|
1125
|
+
if (!this.sessions.has(sessionKey)) {
|
|
1126
|
+
// Enforce 500 active sessions cap to prevent memory leak (FIFO eviction)
|
|
1127
|
+
const MAX_SESSIONS = 500;
|
|
1128
|
+
if (this.sessions.size >= MAX_SESSIONS) {
|
|
1129
|
+
const oldestKey = this.sessions.keys().next().value;
|
|
1130
|
+
if (oldestKey)
|
|
1131
|
+
this.sessions.delete(oldestKey);
|
|
1132
|
+
}
|
|
1133
|
+
this.sessions.set(sessionKey, { fileHistory: new Map() });
|
|
1134
|
+
}
|
|
1135
|
+
fileHistory = this.sessions.get(sessionKey).fileHistory;
|
|
1136
|
+
}
|
|
1137
|
+
catch { }
|
|
1138
|
+
}
|
|
1139
|
+
// Compress long TS/JS codebase file blocks in the enriched prompt
|
|
1140
|
+
const squeezeResult = squeezePrompt(enrichedPrompt, prompt, fileHistory);
|
|
1141
|
+
const squeezedPrompt = squeezeResult.squeezed;
|
|
1142
|
+
const compressionSavings = squeezeResult.originalSize - squeezeResult.squeezedSize;
|
|
1143
|
+
if (compressionSavings > 0) {
|
|
1144
|
+
savingsLedger.record('contextSqueeze', squeezeResult.originalSize, squeezeResult.squeezedSize);
|
|
1145
|
+
console.log(`⚡ [Squeezer] Compressed prompt from ${squeezeResult.originalSize} to ${squeezeResult.squeezedSize} chars (Saved ${(squeezeResult.compressionRatio * 100).toFixed(1)}% context tokens!)`);
|
|
1146
|
+
}
|
|
1147
|
+
if (provider === 'openai' || provider === 'anthropic') {
|
|
1148
|
+
if (req.body.messages && req.body.messages.length > 0) {
|
|
1149
|
+
injectSqueezedContent(req.body.messages[req.body.messages.length - 1], squeezedPrompt);
|
|
1150
|
+
// Apply conversational history pruning
|
|
1151
|
+
const pruneResult = pruneHistoryMessages(req.body.messages);
|
|
1152
|
+
req.body.messages = pruneResult.messages;
|
|
1153
|
+
historyTokensSaved = pruneResult.tokensSavedEstimate + Math.floor(compressionSavings / 4);
|
|
1154
|
+
if (pruneResult.tokensSavedEstimate > 0) {
|
|
1155
|
+
savingsLedger.recordTokens('historyPrune', pruneResult.tokensSavedEstimate);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
else if (provider === 'gemini') {
|
|
1160
|
+
if (req.body.contents && req.body.contents.length > 0) {
|
|
1161
|
+
const lastContent = req.body.contents[req.body.contents.length - 1];
|
|
1162
|
+
if (lastContent.parts && lastContent.parts.length > 0) {
|
|
1163
|
+
lastContent.parts[lastContent.parts.length - 1].text = squeezedPrompt;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1091
1167
|
if (isStream) {
|
|
1092
1168
|
await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
|
|
1093
1169
|
if (!pro) {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1170
|
+
await usageWriteQueue.enqueue(async () => {
|
|
1171
|
+
const u = await loadUsage();
|
|
1172
|
+
u.count++;
|
|
1173
|
+
await writeJson(USAGE_FILE, u);
|
|
1174
|
+
});
|
|
1097
1175
|
}
|
|
1098
1176
|
return relayMissStream(res, req.body, provider, tier);
|
|
1099
1177
|
}
|
|
@@ -1105,9 +1183,11 @@ Adjusted Answer:`;
|
|
|
1105
1183
|
await cloudSync.set(prompt, data);
|
|
1106
1184
|
}
|
|
1107
1185
|
if (!pro) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1186
|
+
await usageWriteQueue.enqueue(async () => {
|
|
1187
|
+
const u = await loadUsage();
|
|
1188
|
+
u.count++;
|
|
1189
|
+
await writeJson(USAGE_FILE, u);
|
|
1190
|
+
});
|
|
1111
1191
|
}
|
|
1112
1192
|
await recordStat(this.stats, this.projectName, false, Date.now() - t0, provider, historyTokensSaved);
|
|
1113
1193
|
return res.json(semanticScrubber.unmask(data, tokenMap));
|
|
@@ -1144,6 +1224,15 @@ Adjusted Answer:`;
|
|
|
1144
1224
|
}
|
|
1145
1225
|
async start() {
|
|
1146
1226
|
this.stats = await loadStats();
|
|
1227
|
+
// Start active cache eviction sweep every hour
|
|
1228
|
+
setInterval(() => {
|
|
1229
|
+
const now = Date.now();
|
|
1230
|
+
for (const [k, e] of CACHE.entries()) {
|
|
1231
|
+
if (e.ttl > 0 && now - e.createdAt > e.ttl) {
|
|
1232
|
+
CACHE.delete(k);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}, 3600000).unref();
|
|
1147
1236
|
const server = http.createServer(this.app);
|
|
1148
1237
|
server.on('error', (e) => {
|
|
1149
1238
|
if (e.code === 'EADDRINUSE') {
|