@alexeiled/pi-model-router 0.5.1 → 0.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/CHANGELOG.md +21 -0
- package/README.md +145 -24
- package/extensions/classifier.ts +119 -0
- package/extensions/commands.ts +76 -40
- package/extensions/config.ts +240 -146
- package/extensions/context.ts +91 -0
- package/extensions/index.ts +120 -132
- package/extensions/jev.ts +223 -0
- package/extensions/provider.ts +392 -178
- package/extensions/routing.ts +233 -436
- package/extensions/state.ts +74 -25
- package/extensions/types.ts +148 -52
- package/extensions/ui.ts +32 -34
- package/model-router.example.json +17 -10
- package/package.json +4 -4
package/extensions/commands.ts
CHANGED
|
@@ -6,12 +6,16 @@ import type {
|
|
|
6
6
|
import type { AutocompleteItem } from '@earendil-works/pi-tui';
|
|
7
7
|
import {
|
|
8
8
|
getUnsupportedTiers,
|
|
9
|
+
isRouterPinValue,
|
|
10
|
+
isRouterTier,
|
|
11
|
+
isThinkingLevel,
|
|
9
12
|
parseCanonicalModelRef,
|
|
10
13
|
profileNames,
|
|
11
14
|
ROUTER_PIN_VALUES,
|
|
12
15
|
ROUTER_TIERS,
|
|
13
16
|
THINKING_LEVELS,
|
|
14
17
|
} from './config';
|
|
18
|
+
import { preservesRouteCoverage } from './routing';
|
|
15
19
|
import type {
|
|
16
20
|
RouterConfig,
|
|
17
21
|
RouterPinByProfile,
|
|
@@ -21,6 +25,7 @@ import type {
|
|
|
21
25
|
} from './types';
|
|
22
26
|
import {
|
|
23
27
|
formatDecision,
|
|
28
|
+
formatDecisionSource,
|
|
24
29
|
formatModelRef,
|
|
25
30
|
formatPinSummary,
|
|
26
31
|
formatThinkingSummary,
|
|
@@ -110,7 +115,7 @@ export const registerCommands = (
|
|
|
110
115
|
args: string[],
|
|
111
116
|
): AutocompleteItem[] | null => {
|
|
112
117
|
// thinking [tier] <level|auto>
|
|
113
|
-
const tierValues = [...ROUTER_TIERS];
|
|
118
|
+
const tierValues: RouterTier[] = [...ROUTER_TIERS];
|
|
114
119
|
const levelValues = ['auto', ...THINKING_LEVELS];
|
|
115
120
|
|
|
116
121
|
if (args.length <= 1) {
|
|
@@ -136,8 +141,8 @@ export const registerCommands = (
|
|
|
136
141
|
];
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
const tier = args[0];
|
|
145
|
+
if (isRouterTier(tier)) {
|
|
141
146
|
const levelPrefix = args[1] ?? '';
|
|
142
147
|
return levelValues
|
|
143
148
|
.filter((v) => v.startsWith(levelPrefix))
|
|
@@ -168,7 +173,6 @@ export const registerCommands = (
|
|
|
168
173
|
`Pins by profile: ${formatPinSummary(state.pinnedTierByProfile)}`,
|
|
169
174
|
`Thinking overrides: ${formatThinkingSummary(state.thinkingByProfile)}`,
|
|
170
175
|
`Widget: ${state.widgetEnabled ? 'on' : 'off'}`,
|
|
171
|
-
`Phase bias: ${state.currentConfig.phaseBias}`,
|
|
172
176
|
`Session cost: $${state.accumulatedCost.toFixed(4)}` +
|
|
173
177
|
(state.currentConfig.maxSessionBudget
|
|
174
178
|
? ` / $${state.currentConfig.maxSessionBudget.toFixed(2)}`
|
|
@@ -183,7 +187,9 @@ export const registerCommands = (
|
|
|
183
187
|
`Last routed tier: ${state.lastDecision.tier}`,
|
|
184
188
|
`Last phase: ${state.lastDecision.phase}`,
|
|
185
189
|
`Last model: ${state.lastDecision.targetProvider}/${state.lastDecision.targetModelId} (${state.lastDecision.thinking})`,
|
|
186
|
-
|
|
190
|
+
...(formatDecisionSource(state.lastDecision)
|
|
191
|
+
? [`Reason: ${formatDecisionSource(state.lastDecision)}`]
|
|
192
|
+
: []),
|
|
187
193
|
);
|
|
188
194
|
}
|
|
189
195
|
if (state.lastConfigWarnings && state.lastConfigWarnings.length > 0) {
|
|
@@ -233,7 +239,7 @@ export const registerCommands = (
|
|
|
233
239
|
[
|
|
234
240
|
`Profile: ${currentProfile}`,
|
|
235
241
|
`Pinned tier: ${state.pinnedTierByProfile[currentProfile] ?? 'auto'}`,
|
|
236
|
-
`Usage: /router pin <high|medium|low|auto>`,
|
|
242
|
+
`Usage: /router pin <high|medium|low|micro|auto>`,
|
|
237
243
|
].join('\n'),
|
|
238
244
|
'info',
|
|
239
245
|
);
|
|
@@ -242,13 +248,13 @@ export const registerCommands = (
|
|
|
242
248
|
}
|
|
243
249
|
|
|
244
250
|
if (args.length > 1) {
|
|
245
|
-
ctx.ui.notify('Usage: /router pin <high|medium|low|auto>', 'error');
|
|
251
|
+
ctx.ui.notify('Usage: /router pin <high|medium|low|micro|auto>', 'error');
|
|
246
252
|
return;
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
const pinValue = args[0];
|
|
250
256
|
|
|
251
|
-
if (!
|
|
257
|
+
if (!isRouterPinValue(pinValue)) {
|
|
252
258
|
ctx.ui.notify(
|
|
253
259
|
`Invalid router pin: ${pinValue}. Use one of: ${ROUTER_PIN_VALUES.join(', ')}`,
|
|
254
260
|
'error',
|
|
@@ -256,7 +262,8 @@ export const registerCommands = (
|
|
|
256
262
|
return;
|
|
257
263
|
}
|
|
258
264
|
|
|
259
|
-
const nextTier
|
|
265
|
+
const nextTier: RouterTier | undefined =
|
|
266
|
+
pinValue === 'auto' ? undefined : pinValue;
|
|
260
267
|
if (nextTier) {
|
|
261
268
|
state.pinnedTierByProfile[currentProfile] = nextTier;
|
|
262
269
|
} else {
|
|
@@ -267,7 +274,7 @@ export const registerCommands = (
|
|
|
267
274
|
ctx.ui.notify(
|
|
268
275
|
nextTier
|
|
269
276
|
? `Router pinned to ${nextTier}`
|
|
270
|
-
: `Router pin cleared;
|
|
277
|
+
: `Router pin cleared; baseline routing restored`,
|
|
271
278
|
'info',
|
|
272
279
|
);
|
|
273
280
|
};
|
|
@@ -303,28 +310,32 @@ export const registerCommands = (
|
|
|
303
310
|
let tier: RouterTier | 'all' | undefined;
|
|
304
311
|
let levelValue = '';
|
|
305
312
|
|
|
306
|
-
const tierValues = ['high', 'medium', 'low'];
|
|
307
313
|
const levelValues = ['auto', ...THINKING_LEVELS];
|
|
308
314
|
|
|
309
315
|
if (args.length === 1) {
|
|
310
|
-
|
|
316
|
+
const level = args[0];
|
|
317
|
+
if (!level) return;
|
|
318
|
+
levelValue = level;
|
|
311
319
|
tier = 'all';
|
|
312
320
|
} else if (args.length === 2) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
const requestedTier = args[0];
|
|
322
|
+
const requestedLevel = args[1];
|
|
323
|
+
if (!requestedTier || !requestedLevel) return;
|
|
324
|
+
if (isRouterTier(requestedTier) || requestedTier === 'all') {
|
|
325
|
+
tier = requestedTier === 'all' ? 'all' : requestedTier;
|
|
326
|
+
levelValue = requestedLevel;
|
|
316
327
|
} else {
|
|
317
328
|
ctx.ui.notify(
|
|
318
|
-
`Invalid tier: ${args[0]}. Use high, medium, or
|
|
329
|
+
`Invalid tier: ${args[0]}. Use high, medium, low, or micro.`,
|
|
319
330
|
'error',
|
|
320
331
|
);
|
|
321
332
|
return;
|
|
322
333
|
}
|
|
323
334
|
}
|
|
324
335
|
|
|
325
|
-
if (tier !== 'all' && !
|
|
336
|
+
if (tier !== 'all' && !tier) {
|
|
326
337
|
ctx.ui.notify(
|
|
327
|
-
`Invalid tier: ${tier}. Use high, medium, or
|
|
338
|
+
`Invalid tier: ${tier}. Use high, medium, low, or micro.`,
|
|
328
339
|
'error',
|
|
329
340
|
);
|
|
330
341
|
return;
|
|
@@ -338,14 +349,34 @@ export const registerCommands = (
|
|
|
338
349
|
}
|
|
339
350
|
|
|
340
351
|
const nextLevel =
|
|
341
|
-
levelValue === 'auto'
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
352
|
+
levelValue === 'auto'
|
|
353
|
+
? undefined
|
|
354
|
+
: isThinkingLevel(levelValue)
|
|
355
|
+
? levelValue
|
|
356
|
+
: undefined;
|
|
357
|
+
const overrides = { ...state.thinkingByProfile[currentProfile] };
|
|
358
|
+
const tiers = tier === 'all' ? ROUTER_TIERS : [tier];
|
|
345
359
|
for (const targetTier of tiers) {
|
|
346
360
|
if (nextLevel) overrides[targetTier] = nextLevel;
|
|
347
361
|
else delete overrides[targetTier];
|
|
348
362
|
}
|
|
363
|
+
const activeProfile = state.currentConfig.profiles[currentProfile];
|
|
364
|
+
if (
|
|
365
|
+
nextLevel &&
|
|
366
|
+
activeProfile &&
|
|
367
|
+
preservesRouteCoverage(
|
|
368
|
+
activeProfile,
|
|
369
|
+
(provider, id) => ctx.modelRegistry.find(provider, id),
|
|
370
|
+
overrides,
|
|
371
|
+
) === false
|
|
372
|
+
) {
|
|
373
|
+
ctx.ui.notify(
|
|
374
|
+
`Router thinking unchanged: '${nextLevel}' leaves no eligible route.`,
|
|
375
|
+
'warning',
|
|
376
|
+
);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
state.thinkingByProfile[currentProfile] = overrides;
|
|
349
380
|
if (Object.keys(overrides).length === 0) {
|
|
350
381
|
delete state.thinkingByProfile[currentProfile];
|
|
351
382
|
}
|
|
@@ -359,14 +390,13 @@ export const registerCommands = (
|
|
|
359
390
|
}
|
|
360
391
|
// Only warn when the level isn't supported by some tiers; skip for 'off' and 'auto'
|
|
361
392
|
if (nextLevel && nextLevel !== 'off') {
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
);
|
|
393
|
+
const activeProfile = state.currentConfig.profiles[currentProfile];
|
|
394
|
+
if (!activeProfile) return;
|
|
395
|
+
const unsupported = getUnsupportedTiers(activeProfile, nextLevel);
|
|
366
396
|
if (unsupported.length > 0) {
|
|
367
397
|
ctx.ui.notify(
|
|
368
398
|
`Router thinking (${tier}) set to ${nextLevel}. ` +
|
|
369
|
-
`${unsupported.join(', ')} tier${unsupported.length > 1 ? 's' : ''} may not support '${nextLevel}'.`,
|
|
399
|
+
`${unsupported.join(', ')} tier${unsupported.length > 1 ? 's' : ''} may not support '${nextLevel}' and will be skipped when unsupported.`,
|
|
370
400
|
'warning',
|
|
371
401
|
);
|
|
372
402
|
}
|
|
@@ -412,19 +442,19 @@ export const registerCommands = (
|
|
|
412
442
|
|
|
413
443
|
const handleFix = async (args: string[], ctx: ExtensionContext) => {
|
|
414
444
|
if (args.length !== 1) {
|
|
415
|
-
ctx.ui.notify('Usage: /router fix <high|medium|low>', 'error');
|
|
445
|
+
ctx.ui.notify('Usage: /router fix <high|medium|low|micro>', 'error');
|
|
416
446
|
return;
|
|
417
447
|
}
|
|
418
448
|
const tier = args[0]?.toLowerCase();
|
|
419
|
-
if (!
|
|
420
|
-
ctx.ui.notify('Usage: /router fix <high|medium|low>', 'error');
|
|
449
|
+
if (!isRouterTier(tier)) {
|
|
450
|
+
ctx.ui.notify('Usage: /router fix <high|medium|low|micro>', 'error');
|
|
421
451
|
return;
|
|
422
452
|
}
|
|
423
453
|
if (!state.lastDecision) {
|
|
424
454
|
ctx.ui.notify('No recent routing decision to fix.', 'warning');
|
|
425
455
|
return;
|
|
426
456
|
}
|
|
427
|
-
state.pinnedTierByProfile[state.lastDecision.profile] = tier
|
|
457
|
+
state.pinnedTierByProfile[state.lastDecision.profile] = tier;
|
|
428
458
|
actions.persistState();
|
|
429
459
|
actions.updateStatus(ctx);
|
|
430
460
|
ctx.ui.notify(
|
|
@@ -515,10 +545,12 @@ export const registerCommands = (
|
|
|
515
545
|
}
|
|
516
546
|
|
|
517
547
|
if (parts.length === 1 && !hasTrailingSpace) {
|
|
518
|
-
|
|
548
|
+
const subcommand = parts[0];
|
|
549
|
+
return subcommand ? getSubcommandCompletions(subcommand) : null;
|
|
519
550
|
}
|
|
520
551
|
|
|
521
552
|
const subcommand = parts[0];
|
|
553
|
+
if (!subcommand) return null;
|
|
522
554
|
const subArgs = parts.slice(1);
|
|
523
555
|
if (hasTrailingSpace && parts.length === 1) {
|
|
524
556
|
subArgs.push('');
|
|
@@ -558,13 +590,13 @@ export const registerCommands = (
|
|
|
558
590
|
}
|
|
559
591
|
case 'fix': {
|
|
560
592
|
const fixPrefix = subArgs[0] ?? '';
|
|
561
|
-
const items =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
593
|
+
const items = ROUTER_TIERS.filter((t) =>
|
|
594
|
+
t.startsWith(fixPrefix.toLowerCase()),
|
|
595
|
+
).map((t) => ({
|
|
596
|
+
value: `fix ${t}`,
|
|
597
|
+
label: t,
|
|
598
|
+
description: `Correct decision and pin to ${t} tier`,
|
|
599
|
+
}));
|
|
568
600
|
return items.length > 0 ? items : null;
|
|
569
601
|
}
|
|
570
602
|
case 'widget': {
|
|
@@ -597,6 +629,10 @@ export const registerCommands = (
|
|
|
597
629
|
const parts = args?.trim().split(/\s+/) ?? [];
|
|
598
630
|
const subcommand = parts[0];
|
|
599
631
|
const subArgs = parts.slice(1);
|
|
632
|
+
if (!subcommand) {
|
|
633
|
+
await handleStatus(subArgs, ctx);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
600
636
|
|
|
601
637
|
switch (subcommand) {
|
|
602
638
|
case 'profile':
|
|
@@ -637,7 +673,7 @@ export const registerCommands = (
|
|
|
637
673
|
'Router Subcommands:',
|
|
638
674
|
' status Show current status, profile, pin, cost, and last decision.',
|
|
639
675
|
' profile [name] Switch to a profile (enables router if off). Lists available if no name.',
|
|
640
|
-
' pin <tier|auto> Force a tier (high|medium|low) or set to auto.',
|
|
676
|
+
' pin <tier|auto> Force a tier (high|medium|low|micro) or set to auto.',
|
|
641
677
|
' thinking [tier] <level> Override thinking level (off|minimal|...|max|auto). Not all tier models may support every level.',
|
|
642
678
|
' disable Disable the router and restore the last used non-router model.',
|
|
643
679
|
' fix <tier> Correct the last routing decision and pin that tier for the current profile.',
|