@alexeiled/pi-model-router 0.6.0 → 0.6.1
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 +7 -0
- package/README.md +9 -2
- package/extensions/commands.ts +3 -0
- package/extensions/provider.ts +111 -83
- package/extensions/state.ts +3 -1
- package/extensions/types.ts +12 -0
- package/extensions/ui.ts +49 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1 — 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Add compact human-readable route provenance to the Pi footer, widget and `/router status`: `🧭 Jev ✓` means Jev selected the route, `🧭 Jev ↪ base` means Jev ran but the local baseline was used, and no marker means Jev was not involved.
|
|
6
|
+
- Track closed route-guidance outcomes across Jev, classifier, bypass and baseline paths, including reusable tool continuations and explicit generation fallbacks without persisting remote text or credentials.
|
|
7
|
+
- Keep the normal footer uncluttered for turns without external route guidance; retain latency only as a short widget/status diagnostic.
|
|
8
|
+
- Add regression coverage for UI rendering, persistence compatibility, advisor fallback, continuation inheritance and privacy boundaries.
|
|
9
|
+
|
|
3
10
|
## 0.6.0 — 2026-09-21
|
|
4
11
|
|
|
5
12
|
- Add the optional `micro` tier with `off` thinking by default; all four tiers are configured model/effort choices, not security permissions. Existing three-tier and partial profiles remain supported.
|
package/README.md
CHANGED
|
@@ -244,6 +244,13 @@ request text, raw response or remote explanations. Older saved explanations are
|
|
|
244
244
|
discarded as non-rendered `legacy` metadata; Pi's own conversation transcript is
|
|
245
245
|
separate from router state.
|
|
246
246
|
|
|
247
|
+
**Footer and widget:** The footer stays in its normal route-only form when Jev is
|
|
248
|
+
not involved. When Jev selects the route it adds `· 🧭 Jev ✓`; when Jev is used
|
|
249
|
+
but its advice is rejected, it adds `· 🧭 Jev ↪ base`. `base` means the local
|
|
250
|
+
deterministic baseline; the selected tier and model remain visible in the route
|
|
251
|
+
text. `/router widget on` and `/router status` show the same marker and a short
|
|
252
|
+
latency value. No marker means no external route guidance was used.
|
|
253
|
+
|
|
247
254
|
For chezmoi, use a **private template**, for example
|
|
248
255
|
`private_model-router.json.tmpl` under your agent-directory source path. Render
|
|
249
256
|
only the `apiKey` value using a reference such as
|
|
@@ -268,11 +275,11 @@ keeps Jev disabled.
|
|
|
268
275
|
| `/router thinking <tier> <level>` | Override thinking level for a specific tier (e.g. `/router thinking low off`). |
|
|
269
276
|
| `/router disable` | Disable the router and switch back to the last non-router model. |
|
|
270
277
|
| `/router widget <on\|off>` | Toggle the persistent state widget (supports `toggle`). |
|
|
271
|
-
| `/router debug <on\|off>` | Toggle
|
|
278
|
+
| `/router debug <on\|off>` | Toggle router debug state; use `show` or `clear` for local decision history. |
|
|
272
279
|
| `/router reload` | Hot-reload the configuration JSON. |
|
|
273
280
|
| `/router help` | Show usage help for all subcommands. |
|
|
274
281
|
|
|
275
282
|
## Documentation
|
|
276
283
|
|
|
277
284
|
- [Architecture Guide](docs/ARCHITECTURE.md): Deep dive into the routing logic and modular design.
|
|
278
|
-
- [Sample Configuration](model-router.example.json):
|
|
285
|
+
- [Sample Configuration](model-router.example.json): Profile examples (`auto`, `cheap`, `deep`, `anthropic`).
|
package/extensions/commands.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
RoutingDecision,
|
|
25
25
|
} from './types';
|
|
26
26
|
import {
|
|
27
|
+
formatAdvisorDetail,
|
|
27
28
|
formatDecision,
|
|
28
29
|
formatDecisionSource,
|
|
29
30
|
formatModelRef,
|
|
@@ -183,6 +184,7 @@ export const registerCommands = (
|
|
|
183
184
|
`Debug history: ${state.debugHistory.length} decisions`,
|
|
184
185
|
];
|
|
185
186
|
if (state.lastDecision) {
|
|
187
|
+
const advisorDetail = formatAdvisorDetail(state.lastDecision);
|
|
186
188
|
lines.push(
|
|
187
189
|
`Last routed tier: ${state.lastDecision.tier}`,
|
|
188
190
|
`Last phase: ${state.lastDecision.phase}`,
|
|
@@ -190,6 +192,7 @@ export const registerCommands = (
|
|
|
190
192
|
...(formatDecisionSource(state.lastDecision)
|
|
191
193
|
? [`Reason: ${formatDecisionSource(state.lastDecision)}`]
|
|
192
194
|
: []),
|
|
195
|
+
...(advisorDetail ? [advisorDetail] : []),
|
|
193
196
|
);
|
|
194
197
|
}
|
|
195
198
|
if (state.lastConfigWarnings && state.lastConfigWarnings.length > 0) {
|
package/extensions/provider.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
selectBaselineRoute,
|
|
40
40
|
} from './routing';
|
|
41
41
|
import type {
|
|
42
|
+
AdvisorOutcome,
|
|
42
43
|
RouterConfig,
|
|
43
44
|
RouterPinByProfile,
|
|
44
45
|
RouterThinkingByProfile,
|
|
@@ -279,7 +280,7 @@ export const registerRouterProvider = (
|
|
|
279
280
|
// Streams can complete out of order. Keep a small turn-keyed history rather
|
|
280
281
|
// than letting the latest stream replace another stream's continuation.
|
|
281
282
|
const continuations = new Map<string, ContinuationRecord>();
|
|
282
|
-
const advisedTurns = new Map<string,
|
|
283
|
+
const advisedTurns = new Map<string, AdvisorOutcome>();
|
|
283
284
|
const rememberContinuation = (record: ContinuationRecord) => {
|
|
284
285
|
continuations.delete(record.turn);
|
|
285
286
|
continuations.set(record.turn, record);
|
|
@@ -289,9 +290,9 @@ export const registerRouterProvider = (
|
|
|
289
290
|
continuations.delete(oldest);
|
|
290
291
|
}
|
|
291
292
|
};
|
|
292
|
-
const rememberAdvisedTurn = (turn: string) => {
|
|
293
|
+
const rememberAdvisedTurn = (turn: string, outcome: AdvisorOutcome) => {
|
|
293
294
|
advisedTurns.delete(turn);
|
|
294
|
-
advisedTurns.set(turn,
|
|
295
|
+
advisedTurns.set(turn, outcome);
|
|
295
296
|
while (advisedTurns.size > 16) {
|
|
296
297
|
const oldest = advisedTurns.keys().next().value;
|
|
297
298
|
if (oldest === undefined) break;
|
|
@@ -391,6 +392,14 @@ export const registerRouterProvider = (
|
|
|
391
392
|
]);
|
|
392
393
|
const toolContinuation =
|
|
393
394
|
context.messages.at(-1)?.role === 'toolResult';
|
|
395
|
+
const jev = state.currentConfig.jev;
|
|
396
|
+
const useJev = Boolean(
|
|
397
|
+
jev?.enabled &&
|
|
398
|
+
profile.jev?.enabled &&
|
|
399
|
+
jev.apiKey.trim().length > 0,
|
|
400
|
+
);
|
|
401
|
+
const advisorConfigured =
|
|
402
|
+
useJev || Boolean(state.currentConfig.classifierModel);
|
|
394
403
|
const continuationRecord =
|
|
395
404
|
toolContinuation && turn ? continuations.get(turn) : undefined;
|
|
396
405
|
const continuationDecision = continuationRecord?.decision;
|
|
@@ -459,6 +468,11 @@ export const registerRouterProvider = (
|
|
|
459
468
|
baseline.reasonCode,
|
|
460
469
|
);
|
|
461
470
|
decision.isBudgetForced = baseline.isBudgetForced;
|
|
471
|
+
decision.advisor = advisorConfigured ? 'bypassed' : 'none';
|
|
472
|
+
if (!toolContinuation && turn) {
|
|
473
|
+
const previousAdvisor = advisedTurns.get(turn);
|
|
474
|
+
if (previousAdvisor) decision.advisor = previousAdvisor;
|
|
475
|
+
}
|
|
462
476
|
}
|
|
463
477
|
|
|
464
478
|
// Tool results never invoke advisors, even when their prior route cannot be reused.
|
|
@@ -469,99 +483,110 @@ export const registerRouterProvider = (
|
|
|
469
483
|
user &&
|
|
470
484
|
turn &&
|
|
471
485
|
!advisedTurns.has(turn) &&
|
|
472
|
-
|
|
473
|
-
(state.currentConfig.jev?.enabled && profile.jev?.enabled))
|
|
486
|
+
advisorConfigured
|
|
474
487
|
) {
|
|
475
|
-
rememberAdvisedTurn(turn);
|
|
476
488
|
const started = performance.now();
|
|
477
|
-
const jev = state.currentConfig.jev;
|
|
478
|
-
const useJev =
|
|
479
|
-
jev?.enabled &&
|
|
480
|
-
profile.jev?.enabled &&
|
|
481
|
-
jev.apiKey.trim().length > 0;
|
|
482
489
|
const routingDeadline = started + (useJev ? 1500 : 10_000);
|
|
483
490
|
const candidates = primaryRoutePairs(profile, pairs).map(
|
|
484
491
|
createJevCandidate,
|
|
485
492
|
);
|
|
486
493
|
// A single primary bypasses advice, not a baseline's eligible fallback.
|
|
487
|
-
if (candidates.length
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
options?.signal?.throwIfAborted();
|
|
506
|
-
// Re-read registry capabilities after the network boundary.
|
|
507
|
-
pairs = available();
|
|
508
|
-
const candidate = candidates.find(
|
|
509
|
-
(entry) => entry.id === advice?.candidateId,
|
|
510
|
-
);
|
|
511
|
-
if (
|
|
512
|
-
candidate &&
|
|
513
|
-
performance.now() < routingDeadline &&
|
|
514
|
-
pairs.some(
|
|
515
|
-
(pair) =>
|
|
516
|
-
pair.model === candidate.model &&
|
|
517
|
-
pair.tier === candidate.tier &&
|
|
518
|
-
pair.thinking === candidate.thinking,
|
|
519
|
-
)
|
|
520
|
-
) {
|
|
521
|
-
decision = decisionForPair(model.id, candidate, 'jev');
|
|
522
|
-
} else {
|
|
523
|
-
const baseline = selectBaselineRoute(
|
|
524
|
-
model.id,
|
|
525
|
-
profile,
|
|
526
|
-
pairs,
|
|
527
|
-
);
|
|
528
|
-
decision = decisionForPair(
|
|
529
|
-
model.id,
|
|
530
|
-
baseline.pair,
|
|
531
|
-
baseline.reasonCode,
|
|
532
|
-
);
|
|
533
|
-
decision.errorClass = 'advisor-unavailable';
|
|
534
|
-
}
|
|
535
|
-
} else if (state.currentConfig.classifierModel) {
|
|
536
|
-
const classifier = state.currentConfig.classifierModel;
|
|
537
|
-
const result = await runClassifier(
|
|
538
|
-
classifier.model,
|
|
539
|
-
registry,
|
|
540
|
-
context,
|
|
541
|
-
undefined,
|
|
542
|
-
classifier.thinking,
|
|
543
|
-
options?.signal,
|
|
494
|
+
if (candidates.length <= 1) {
|
|
495
|
+
decision.advisor = 'bypassed';
|
|
496
|
+
rememberAdvisedTurn(turn, 'bypassed');
|
|
497
|
+
} else if (useJev && jev) {
|
|
498
|
+
decision.advisor = 'jev';
|
|
499
|
+
rememberAdvisedTurn(turn, 'jev');
|
|
500
|
+
const advice = await runJev(
|
|
501
|
+
{
|
|
502
|
+
...jev,
|
|
503
|
+
timeoutMs: Math.min(750, jev.timeoutMs),
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
taskSummary: getBoundedRecentContext(
|
|
507
|
+
context,
|
|
508
|
+
jev.maxStateChars,
|
|
509
|
+
),
|
|
510
|
+
candidates,
|
|
511
|
+
profile: profile.jev,
|
|
544
512
|
routingDeadline,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
513
|
+
signal: options?.signal,
|
|
514
|
+
},
|
|
515
|
+
).catch(() => undefined);
|
|
516
|
+
options?.signal?.throwIfAborted();
|
|
517
|
+
// Re-read registry capabilities after the network boundary.
|
|
518
|
+
pairs = available();
|
|
519
|
+
const candidate = candidates.find(
|
|
520
|
+
(entry) => entry.id === advice?.candidateId,
|
|
521
|
+
);
|
|
522
|
+
if (
|
|
523
|
+
candidate &&
|
|
524
|
+
performance.now() < routingDeadline &&
|
|
525
|
+
pairs.some(
|
|
526
|
+
(pair) =>
|
|
527
|
+
pair.model === candidate.model &&
|
|
528
|
+
pair.tier === candidate.tier &&
|
|
529
|
+
pair.thinking === candidate.thinking,
|
|
530
|
+
)
|
|
531
|
+
) {
|
|
532
|
+
decision = {
|
|
533
|
+
...decisionForPair(model.id, candidate, 'jev'),
|
|
534
|
+
advisor: 'jev',
|
|
535
|
+
};
|
|
536
|
+
} else {
|
|
548
537
|
const baseline = selectBaselineRoute(model.id, profile, pairs);
|
|
549
538
|
decision = decisionForPair(
|
|
550
539
|
model.id,
|
|
551
540
|
baseline.pair,
|
|
552
541
|
baseline.reasonCode,
|
|
553
542
|
);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
543
|
+
decision.advisor = 'jev-fallback';
|
|
544
|
+
rememberAdvisedTurn(turn, 'jev-fallback');
|
|
545
|
+
decision.errorClass = 'advisor-unavailable';
|
|
546
|
+
}
|
|
547
|
+
decision.routingLatencyMs = Math.max(
|
|
548
|
+
0,
|
|
549
|
+
performance.now() - started,
|
|
550
|
+
);
|
|
551
|
+
if (performance.now() >= routingDeadline)
|
|
552
|
+
decision.errorClass = 'deadline';
|
|
553
|
+
} else if (state.currentConfig.classifierModel) {
|
|
554
|
+
const classifier = state.currentConfig.classifierModel;
|
|
555
|
+
const result = await runClassifier(
|
|
556
|
+
classifier.model,
|
|
557
|
+
registry,
|
|
558
|
+
context,
|
|
559
|
+
undefined,
|
|
560
|
+
classifier.thinking,
|
|
561
|
+
options?.signal,
|
|
562
|
+
routingDeadline,
|
|
563
|
+
).catch(() => undefined);
|
|
564
|
+
options?.signal?.throwIfAborted();
|
|
565
|
+
pairs = available();
|
|
566
|
+
const baseline = selectBaselineRoute(model.id, profile, pairs);
|
|
567
|
+
decision = decisionForPair(
|
|
568
|
+
model.id,
|
|
569
|
+
baseline.pair,
|
|
570
|
+
baseline.reasonCode,
|
|
571
|
+
);
|
|
572
|
+
if (result && performance.now() < routingDeadline) {
|
|
573
|
+
const pair = pairs.find((entry) => entry.tier === result.tier);
|
|
574
|
+
if (pair) {
|
|
575
|
+
decision = {
|
|
576
|
+
...decisionForPair(model.id, pair, 'classifier'),
|
|
577
|
+
isClassifier: true,
|
|
578
|
+
advisor: 'classifier',
|
|
579
|
+
};
|
|
580
|
+
rememberAdvisedTurn(turn, 'classifier');
|
|
581
|
+
} else {
|
|
582
|
+
decision.advisor = 'classifier-fallback';
|
|
583
|
+
rememberAdvisedTurn(turn, 'classifier-fallback');
|
|
584
|
+
decision.errorClass = 'advisor-unavailable';
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
decision.advisor = 'classifier-fallback';
|
|
588
|
+
rememberAdvisedTurn(turn, 'classifier-fallback');
|
|
589
|
+
decision.errorClass = 'advisor-unavailable';
|
|
565
590
|
}
|
|
566
591
|
decision.routingLatencyMs = Math.max(
|
|
567
592
|
0,
|
|
@@ -603,7 +628,10 @@ export const registerRouterProvider = (
|
|
|
603
628
|
throw new Error(
|
|
604
629
|
'No compatible route for Google tool continuation.',
|
|
605
630
|
);
|
|
606
|
-
decision =
|
|
631
|
+
decision = {
|
|
632
|
+
...decisionForPair(model.id, priorPair, 'continuation'),
|
|
633
|
+
advisor: decision.advisor,
|
|
634
|
+
};
|
|
607
635
|
}
|
|
608
636
|
|
|
609
637
|
state.lastDecision = decision;
|
package/extensions/state.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
RouterPinByProfile,
|
|
15
15
|
RoutingDecision,
|
|
16
16
|
} from './types';
|
|
17
|
-
import { isRoutingReasonCode } from './types';
|
|
17
|
+
import { isAdvisorOutcome, isRoutingReasonCode } from './types';
|
|
18
18
|
|
|
19
19
|
const LAST_PROFILE_STATE_FILE = 'model-router-state.json';
|
|
20
20
|
|
|
@@ -57,6 +57,7 @@ const isDecision = (value: unknown): value is RoutingDecision =>
|
|
|
57
57
|
(value.reasonCode === undefined
|
|
58
58
|
? typeof value.reasoning === 'string'
|
|
59
59
|
: isPersistedReasonCode(value.reasonCode)) &&
|
|
60
|
+
(value.advisor === undefined || isAdvisorOutcome(value.advisor)) &&
|
|
60
61
|
['isClassifier', 'isFallback', 'isBudgetForced'].every(
|
|
61
62
|
(key) => value[key] === undefined || typeof value[key] === 'boolean',
|
|
62
63
|
);
|
|
@@ -164,6 +165,7 @@ export const snapshotDecision = (
|
|
|
164
165
|
decision.errorClass === 'deadline'
|
|
165
166
|
? decision.errorClass
|
|
166
167
|
: undefined,
|
|
168
|
+
advisor: isAdvisorOutcome(decision.advisor) ? decision.advisor : undefined,
|
|
167
169
|
thinking: decision.thinking,
|
|
168
170
|
timestamp: decision.timestamp,
|
|
169
171
|
isClassifier: decision.isClassifier,
|
package/extensions/types.ts
CHANGED
|
@@ -131,6 +131,17 @@ export const isRoutingReasonCode = (
|
|
|
131
131
|
): value is RoutingReasonCode =>
|
|
132
132
|
ROUTING_REASON_CODES.some((code) => code === value);
|
|
133
133
|
export type RoutingErrorClass = 'advisor-unavailable' | 'deadline';
|
|
134
|
+
export const ADVISOR_OUTCOMES = [
|
|
135
|
+
'none',
|
|
136
|
+
'bypassed',
|
|
137
|
+
'jev',
|
|
138
|
+
'jev-fallback',
|
|
139
|
+
'classifier',
|
|
140
|
+
'classifier-fallback',
|
|
141
|
+
] as const;
|
|
142
|
+
export type AdvisorOutcome = (typeof ADVISOR_OUTCOMES)[number];
|
|
143
|
+
export const isAdvisorOutcome = (value: unknown): value is AdvisorOutcome =>
|
|
144
|
+
ADVISOR_OUTCOMES.some((outcome) => outcome === value);
|
|
134
145
|
|
|
135
146
|
export interface RoutingDecision {
|
|
136
147
|
profile: string;
|
|
@@ -142,6 +153,7 @@ export interface RoutingDecision {
|
|
|
142
153
|
reasonCode: RoutingReasonCode;
|
|
143
154
|
routingLatencyMs?: number | undefined;
|
|
144
155
|
errorClass?: RoutingErrorClass | undefined;
|
|
156
|
+
advisor?: AdvisorOutcome | undefined;
|
|
145
157
|
thinking: ThinkingLevel;
|
|
146
158
|
timestamp: number;
|
|
147
159
|
isClassifier?: boolean | undefined;
|
package/extensions/ui.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
RouterThinkingByProfile,
|
|
6
6
|
RoutingDecision,
|
|
7
7
|
} from './types';
|
|
8
|
-
import { isRoutingReasonCode } from './types';
|
|
8
|
+
import { isAdvisorOutcome, isRoutingReasonCode } from './types';
|
|
9
9
|
|
|
10
10
|
const getDecisionFlags = (decision: RoutingDecision): string[] => {
|
|
11
11
|
const flags: string[] = [];
|
|
@@ -19,9 +19,53 @@ export const formatDecisionSource = (decision: RoutingDecision): string =>
|
|
|
19
19
|
? decision.reasonCode
|
|
20
20
|
: '';
|
|
21
21
|
|
|
22
|
+
export const formatAdvisorLabel = (
|
|
23
|
+
decision: RoutingDecision,
|
|
24
|
+
): string | undefined => {
|
|
25
|
+
if (!isAdvisorOutcome(decision.advisor)) return undefined;
|
|
26
|
+
switch (decision.advisor) {
|
|
27
|
+
case 'none':
|
|
28
|
+
case 'bypassed':
|
|
29
|
+
return undefined;
|
|
30
|
+
case 'jev':
|
|
31
|
+
return '🧭 Jev ✓';
|
|
32
|
+
case 'jev-fallback':
|
|
33
|
+
return '🧭 Jev ↪ base';
|
|
34
|
+
case 'classifier':
|
|
35
|
+
return '🧠 Classifier ✓';
|
|
36
|
+
case 'classifier-fallback':
|
|
37
|
+
return '🧠 Classifier ↪ base';
|
|
38
|
+
default:
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const formatAdvisorDetail = (
|
|
44
|
+
decision: RoutingDecision,
|
|
45
|
+
): string | undefined => {
|
|
46
|
+
const label = formatAdvisorLabel(decision);
|
|
47
|
+
if (!label) return undefined;
|
|
48
|
+
const latency = Number.isFinite(decision.routingLatencyMs)
|
|
49
|
+
? ` · ${Math.round(decision.routingLatencyMs ?? 0)}ms`
|
|
50
|
+
: '';
|
|
51
|
+
return `${label}${latency}`;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const formatAdvisorFooter = (decision: RoutingDecision): string => {
|
|
55
|
+
if (
|
|
56
|
+
!decision.advisor ||
|
|
57
|
+
decision.advisor === 'none' ||
|
|
58
|
+
decision.advisor === 'bypassed'
|
|
59
|
+
)
|
|
60
|
+
return '';
|
|
61
|
+
const label = formatAdvisorLabel(decision);
|
|
62
|
+
return label ? ` · ${label}` : '';
|
|
63
|
+
};
|
|
64
|
+
|
|
22
65
|
export const formatDecision = (decision: RoutingDecision): string => {
|
|
23
66
|
const source = formatDecisionSource(decision);
|
|
24
|
-
|
|
67
|
+
const advisor = formatAdvisorLabel(decision);
|
|
68
|
+
return `${decision.profile}: ${decision.tier} -> ${decision.targetProvider}/${decision.targetModelId} [${decision.thinking}]${source ? ` (${source})` : ''}${advisor ? ` [${advisor}]` : ''}`;
|
|
25
69
|
};
|
|
26
70
|
|
|
27
71
|
export const formatPinSummary = (
|
|
@@ -79,7 +123,7 @@ export const updateStatus = (
|
|
|
79
123
|
|
|
80
124
|
let statusText: string;
|
|
81
125
|
if (lastDecision && matchesProfile && matchesPin) {
|
|
82
|
-
statusText = `router:${activeRouterProfile}${pinLabel} -> ${lastDecision.tier} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${lastDecision.thinking})`;
|
|
126
|
+
statusText = `router:${activeRouterProfile}${pinLabel} -> ${lastDecision.tier} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${lastDecision.thinking})${formatAdvisorFooter(lastDecision)}`;
|
|
83
127
|
} else {
|
|
84
128
|
statusText = `router:${activeRouterProfile}${pinLabel} -> waiting`;
|
|
85
129
|
}
|
|
@@ -103,18 +147,13 @@ export const updateStatus = (
|
|
|
103
147
|
if (lastDecision && lastDecision.profile === statusProfile) {
|
|
104
148
|
const flags = getDecisionFlags(lastDecision);
|
|
105
149
|
const flagsStr = flags.length > 0 ? ` [${flags.join(',')}]` : '';
|
|
150
|
+
const advisorDetail = formatAdvisorDetail(lastDecision);
|
|
106
151
|
|
|
107
152
|
widgetLines.push(
|
|
108
153
|
`Route: ${lastDecision.tier}${flagsStr} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${lastDecision.thinking})`,
|
|
109
154
|
`Phase: ${lastDecision.phase}`,
|
|
110
155
|
`Source: ${formatDecisionSource(lastDecision) || 'unknown'}`,
|
|
111
|
-
...(
|
|
112
|
-
? [`Routing: ${Math.round(lastDecision.routingLatencyMs ?? 0)}ms`]
|
|
113
|
-
: []),
|
|
114
|
-
...(lastDecision.errorClass === 'deadline' ||
|
|
115
|
-
lastDecision.errorClass === 'advisor-unavailable'
|
|
116
|
-
? [`Routing error: ${lastDecision.errorClass}`]
|
|
117
|
-
: []),
|
|
156
|
+
...(advisorDetail ? [advisorDetail] : []),
|
|
118
157
|
);
|
|
119
158
|
} else if (!routerEnabled && lastNonRouterModel) {
|
|
120
159
|
widgetLines.push(`Fallback: ${lastNonRouterModel}`);
|