omniharness-cli 0.1.84 → 0.1.85
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/dist/config/omniRoute.js +21 -5
- package/dist/ui/modelWindows.js +51 -5
- package/dist/ui/terminalInterface.js +14 -3
- package/package.json +1 -1
package/dist/config/omniRoute.js
CHANGED
|
@@ -145,17 +145,27 @@ export class OmniRouteClient {
|
|
|
145
145
|
}
|
|
146
146
|
/** List every model id the gateway exposes, including `auto/*` virtual combos and individual providers. */
|
|
147
147
|
async listModels(signal) {
|
|
148
|
+
return (await this.listCatalog(signal)).map((entry) => entry.id);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The catalog with the context window each entry advertises. OmniRoute
|
|
152
|
+
* states `context_length` on providers' models, on `auto/*` engines and on
|
|
153
|
+
* combos alike; `max_input_tokens` stands in when an entry carries only that.
|
|
154
|
+
*/
|
|
155
|
+
async listCatalog(signal) {
|
|
148
156
|
const response = await this.requestWithRetry('/v1/models', { method: 'GET', signal });
|
|
149
157
|
const payload = await response.json();
|
|
150
158
|
const data = this.isRecord(payload) && Array.isArray(payload.data) ? payload.data : [];
|
|
151
|
-
const
|
|
159
|
+
const entries = [];
|
|
152
160
|
for (const entry of data) {
|
|
153
|
-
if (this.isRecord(entry)
|
|
154
|
-
|
|
161
|
+
if (!this.isRecord(entry) || typeof entry.id !== 'string' || entry.id.trim() === '')
|
|
162
|
+
continue;
|
|
163
|
+
const window = this.positive(entry.context_length) ?? this.positive(entry.max_input_tokens);
|
|
164
|
+
entries.push(window !== undefined ? { id: entry.id, contextLength: window } : { id: entry.id });
|
|
155
165
|
}
|
|
156
|
-
if (
|
|
166
|
+
if (entries.length === 0)
|
|
157
167
|
throw new OmniRouteError(response.status, 'invalid models response');
|
|
158
|
-
return
|
|
168
|
+
return entries;
|
|
159
169
|
}
|
|
160
170
|
/** Retry transient responses for idempotent metadata reads without touching chat/tool requests. */
|
|
161
171
|
async requestWithRetry(path, init) {
|
|
@@ -431,6 +441,9 @@ export class OmniRouteClient {
|
|
|
431
441
|
const provider = headers.get('x-omniroute-provider') ?? decision.provider;
|
|
432
442
|
if (provider)
|
|
433
443
|
fallback.activeProvider = provider;
|
|
444
|
+
const model = headers.get('x-omniroute-model');
|
|
445
|
+
if (model && model.trim() !== '')
|
|
446
|
+
fallback.model = model.trim();
|
|
434
447
|
if (decision.strategy)
|
|
435
448
|
fallback.strategy = decision.strategy;
|
|
436
449
|
if (decision.latencyMs !== undefined)
|
|
@@ -485,6 +498,9 @@ export class OmniRouteClient {
|
|
|
485
498
|
number(value) {
|
|
486
499
|
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
487
500
|
}
|
|
501
|
+
positive(value) {
|
|
502
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
503
|
+
}
|
|
488
504
|
safeParse(text) {
|
|
489
505
|
try {
|
|
490
506
|
return JSON.parse(text);
|
package/dist/ui/modelWindows.js
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* a model id (or the provider name OmniRoute reports) to a token budget;
|
|
7
7
|
* `contextMeter` turns "tokens in" into a fill fraction and a zone the UI
|
|
8
8
|
* colours (ok / warn / danger) using the playbook's 70 / 90 thresholds.
|
|
9
|
+
*
|
|
10
|
+
* The gateway's catalog is the first source: `/v1/models` states a
|
|
11
|
+
* `context_length` per entry, and `windowIndex` turns that into a lookup the
|
|
12
|
+
* meter consults before the substring table below. The table remains the
|
|
13
|
+
* answer for a model the catalog does not size, and when the catalog could
|
|
14
|
+
* not be read at all.
|
|
9
15
|
*/
|
|
10
16
|
/** Known windows keyed by a substring of the model / provider id (longest match wins). */
|
|
11
17
|
const WINDOWS = [
|
|
@@ -35,12 +41,52 @@ const WINDOWS = [
|
|
|
35
41
|
];
|
|
36
42
|
/** Fallback window when nothing matches — conservative so the meter warns early rather than late. */
|
|
37
43
|
export const DEFAULT_WINDOW = 128_000;
|
|
44
|
+
/**
|
|
45
|
+
* Build the lookup from a catalog. Each entry is keyed by its full id and,
|
|
46
|
+
* when the id carries a provider prefix, by the bare model name after it —
|
|
47
|
+
* `X-OmniRoute-Model` reports the upstream name (`claude-sonnet-4-6`) while
|
|
48
|
+
* the catalog lists it under a prefix (`cc/claude-sonnet-4-6`). The first
|
|
49
|
+
* entry to claim a bare name keeps it, so a `dual`-mode mirror never
|
|
50
|
+
* contradicts its primary.
|
|
51
|
+
*/
|
|
52
|
+
export function windowIndex(entries) {
|
|
53
|
+
const index = new Map();
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const tokens = entry.contextLength;
|
|
56
|
+
if (tokens === undefined || !Number.isFinite(tokens) || tokens <= 0)
|
|
57
|
+
continue;
|
|
58
|
+
const id = entry.id.trim().toLowerCase();
|
|
59
|
+
if (id === '')
|
|
60
|
+
continue;
|
|
61
|
+
if (!index.has(id))
|
|
62
|
+
index.set(id, tokens);
|
|
63
|
+
const slash = id.indexOf('/');
|
|
64
|
+
if (slash > 0 && slash < id.length - 1) {
|
|
65
|
+
const bare = id.slice(slash + 1);
|
|
66
|
+
if (!index.has(bare))
|
|
67
|
+
index.set(bare, tokens);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return index;
|
|
71
|
+
}
|
|
38
72
|
/**
|
|
39
73
|
* Resolve a context window for a model id and/or the provider OmniRoute
|
|
40
|
-
* reported for the turn.
|
|
41
|
-
*
|
|
74
|
+
* reported for the turn. A catalog `known` answers first, by the exact id and
|
|
75
|
+
* then by the bare name after a provider prefix. Otherwise matching is
|
|
76
|
+
* case-insensitive substring; the longest matching pattern wins so
|
|
77
|
+
* `gpt-4o-mini` beats `gpt-4o`.
|
|
42
78
|
*/
|
|
43
|
-
export function windowFor(modelId, provider) {
|
|
79
|
+
export function windowFor(modelId, provider, known) {
|
|
80
|
+
if (known && modelId) {
|
|
81
|
+
const id = modelId.trim().toLowerCase();
|
|
82
|
+
const exact = known.get(id);
|
|
83
|
+
if (exact !== undefined)
|
|
84
|
+
return exact;
|
|
85
|
+
const slash = id.indexOf('/');
|
|
86
|
+
const bare = slash > 0 ? known.get(id.slice(slash + 1)) : undefined;
|
|
87
|
+
if (bare !== undefined)
|
|
88
|
+
return bare;
|
|
89
|
+
}
|
|
44
90
|
const haystack = `${modelId ?? ''} ${provider ?? ''}`.toLowerCase();
|
|
45
91
|
let best;
|
|
46
92
|
let bestLen = 0;
|
|
@@ -53,8 +99,8 @@ export function windowFor(modelId, provider) {
|
|
|
53
99
|
return best ?? DEFAULT_WINDOW;
|
|
54
100
|
}
|
|
55
101
|
/** Build the meter. `used` below 0 clamps to 0; the fraction is capped at 1. */
|
|
56
|
-
export function contextMeter(used, modelId, provider) {
|
|
57
|
-
const window = windowFor(modelId, provider);
|
|
102
|
+
export function contextMeter(used, modelId, provider, known) {
|
|
103
|
+
const window = windowFor(modelId, provider, known);
|
|
58
104
|
const safeUsed = Math.max(0, used);
|
|
59
105
|
const fraction = Math.min(1, safeUsed / window);
|
|
60
106
|
const zone = fraction >= 0.9 ? 'danger' : fraction >= 0.7 ? 'warn' : 'ok';
|
|
@@ -12,7 +12,7 @@ import { capabilityLine, recentRows, shortenPath, twoColumn } from './home.js';
|
|
|
12
12
|
import { conversationWidth, overflowCount, sidebarMode, SIDEBAR_WIDTH, todoRows, usageRows, clip as clipRow } from './sidebar.js';
|
|
13
13
|
import { planViewport } from './viewport.js';
|
|
14
14
|
import { statusMarker, toolHead } from './toolrow.js';
|
|
15
|
-
import { contextMeter, meterBar } from './modelWindows.js';
|
|
15
|
+
import { contextMeter, meterBar, windowIndex } from './modelWindows.js';
|
|
16
16
|
import { BEL, SYNC_QUERY, isSyncOutputReply, osc9Notify, osc52Copy, shouldNudgeOnFinish, wrapSynchronizedOutput } from './termcaps.js';
|
|
17
17
|
import { KITTY_POP, KITTY_PUSH, KITTY_QUERY, isEncodedKey, isKittyQueryResponse, parseRawKey } from './keys.js';
|
|
18
18
|
import { ownVersion } from '../update.js';
|
|
@@ -271,6 +271,9 @@ export function TerminalInterface({ engine }) {
|
|
|
271
271
|
const [pickerItems, setPickerItems] = useState([]);
|
|
272
272
|
const [pickerIndex, setPickerIndex] = useState(0);
|
|
273
273
|
const [pickerError, setPickerError] = useState();
|
|
274
|
+
// Context windows the catalog states, keyed by model id. Empty until the
|
|
275
|
+
// catalog has been read; the meter falls back to its own table meanwhile.
|
|
276
|
+
const [windows, setWindows] = useState(() => new Map());
|
|
274
277
|
const [mode, setMode] = useState(engine.state.mode);
|
|
275
278
|
const [permMode, setPermMode] = useState(engine.state.permissionMode ?? 'ask');
|
|
276
279
|
const [approval, setApproval] = useState(null);
|
|
@@ -310,6 +313,10 @@ export function TerminalInterface({ engine }) {
|
|
|
310
313
|
if (alive)
|
|
311
314
|
setRecentSessions(found);
|
|
312
315
|
}).catch(() => { });
|
|
316
|
+
void Promise.resolve().then(() => engine.client.listCatalog()).then((catalog) => {
|
|
317
|
+
if (alive)
|
|
318
|
+
setWindows(windowIndex(catalog));
|
|
319
|
+
}).catch(() => { });
|
|
313
320
|
return () => { alive = false; };
|
|
314
321
|
}, []);
|
|
315
322
|
useEffect(() => {
|
|
@@ -434,7 +441,9 @@ export function TerminalInterface({ engine }) {
|
|
|
434
441
|
const loadPicker = async () => {
|
|
435
442
|
setPickerError(undefined);
|
|
436
443
|
try {
|
|
437
|
-
const [accountCombos,
|
|
444
|
+
const [accountCombos, catalog] = await Promise.all([engine.client.listCombos(), engine.client.listCatalog()]);
|
|
445
|
+
setWindows(windowIndex(catalog));
|
|
446
|
+
const modelIds = catalog.map((entry) => entry.id);
|
|
438
447
|
const items = [];
|
|
439
448
|
for (const combo of accountCombos) {
|
|
440
449
|
if (combo.name.trim() !== '' && !items.some((item) => item.id === combo.name)) {
|
|
@@ -984,7 +993,9 @@ export function TerminalInterface({ engine }) {
|
|
|
984
993
|
// The prompt tokens of the last completion, as the gateway counted them,
|
|
985
994
|
// are the size of the context the next turn will carry.
|
|
986
995
|
const contextTokens = metrics.usage?.contextTokens ?? 0;
|
|
987
|
-
|
|
996
|
+
// Sized to the model that answered, when the gateway named one: an `auto/*`
|
|
997
|
+
// engine or a combo can land anywhere, and the window is that model's.
|
|
998
|
+
const meter = contextMeter(contextTokens, metrics.fallback.model ?? engine.state.activeModel, metrics.fallback.activeProvider, windows);
|
|
988
999
|
const meterColor = meter.zone === 'danger' ? PALETTE.error : meter.zone === 'warn' ? PALETTE.warn : PALETTE.muted;
|
|
989
1000
|
const contextLabel = contextTokens > 0 ? `ctx ${meterBar(meter.fraction, 8)} ${Math.round(meter.fraction * 100)}%` : '';
|
|
990
1001
|
const compression = metrics.compression.inputTokens > 0 ? `${Math.round((1 - metrics.compression.ratio) * 100)}% ${metrics.compression.strategy.toUpperCase()}` : '';
|