@alexeiled/pi-model-router 0.6.1 → 0.6.2
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 +15 -7
- package/extensions/config.ts +6 -7
- package/extensions/index.ts +32 -1
- package/extensions/provider.ts +12 -17
- package/model-router.example.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2 - 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Increase the default Jev timeout from 750 ms to 1500 ms. User-level `jev.timeoutMs` now sets the total advisory budget without the previous hidden 750 ms cap or an arbitrary upper cap. Positive finite values within Node's timer range are accepted, including 4000 and 5000 ms. Existing explicit shorter timeouts remain valid.
|
|
6
|
+
- Fix delayed Pi thinking-display events being mistaken for user changes and overriding every routing tier. Internal display updates now preserve configured per-tier effort; explicit user overrides still apply.
|
|
7
|
+
- Keep privacy opt-in, confidence validation, caller cancellation, and direct baseline fallback unchanged. No additional advisory requests or retries.
|
|
8
|
+
- Add regression coverage for delayed valid Jev responses, configurable deadlines, internal thinking events, and Astra/Luna candidate selection. Align configuration examples and operator documentation.
|
|
9
|
+
|
|
3
10
|
## 0.6.1 — 2026-09-21
|
|
4
11
|
|
|
5
12
|
- 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.
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ pi -e ./extensions/index.ts
|
|
|
95
95
|
|
|
96
96
|
- Generation and classification use Pi's provider registry, including native/custom providers and credential-specific URLs. Only the optional Jev advisor uses separate HTTPS transport.
|
|
97
97
|
- Fallbacks run only before content is emitted; cancellation does not retry. Every target must support the requested input and exact thinking level; explicit unsupported effort is not silently reduced. Omitted thinking defaults to `off` for non-reasoning targets, including fallbacks.
|
|
98
|
-
- Jev gets
|
|
98
|
+
- Jev gets a configurable total advisory budget via `jev.timeoutMs`: 1500 ms by default, with no additional routing cap or retry. The separate classifier-only compatibility path retains its 10-second bound and 256-token output limit. Failure or uncertainty means eligible baseline; caller cancellation stops generation.
|
|
99
99
|
- Valid same-turn tool continuations reuse the actual prior route before either advisor. Pins, budget policy and a single eligible primary candidate also bypass advisors. Invalid continuations choose a compatible local route without advice; incompatible Google thought-signature replay fails plainly.
|
|
100
100
|
- Pi owns tool execution permissions and per-request authentication. The router checks configured provider/profile identity, not which backend login is currently behind a provider. No private authentication storage is read.
|
|
101
101
|
- Context trimming preserves system instructions and whole active tool turns. It is a text estimate, not a guarantee that images or a large active turn fit.
|
|
@@ -199,7 +199,7 @@ profile opt-ins, are ignored with a warning, before merging user credentials.
|
|
|
199
199
|
"apiKey": "<rendered by chezmoi/1Password>",
|
|
200
200
|
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
201
201
|
"model": "jev-1.13.0",
|
|
202
|
-
"timeoutMs":
|
|
202
|
+
"timeoutMs": 1500,
|
|
203
203
|
"confidenceThreshold": 0.65,
|
|
204
204
|
"maxStateChars": 12000,
|
|
205
205
|
"mode": "advisory"
|
|
@@ -218,14 +218,22 @@ profile opt-ins, are ignored with a warning, before merging user credentials.
|
|
|
218
218
|
|
|
219
219
|
The endpoint, model, timeout, confidence threshold, state limit and mode shown
|
|
220
220
|
above are defaults. Only HTTPS endpoints without embedded credentials, query
|
|
221
|
-
parameters or fragments are accepted.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
parameters or fragments are accepted. `timeoutMs` defaults to 1500 ms and must
|
|
222
|
+
be a positive finite number within Node's timer range (at most 2147483647 ms).
|
|
223
|
+
There is no product-level cap: 4000 or 5000 ms are valid if you prefer waiting
|
|
224
|
+
longer before falling back. It sets the total Jev advisory budget, including
|
|
225
|
+
request and response-body time; there is no separate 750 ms cap. Confidence must
|
|
226
|
+
be 0–1, and the context limit must be 1–12000 characters. The separate classifier-only
|
|
226
227
|
path keeps a 10-second bound. Neither path retries or starts generation after
|
|
227
228
|
caller cancellation.
|
|
228
229
|
|
|
230
|
+
If Jev frequently falls back because requests time out, try `"timeoutMs": 3000`
|
|
231
|
+
in your user config. Existing explicit values such as 750 remain unchanged;
|
|
232
|
+
remove the field or set it to 1500 to use the new default. Increasing the timeout
|
|
233
|
+
does not lower the confidence threshold or guarantee a different route. After
|
|
234
|
+
upgrading, start a new Pi session; use `/router thinking auto` to clear any
|
|
235
|
+
unwanted effort override in an existing session.
|
|
236
|
+
|
|
229
237
|
**External data:** Jev receives bounded, role-labelled recent user/assistant/tool
|
|
230
238
|
text, prioritizing the latest user request within `maxStateChars`, plus candidate
|
|
231
239
|
tier/model/thinking identifiers. Truncation is deterministic, with no keyword
|
package/extensions/config.ts
CHANGED
|
@@ -376,10 +376,13 @@ export const normalizeTierConfig = (
|
|
|
376
376
|
};
|
|
377
377
|
};
|
|
378
378
|
|
|
379
|
+
// Node turns larger setTimeout delays into 1 ms rather than waiting longer.
|
|
380
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
381
|
+
|
|
379
382
|
export const DEFAULT_JEV_CONFIG = {
|
|
380
383
|
endpoint: 'https://api.typesafe.ai/v1/systemone',
|
|
381
384
|
model: 'jev-1.13.0',
|
|
382
|
-
timeoutMs:
|
|
385
|
+
timeoutMs: 1500,
|
|
383
386
|
confidenceThreshold: 0.65,
|
|
384
387
|
maxStateChars: 12000,
|
|
385
388
|
mode: 'advisory',
|
|
@@ -420,7 +423,7 @@ export const normalizeJevConfig = (
|
|
|
420
423
|
typeof value.timeoutMs !== 'number' ||
|
|
421
424
|
!Number.isFinite(value.timeoutMs) ||
|
|
422
425
|
value.timeoutMs <= 0 ||
|
|
423
|
-
value.timeoutMs >
|
|
426
|
+
value.timeoutMs > MAX_TIMER_DELAY_MS ||
|
|
424
427
|
typeof value.confidenceThreshold !== 'number' ||
|
|
425
428
|
!Number.isFinite(value.confidenceThreshold) ||
|
|
426
429
|
value.confidenceThreshold < 0 ||
|
|
@@ -434,10 +437,6 @@ export const normalizeJevConfig = (
|
|
|
434
437
|
(typeof value.apiKey !== 'string' || /[\r\n]/.test(value.apiKey)))
|
|
435
438
|
)
|
|
436
439
|
return invalid();
|
|
437
|
-
if (value.timeoutMs > 750)
|
|
438
|
-
warnings.push(
|
|
439
|
-
'Jev timeoutMs clamped to the effective 750 ms provider cap.',
|
|
440
|
-
);
|
|
441
440
|
const apiKey = typeof value.apiKey === 'string' ? value.apiKey.trim() : '';
|
|
442
441
|
if (value.enabled === true && !apiKey) {
|
|
443
442
|
warnings.push('Jev disabled: missing user-config API key.');
|
|
@@ -447,7 +446,7 @@ export const normalizeJevConfig = (
|
|
|
447
446
|
apiKey,
|
|
448
447
|
endpoint: value.endpoint,
|
|
449
448
|
model: value.model,
|
|
450
|
-
timeoutMs:
|
|
449
|
+
timeoutMs: value.timeoutMs,
|
|
451
450
|
confidenceThreshold: value.confidenceThreshold,
|
|
452
451
|
maxStateChars: value.maxStateChars,
|
|
453
452
|
mode: 'advisory',
|
package/extensions/index.ts
CHANGED
|
@@ -57,6 +57,10 @@ const routerExtension = (pi: ExtensionAPI) => {
|
|
|
57
57
|
let isInitialized = false;
|
|
58
58
|
let isInternalModelSwitch = false;
|
|
59
59
|
let isInternalThinkingChange = false;
|
|
60
|
+
const pendingThinkingChanges: Array<{
|
|
61
|
+
previousLevel: ThinkingLevel;
|
|
62
|
+
level: ThinkingLevel;
|
|
63
|
+
}> = [];
|
|
60
64
|
let ignoreStartupThinkingEvent = false;
|
|
61
65
|
|
|
62
66
|
const runtimeState = {
|
|
@@ -142,13 +146,27 @@ const routerExtension = (pi: ExtensionAPI) => {
|
|
|
142
146
|
};
|
|
143
147
|
|
|
144
148
|
const setThinkingLevelInternally = (level: ThinkingLevel) => {
|
|
149
|
+
let previousLevel: ThinkingLevel;
|
|
150
|
+
try {
|
|
151
|
+
previousLevel = pi.getThinkingLevel();
|
|
152
|
+
} catch {
|
|
153
|
+
return; // The session runtime may already be torn down.
|
|
154
|
+
}
|
|
155
|
+
const change = { previousLevel, level };
|
|
156
|
+
pendingThinkingChanges.push(change);
|
|
145
157
|
isInternalThinkingChange = true;
|
|
146
158
|
try {
|
|
147
159
|
pi.setThinkingLevel(level);
|
|
160
|
+
change.level = pi.getThinkingLevel();
|
|
148
161
|
} catch {
|
|
149
162
|
// Extension context may be stale after session teardown.
|
|
163
|
+
change.level = change.previousLevel;
|
|
150
164
|
} finally {
|
|
151
165
|
isInternalThinkingChange = false;
|
|
166
|
+
if (change.level === change.previousLevel) {
|
|
167
|
+
const index = pendingThinkingChanges.indexOf(change);
|
|
168
|
+
if (index >= 0) pendingThinkingChanges.splice(index, 1);
|
|
169
|
+
}
|
|
152
170
|
}
|
|
153
171
|
};
|
|
154
172
|
|
|
@@ -513,8 +531,21 @@ const routerExtension = (pi: ExtensionAPI) => {
|
|
|
513
531
|
|
|
514
532
|
pi.on('thinking_level_select', (event, ctx) => {
|
|
515
533
|
ensureInitializedFromContext(ctx);
|
|
534
|
+
if (isInternalThinkingChange) {
|
|
535
|
+
pendingThinkingChanges.pop();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// Pi emits without awaiting extension handlers; earlier handlers can delay this echo.
|
|
539
|
+
const internalChange = pendingThinkingChanges.findIndex(
|
|
540
|
+
(change) =>
|
|
541
|
+
change.previousLevel === event.previousLevel &&
|
|
542
|
+
change.level === event.level,
|
|
543
|
+
);
|
|
544
|
+
if (internalChange >= 0) {
|
|
545
|
+
pendingThinkingChanges.splice(internalChange, 1);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
516
548
|
if (!isInitialized || !routerEnabled || !selectedProfile) return;
|
|
517
|
-
if (isInternalThinkingChange) return;
|
|
518
549
|
if (ignoreStartupThinkingEvent) {
|
|
519
550
|
ignoreStartupThinkingEvent = false;
|
|
520
551
|
return;
|
package/extensions/provider.ts
CHANGED
|
@@ -486,7 +486,8 @@ export const registerRouterProvider = (
|
|
|
486
486
|
advisorConfigured
|
|
487
487
|
) {
|
|
488
488
|
const started = performance.now();
|
|
489
|
-
const routingDeadline =
|
|
489
|
+
const routingDeadline =
|
|
490
|
+
started + (useJev && jev ? jev.timeoutMs : 10_000);
|
|
490
491
|
const candidates = primaryRoutePairs(profile, pairs).map(
|
|
491
492
|
createJevCandidate,
|
|
492
493
|
);
|
|
@@ -497,22 +498,16 @@ export const registerRouterProvider = (
|
|
|
497
498
|
} else if (useJev && jev) {
|
|
498
499
|
decision.advisor = 'jev';
|
|
499
500
|
rememberAdvisedTurn(turn, 'jev');
|
|
500
|
-
const advice = await runJev(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
candidates,
|
|
511
|
-
profile: profile.jev,
|
|
512
|
-
routingDeadline,
|
|
513
|
-
signal: options?.signal,
|
|
514
|
-
},
|
|
515
|
-
).catch(() => undefined);
|
|
501
|
+
const advice = await runJev(jev, {
|
|
502
|
+
taskSummary: getBoundedRecentContext(
|
|
503
|
+
context,
|
|
504
|
+
jev.maxStateChars,
|
|
505
|
+
),
|
|
506
|
+
candidates,
|
|
507
|
+
profile: profile.jev,
|
|
508
|
+
routingDeadline,
|
|
509
|
+
signal: options?.signal,
|
|
510
|
+
}).catch(() => undefined);
|
|
516
511
|
options?.signal?.throwIfAborted();
|
|
517
512
|
// Re-read registry capabilities after the network boundary.
|
|
518
513
|
pairs = available();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"apiKey": "<rendered by chezmoi/1Password in user config only>",
|
|
6
6
|
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
7
7
|
"model": "jev-1.13.0",
|
|
8
|
-
"timeoutMs":
|
|
8
|
+
"timeoutMs": 1500,
|
|
9
9
|
"confidenceThreshold": 0.65,
|
|
10
10
|
"maxStateChars": 12000,
|
|
11
11
|
"mode": "advisory"
|