troxy-cli 1.29.58 → 1.29.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.29.58",
3
+ "version": "1.29.60",
4
4
  "description": "A secure control layer for AI agents: policies across payments, messages, logins, destructive actions, model usage, and secrets, all enforceable from the CLI",
5
5
  "homepage": "https://troxy.io",
6
6
  "bugs": {
@@ -188,9 +188,9 @@ function sessionLine(t) {
188
188
  // by the server. undefined until something was saved.
189
189
  function savingsText(s) {
190
190
  const c = (s && s.compressed_tokens) || 0, t = (s && s.trimmed_tokens) || 0;
191
- const k = n => n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
191
+ const k = n => n >= 1e6 ? `${Math.round(n / 1e5) / 10}M`.replace('.0M', 'M') : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
192
192
  // Always show, even if 0, so the band is transparent: 0 means no trimming/compression on this turn
193
- return `${k(c + t)} tokens saved by compression and trimming`;
193
+ return `${k(c + t)} tokens not re-sent, thanks to compression and trimming`;
194
194
  }
195
195
 
196
196
  // This session's savings from the server, or null when there is no key, no session id or
@@ -252,6 +252,15 @@ function addTurn(totals, start, end, ratio) {
252
252
  return { ...t, paid: t.paid + paid, full: t.full + paid * ratio, meter }; // keeps t.kept
253
253
  }
254
254
 
255
+ // kept_usd from the server is the whole session's total, but paid only counts turns since
256
+ // the mod began tracking. Count only what arrived after the first reading (the first turn's
257
+ // own clearing is missed, so this undercounts slightly rather than inflating the multiplier).
258
+ function withKept(t, keptUsd) {
259
+ if (!(keptUsd >= 0)) return {};
260
+ const keptBase = t.keptBase === undefined ? keptUsd : t.keptBase;
261
+ return { keptBase, kept: Math.max(0, keptUsd - keptBase) };
262
+ }
263
+
255
264
  // Idle compaction: shrink a big session just before its prompt cache expires (1h), so
256
265
  // the summary reads the conversation from cache (~0.1x) and the user comes back to a
257
266
  // small session instead of re-sending the whole thing. Never while the plan meter is
@@ -541,9 +550,7 @@ export function register(on) {
541
550
  try {
542
551
  const { value } = await $.state.get(SAVED);
543
552
  const totals = addTurn(value, start, await snapshot($), ratio);
544
- // ponytail: kept_usd is the whole session's server total, paid only counts turns
545
- // since the mod started tracking; close enough once the session has a few turns.
546
- if (savings && savings.kept_usd >= 0) totals.kept = savings.kept_usd;
553
+ if (savings) Object.assign(totals, withKept(totals, savings.kept_usd));
547
554
  await $.state.set(SAVED, totals);
548
555
  const line = sessionLine(totals);
549
556
  if (line) await $.state.set(LINE, line);
@@ -557,4 +564,4 @@ export function register(on) {
557
564
  }
558
565
 
559
566
  // Exported for tests only - not part of the mod's runtime contract.
560
- export const _internal = { askedFor, rememberAsked, asked, armCompact, cancelCompact, toDomainAndFields, guardBash, failClosed, parseScore, asRequest, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn, statusText, compactPlan, savingsText, fetchSavings, scorePrompt, routeStep, stepModel, HOLD_PROMPTS };
567
+ export const _internal = { askedFor, rememberAsked, asked, armCompact, cancelCompact, toDomainAndFields, guardBash, failClosed, parseScore, asRequest, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn, withKept, statusText, compactPlan, savingsText, fetchSavings, scorePrompt, routeStep, stepModel, HOLD_PROMPTS };
@@ -92,6 +92,14 @@ test('addTurn: a session total from its own turns, a routed one counted at the a
92
92
  t = addTurn({ ...t, kept: 1.5 }, { cost: 2, meter: {} }, { cost: 2, meter: {} }, 1);
93
93
  assert.equal(t.kept, 1.5);
94
94
  assert.match(sessionLine(t), /your bar lasts 3\.0x longer · 5h: 3 instead of 9/);
95
+ // Server total of a session already 28 dollars deep: only growth after the first reading counts.
96
+ const { withKept } = _internal;
97
+ let k = { paid: 1, full: 1, meter: {} };
98
+ Object.assign(k, withKept(k, 28));
99
+ assert.deepEqual([k.keptBase, k.kept], [28, 0]);
100
+ Object.assign(k, withKept(k, 29.5));
101
+ assert.deepEqual([k.keptBase, k.kept], [28, 1.5]);
102
+ assert.deepEqual(withKept(k, undefined), {});
95
103
  // No routed turn yet: no band.
96
104
  assert.equal(sessionLine(addTurn(undefined, { cost: 0, meter: {} }, { cost: 1, meter: {} }, 1)), undefined);
97
105
  });
@@ -126,10 +134,12 @@ test('compactPlan: big idle sessions compact, small ones and a nearly full bar d
126
134
 
127
135
  test('savingsText: shows compression + trimming together, 0 when nothing was saved', () => {
128
136
  const { savingsText } = _internal;
129
- assert.equal(savingsText({ compressed_tokens: 5200, trimmed_tokens: 40100 }), '45k tokens saved by compression and trimming');
130
- assert.equal(savingsText({ compressed_tokens: 300, trimmed_tokens: 0 }), '300 tokens saved by compression and trimming');
131
- assert.equal(savingsText({ compressed_tokens: 0, trimmed_tokens: 0 }), '0 tokens saved by compression and trimming');
132
- assert.equal(savingsText(null), '0 tokens saved by compression and trimming');
137
+ assert.equal(savingsText({ compressed_tokens: 5200, trimmed_tokens: 40100 }), '45k tokens not re-sent, thanks to compression and trimming');
138
+ assert.equal(savingsText({ compressed_tokens: 300, trimmed_tokens: 0 }), '300 tokens not re-sent, thanks to compression and trimming');
139
+ assert.equal(savingsText({ compressed_tokens: 0, trimmed_tokens: 0 }), '0 tokens not re-sent, thanks to compression and trimming');
140
+ assert.equal(savingsText({ compressed_tokens: 123277, trimmed_tokens: 154512861 }), '154.6M tokens not re-sent, thanks to compression and trimming');
141
+ assert.equal(savingsText({ compressed_tokens: 0, trimmed_tokens: 2000000 }), '2M tokens not re-sent, thanks to compression and trimming');
142
+ assert.equal(savingsText(null), '0 tokens not re-sent, thanks to compression and trimming');
133
143
  });
134
144
 
135
145
  test('fetchSavings: sends the saved key and the session id; null without a key or on an error', async () => {