troxy-cli 1.30.9 → 1.30.10
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.30.
|
|
3
|
+
"version": "1.30.10",
|
|
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": {
|
|
@@ -56,7 +56,7 @@ async function failClosed($, e, next, err) {
|
|
|
56
56
|
// to every step of its turn, so the cache prefix stays stable within a turn.
|
|
57
57
|
|
|
58
58
|
const MODELS = {
|
|
59
|
-
haiku: 'claude-haiku-
|
|
59
|
+
haiku: 'claude-haiku-5-5',
|
|
60
60
|
sonnet: 'claude-sonnet-5-5',
|
|
61
61
|
opus: 'claude-opus-5-5',
|
|
62
62
|
};
|
|
@@ -67,6 +67,10 @@ const RANK = { haiku: 0, sonnet: 1, opus: 2 };
|
|
|
67
67
|
// pick flipped between Sonnet and Opus. Sonnet is the default; only a clearly hard
|
|
68
68
|
// prompt (above 0.85) keeps the requested model.
|
|
69
69
|
const BANDS = [[0.85, 'sonnet']];
|
|
70
|
+
// Haiku band, off unless switched on (config haikuBand, or TROXY_HAIKU_BAND=1 for an A/B run):
|
|
71
|
+
// an easy prompt from a Sonnet session goes to Haiku 5.5. The 10-15 rejection was Haiku 4.5.
|
|
72
|
+
// Sonnet sessions only; Opus keeps the bands above.
|
|
73
|
+
const HAIKU_BANDS = [[0.2, 'haiku']];
|
|
70
74
|
const ROUTE_CONTEXT_FLOOR_TOKENS = 50000;
|
|
71
75
|
const SCORE_TIMEOUT_MS = 3000;
|
|
72
76
|
const SCORER_PROMPT_CHARS = 4000;
|
|
@@ -104,7 +108,7 @@ function asRequest(text) {
|
|
|
104
108
|
return `<request>\n${String(text || '').replace(/<\/request>/gi, '</ request>')}\n</request>`;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
function pickModel(score, requested, contextTokens, warm) {
|
|
111
|
+
function pickModel(score, requested, contextTokens, warm, haiku = false) {
|
|
108
112
|
const rq = tierOf(requested);
|
|
109
113
|
if (rq === null) return requested; // unknown family (e.g. a newer tier): leave it alone
|
|
110
114
|
// No score (scorer failed or echoed the prompt): stay on the warm model at any
|
|
@@ -114,7 +118,7 @@ function pickModel(score, requested, contextTokens, warm) {
|
|
|
114
118
|
return w !== null && RANK[w] <= RANK[rq] ? warm : requested;
|
|
115
119
|
}
|
|
116
120
|
let tier = rq;
|
|
117
|
-
for (const [limit, name] of BANDS) {
|
|
121
|
+
for (const [limit, name] of (haiku && rq === 'sonnet' ? HAIKU_BANDS : BANDS)) {
|
|
118
122
|
if (score <= limit) { if (RANK[name] < RANK[rq]) tier = name; break; }
|
|
119
123
|
}
|
|
120
124
|
// The cache is per model: the first prompt on a new model re-writes the whole
|
|
@@ -522,9 +526,9 @@ async function rememberAsked($, sid, want, ran) {
|
|
|
522
526
|
// compaction) the cache is gone anyway, so pick by score. Otherwise hold the model the
|
|
523
527
|
// cache is warm on: every switch re-writes the whole conversation (2026-10-16 A/B: 4-6
|
|
524
528
|
// switches per session cost more bar than routing saved). /model always wins.
|
|
525
|
-
function stepModel({ arm, requested, switched, cold, score, warm }) {
|
|
529
|
+
function stepModel({ arm, requested, switched, cold, score, warm, haiku = false }) {
|
|
526
530
|
if (arm !== 'treated' || switched) return requested;
|
|
527
|
-
if (cold) return pickModel(score, requested, 0, null);
|
|
531
|
+
if (cold) return pickModel(score, requested, 0, null, haiku);
|
|
528
532
|
const w = tierOf(warm), rq = tierOf(requested);
|
|
529
533
|
return w !== null && rq !== null && RANK[w] <= RANK[rq] ? warm : requested;
|
|
530
534
|
}
|
|
@@ -547,6 +551,11 @@ async function effortSwapOn($) {
|
|
|
547
551
|
return Boolean((await troxyConfig($)).effortSwap);
|
|
548
552
|
}
|
|
549
553
|
|
|
554
|
+
async function haikuBandOn($) {
|
|
555
|
+
if ((await $.env.get('TROXY_HAIKU_BAND')) === '1') return true;
|
|
556
|
+
return Boolean((await troxyConfig($)).haikuBand);
|
|
557
|
+
}
|
|
558
|
+
|
|
550
559
|
async function* routeStep($, e, next) {
|
|
551
560
|
// Main loop only: a subagent keeps whatever model it was given.
|
|
552
561
|
if (e.agentId) {
|
|
@@ -575,6 +584,7 @@ async function* routeStep($, e, next) {
|
|
|
575
584
|
arm, requested: want, switched,
|
|
576
585
|
cold: isCold,
|
|
577
586
|
score: decision ? decision.score : null, warm: ls && ls.model,
|
|
587
|
+
haiku: await haikuBandOn($).catch(() => false),
|
|
578
588
|
});
|
|
579
589
|
coldNext.delete(sid);
|
|
580
590
|
lastSent.set(sid, { model, at: now });
|
|
@@ -90,3 +90,13 @@ test('estimateTokens: counts the prompt being submitted, not just the context so
|
|
|
90
90
|
// a first-prompt of ~60k tokens must trip the floor
|
|
91
91
|
assert.equal(pickModel(0.05, MODELS.opus, estimateTokens(0, 'x'.repeat(240000))), MODELS.opus);
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
test('pickModel: haiku band (flag) sends an easy Sonnet prompt to Haiku 5.5, nothing else changes', () => {
|
|
95
|
+
assert.equal(MODELS.haiku, 'claude-haiku-5-5');
|
|
96
|
+
assert.equal(pickModel(0.1, MODELS.sonnet, 1000, null, true), MODELS.haiku);
|
|
97
|
+
assert.equal(pickModel(0.2, MODELS.sonnet, 1000, null, true), MODELS.haiku);
|
|
98
|
+
assert.equal(pickModel(0.3, MODELS.sonnet, 1000, null, true), MODELS.sonnet);
|
|
99
|
+
assert.equal(pickModel(0.1, MODELS.sonnet, 1000, null, false), MODELS.sonnet); // off by default
|
|
100
|
+
assert.equal(pickModel(0.1, MODELS.opus, 1000, null, true), MODELS.sonnet); // Opus keeps its bands
|
|
101
|
+
assert.equal(pickModel(0.1, MODELS.sonnet, 60000, null, true), MODELS.sonnet); // 50k floor still holds
|
|
102
|
+
});
|