@nxuss/lemma 1.4.0 → 1.6.0
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 +75 -7
- package/bin/init.js +1 -1
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +50 -6
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +23 -0
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/mcp/prompts.d.ts +2 -2
- package/dist/cjs/mcp/prompts.d.ts.map +1 -1
- package/dist/cjs/mcp/prompts.js +10 -5
- package/dist/cjs/mcp/prompts.js.map +1 -1
- package/dist/cjs/mcp/tools.d.ts +2 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +111 -5
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/utils/CommandOutputDistiller.d.ts +75 -0
- package/dist/cjs/utils/CommandOutputDistiller.d.ts.map +1 -0
- package/dist/cjs/utils/CommandOutputDistiller.js +598 -0
- package/dist/cjs/utils/CommandOutputDistiller.js.map +1 -0
- package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.js +5 -3
- package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
- package/dist/cjs/utils/SavingsLedger.d.ts +38 -1
- package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -1
- package/dist/cjs/utils/SavingsLedger.js +89 -13
- package/dist/cjs/utils/SavingsLedger.js.map +1 -1
- package/dist/cjs/utils/reportSavings.d.ts +15 -0
- package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
- package/dist/cjs/utils/reportSavings.js +13 -1
- package/dist/cjs/utils/reportSavings.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +50 -6
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +24 -1
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/prompts.d.ts +2 -2
- package/dist/esm/mcp/prompts.d.ts.map +1 -1
- package/dist/esm/mcp/prompts.js +10 -5
- package/dist/esm/mcp/prompts.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts +2 -0
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +112 -7
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/utils/CommandOutputDistiller.d.ts +75 -0
- package/dist/esm/utils/CommandOutputDistiller.d.ts.map +1 -0
- package/dist/esm/utils/CommandOutputDistiller.js +588 -0
- package/dist/esm/utils/CommandOutputDistiller.js.map +1 -0
- package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/esm/utils/ContextSqueezer.js +5 -3
- package/dist/esm/utils/ContextSqueezer.js.map +1 -1
- package/dist/esm/utils/SavingsLedger.d.ts +38 -1
- package/dist/esm/utils/SavingsLedger.d.ts.map +1 -1
- package/dist/esm/utils/SavingsLedger.js +89 -13
- package/dist/esm/utils/SavingsLedger.js.map +1 -1
- package/dist/esm/utils/reportSavings.d.ts +15 -0
- package/dist/esm/utils/reportSavings.d.ts.map +1 -1
- package/dist/esm/utils/reportSavings.js +12 -1
- package/dist/esm/utils/reportSavings.js.map +1 -1
- package/package.json +1 -1
|
@@ -580,6 +580,31 @@ async function loadUsage() {
|
|
|
580
580
|
async function loadStats() {
|
|
581
581
|
return readJson(STATS_FILE, {});
|
|
582
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Tokens a cache hit actually avoided regenerating, measured from the cached payload
|
|
585
|
+
* that was served instead of an LLM call. Replaces the old hardcoded 2000 guess.
|
|
586
|
+
*/
|
|
587
|
+
function measureCachedTokens(data) {
|
|
588
|
+
try {
|
|
589
|
+
const chars = JSON.stringify(data)?.length;
|
|
590
|
+
if (!chars)
|
|
591
|
+
return undefined;
|
|
592
|
+
return Math.floor(chars / 4);
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* @param tokensSaved Measured tokens saved. When omitted for a cache hit, stats.json
|
|
600
|
+
* still falls back to a 2000-token placeholder for dashboard continuity, but that
|
|
601
|
+
* guess is deliberately NOT written to the savings ledger — the ledger only accepts
|
|
602
|
+
* measured values, otherwise its net position is built on invented numbers.
|
|
603
|
+
*
|
|
604
|
+
* Note: a cache *miss* is not charged to the cost ledger. The lookup is local (no LLM
|
|
605
|
+
* round-trip), so a miss costs latency, not tokens. Charging it a fabricated token
|
|
606
|
+
* figure would repeat exactly the error the cost ledger exists to correct.
|
|
607
|
+
*/
|
|
583
608
|
async function recordStat(stats, project, fromCache, latencyMs, provider, tokensSaved) {
|
|
584
609
|
if (!stats[project]) {
|
|
585
610
|
stats[project] = { total: 0, hits: 0, misses: 0, totalLatency: 0, totalTokensSaved: 0, providers: {} };
|
|
@@ -593,9 +618,11 @@ async function recordStat(stats, project, fromCache, latencyMs, provider, tokens
|
|
|
593
618
|
if (!s.providers[provider])
|
|
594
619
|
s.providers[provider] = { hits: 0, misses: 0 };
|
|
595
620
|
fromCache ? s.providers[provider].hits++ : s.providers[provider].misses++;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
621
|
+
// Only measured savings reach the ledger. `tokensSaved === undefined` means the
|
|
622
|
+
// caller never counted, so tkSaved is the 2000 placeholder — usable for the
|
|
623
|
+
// dashboard, not for a net-position claim.
|
|
624
|
+
if (fromCache && typeof tokensSaved === 'number' && tokensSaved > 0) {
|
|
625
|
+
savingsLedger.recordTokens('cache', tokensSaved);
|
|
599
626
|
}
|
|
600
627
|
logEvent({
|
|
601
628
|
type: fromCache ? 'cache:hit' : 'cache:miss',
|
|
@@ -1501,6 +1528,23 @@ class LemmaServer {
|
|
|
1501
1528
|
}
|
|
1502
1529
|
res.json({ ok: true, snapshot: savingsLedger.getSnapshot().total });
|
|
1503
1530
|
});
|
|
1531
|
+
// Cost Record Endpoint — the counterpart to /api/savings-record. Without it the
|
|
1532
|
+
// ledger only ever counts wins, which makes its totals unfalsifiable.
|
|
1533
|
+
this.app.post('/api/cost-record', (req, res) => {
|
|
1534
|
+
const { source, tokens, chars } = req.body || {};
|
|
1535
|
+
const validSources = ['mcpInstructions', 'toolResult', 'cacheMiss'];
|
|
1536
|
+
if (!source || !validSources.includes(source)) {
|
|
1537
|
+
return res.status(400).json({ error: 'Invalid cost source' });
|
|
1538
|
+
}
|
|
1539
|
+
if (typeof tokens === 'number' && tokens > 0) {
|
|
1540
|
+
savingsLedger.recordCost(source, tokens);
|
|
1541
|
+
}
|
|
1542
|
+
else if (typeof chars === 'number' && chars > 0) {
|
|
1543
|
+
savingsLedger.recordCostChars(source, chars);
|
|
1544
|
+
}
|
|
1545
|
+
const snap = savingsLedger.getSnapshot();
|
|
1546
|
+
res.json({ ok: true, totalCost: snap.totalCost, net: snap.net });
|
|
1547
|
+
});
|
|
1504
1548
|
// MCP Tool Event Endpoint — called by MCP server when tools execute
|
|
1505
1549
|
this.app.post('/api/mcp-event', (req, res) => {
|
|
1506
1550
|
try {
|
|
@@ -1726,7 +1770,7 @@ class LemmaServer {
|
|
|
1726
1770
|
const semHit = await semanticGet(provider, prompt, this.projectName);
|
|
1727
1771
|
if (semHit) {
|
|
1728
1772
|
if (semHit.similarity >= 0.90) {
|
|
1729
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1773
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(semHit.data));
|
|
1730
1774
|
const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
|
|
1731
1775
|
const cacheHeader = semHit.hiveMind ? 'HIT-HIVE-MIND' : 'HIT';
|
|
1732
1776
|
if (isStream)
|
|
@@ -1777,7 +1821,7 @@ Adjusted Answer:`;
|
|
|
1777
1821
|
else if (provider === 'anthropic') {
|
|
1778
1822
|
synthesizedData.content[0].text = adjustedText;
|
|
1779
1823
|
}
|
|
1780
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1824
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(synthesizedData));
|
|
1781
1825
|
const unmaskedData = semanticScrubber.unmask(synthesizedData, tokenMap);
|
|
1782
1826
|
if (isStream) {
|
|
1783
1827
|
return SseRelay.simulateHit(res, unmaskedData, { provider: provider, tier, similarity: semHit.similarity });
|
|
@@ -1797,7 +1841,7 @@ Adjusted Answer:`;
|
|
|
1797
1841
|
const cloudHit = await cloudSync.get(prompt);
|
|
1798
1842
|
if (cloudHit) {
|
|
1799
1843
|
cacheSet(provider, prompt, cloudHit.data);
|
|
1800
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1844
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(cloudHit.data));
|
|
1801
1845
|
const unmaskedData = semanticScrubber.unmask(cloudHit.data, tokenMap);
|
|
1802
1846
|
if (isStream)
|
|
1803
1847
|
return SseRelay.simulateHit(res, unmaskedData, { provider: provider, tier, similarity: cloudHit.similarity || 0.95 });
|