troxy-cli 1.29.59 → 1.29.61
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.
|
|
3
|
+
"version": "1.29.61",
|
|
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
|
|
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
|
|
@@ -440,13 +440,17 @@ const held = new Map();
|
|
|
440
440
|
|
|
441
441
|
// Which model a step runs on. At a cold moment (first step, 55+ min idle, after a
|
|
442
442
|
// compaction) any model is free to pick. While warm, hold the model, except at the
|
|
443
|
-
// first step of a new prompt once it has held HOLD_PROMPTS prompts
|
|
444
|
-
//
|
|
443
|
+
// first step of a new prompt: up at once, down once it has held HOLD_PROMPTS prompts,
|
|
444
|
+
// re-picking by score. The user's own /model always wins and becomes the held model.
|
|
445
445
|
function stepModel({ arm, requested, switched, cold, score, warm, newPrompt, heldPrompts, tokens }) {
|
|
446
446
|
if (arm !== 'treated' || switched) return requested;
|
|
447
447
|
if (cold) return pickModel(score, requested, 0, null);
|
|
448
|
-
if (newPrompt && score !== null &&
|
|
449
|
-
|
|
448
|
+
if (newPrompt && score !== null && tokens <= WARM_SWITCH_MAX_TOKENS) {
|
|
449
|
+
const pick = pickModel(score, requested, 0, null);
|
|
450
|
+
// Up is immediate: a cheap model chosen for an easy first prompt must not keep
|
|
451
|
+
// running a harder one (the 2026-10-15 A/B: Haiku held a whole coding session and
|
|
452
|
+
// made 2-4x the calls). Down still waits HOLD_PROMPTS, since it re-writes the cache.
|
|
453
|
+
if (RANK[tierOf(pick)] > RANK[tierOf(warm)] || heldPrompts >= HOLD_PROMPTS) return pick;
|
|
450
454
|
}
|
|
451
455
|
const w = tierOf(warm), rq = tierOf(requested);
|
|
452
456
|
return w !== null && rq !== null && RANK[w] <= RANK[rq] ? warm : requested;
|
|
@@ -134,10 +134,12 @@ test('compactPlan: big idle sessions compact, small ones and a nearly full bar d
|
|
|
134
134
|
|
|
135
135
|
test('savingsText: shows compression + trimming together, 0 when nothing was saved', () => {
|
|
136
136
|
const { savingsText } = _internal;
|
|
137
|
-
assert.equal(savingsText({ compressed_tokens: 5200, trimmed_tokens: 40100 }), '45k tokens
|
|
138
|
-
assert.equal(savingsText({ compressed_tokens: 300, trimmed_tokens: 0 }), '300 tokens
|
|
139
|
-
assert.equal(savingsText({ compressed_tokens: 0, trimmed_tokens: 0 }), '0 tokens
|
|
140
|
-
assert.equal(savingsText(
|
|
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');
|
|
141
143
|
});
|
|
142
144
|
|
|
143
145
|
test('fetchSavings: sends the saved key and the session id; null without a key or on an error', async () => {
|
|
@@ -213,6 +215,18 @@ test('stepModel: warm switch at a new prompt only after the hold, and under the
|
|
|
213
215
|
assert.equal(stepModel({ ...base, arm: 'holdout', newPrompt: true, heldPrompts: HOLD_PROMPTS }), M.opus);
|
|
214
216
|
});
|
|
215
217
|
|
|
218
|
+
test('stepModel: a harder prompt upgrades a cheap warm session at once, a lighter one waits', () => {
|
|
219
|
+
const { stepModel, MODELS: M, HOLD_PROMPTS } = _internal;
|
|
220
|
+
const base = { arm: 'treated', requested: M.opus, switched: false, cold: false, warm: M.haiku, newPrompt: true, tokens: 5000 };
|
|
221
|
+
assert.equal(stepModel({ ...base, score: 0.9, heldPrompts: 0 }), M.opus); // hard prompt on haiku: up now
|
|
222
|
+
assert.equal(stepModel({ ...base, score: 0.5, heldPrompts: 0 }), M.sonnet);
|
|
223
|
+
assert.equal(stepModel({ ...base, score: 0.1, heldPrompts: 0 }), M.haiku); // still easy: stay
|
|
224
|
+
assert.equal(stepModel({ ...base, warm: M.opus, score: 0.1, heldPrompts: 0 }), M.opus); // down waits
|
|
225
|
+
assert.equal(stepModel({ ...base, warm: M.opus, score: 0.1, heldPrompts: HOLD_PROMPTS }), M.haiku); // ...for the hold
|
|
226
|
+
assert.equal(stepModel({ ...base, score: 0.9, heldPrompts: 0, tokens: 300000 }), M.haiku); // too big to re-write
|
|
227
|
+
assert.equal(stepModel({ ...base, score: 0.9, heldPrompts: 0, newPrompt: false }), M.haiku); // mid-turn: hold
|
|
228
|
+
});
|
|
229
|
+
|
|
216
230
|
test('pickModel: past the floor, stay on the warm model unless the prompt needs a stronger one', () => {
|
|
217
231
|
const { pickModel, MODELS } = _internal;
|
|
218
232
|
const big = 80000;
|