jeo-code 0.12.2 → 0.12.3
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 +15 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/agent/task-tool.ts +9 -3
- package/src/ai/model-catalog-compat.ts +5 -1
- package/src/ai/model-catalog.ts +13 -14
- package/src/ai/model-registry.ts +4 -4
- package/src/commands/launch/route-slash.ts +8 -3
- package/src/commands/launch.ts +28 -11
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,21 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.12.3] - 2026-10-17
|
|
13
|
+
_Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover._
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Anthropic claude-4.x entries removed from the static catalog** (`src/ai/model-catalog.ts`) — `claude-opus-4-8`, `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-opus-4-6` dropped. Static catalog is now a fallback-only claude-5.x set (`sonnet-5-5`, `opus-5-5`, `sonnet-5`, `fable-5`, `mythos-5`).
|
|
17
|
+
- **`sonnet` / `opus` built-in aliases updated to claude-5.5** (`src/ai/model-registry.ts`) — `sonnet` → `claude-sonnet-5-5`, `opus` → `claude-opus-5-5`.
|
|
18
|
+
- **Live `https://api.anthropic.com/v1/models` is the primary model source** — when logged in, the API returns the account's actual available models; the static catalog serves as fallback only.
|
|
19
|
+
- **`/route off` fixes the model with no failover** (`src/commands/launch.ts`, `src/commands/launch/route-slash.ts`, `src/agent/task-tool.ts`) — when routing is off (via config or `/route off`), neither main turns nor delegated subagents bail early or fail over to equivalent models on 429/refusal, and `/route` status reports `routing: off (this session) — model fixed, no failover`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Provider-qualified catalog entries no longer hijack bare model IDs** (`src/ai/model-catalog.ts`, `src/ai/model-catalog-compat.ts`) — `findCatalogModel`, `findCatalogEntry`, and `catalogMetadata` only match a bare `providerModel` when the catalog row itself is not provider-qualified (`!canonical.startsWith(`${provider}/`)`), preventing `antigravity/claude-sonnet-4-6` from misrouting bare `claude-sonnet-4-6` to `antigravity`.
|
|
23
|
+
|
|
24
|
+
### Verified
|
|
25
|
+
- `bun run typecheck`, `bun run pack:check`, and full regression suites green.
|
|
26
|
+
|
|
12
27
|
## [0.12.2] - 2026-10-16
|
|
13
28
|
_Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend._
|
|
14
29
|
|
package/README.ja.md
CHANGED
|
@@ -416,10 +416,10 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
416
416
|
|
|
417
417
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
418
418
|
- **[Unreleased]**
|
|
419
|
+
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
419
420
|
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
420
421
|
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
421
422
|
- **[0.12.0]** (2026-10-16) — Karpathy behavioral guardrails become always-on discipline, and skill routing gains an ECC single-harness inventory plus a Jev-style advisory router.
|
|
422
|
-
- **[0.11.6]** (2026-10-16) — Live Antigravity account model discovery now serves the actual account's available models instead of assuming static availability.
|
|
423
423
|
|
|
424
424
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
425
425
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -417,10 +417,10 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
417
417
|
|
|
418
418
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
419
419
|
- **[Unreleased]**
|
|
420
|
+
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
420
421
|
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
421
422
|
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
422
423
|
- **[0.12.0]** (2026-10-16) — Karpathy behavioral guardrails become always-on discipline, and skill routing gains an ECC single-harness inventory plus a Jev-style advisory router.
|
|
423
|
-
- **[0.11.6]** (2026-10-16) — Live Antigravity account model discovery now serves the actual account's available models instead of assuming static availability.
|
|
424
424
|
|
|
425
425
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
426
426
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -409,10 +409,10 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
409
409
|
|
|
410
410
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
411
411
|
- **[Unreleased]**
|
|
412
|
+
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
412
413
|
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
413
414
|
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
414
415
|
- **[0.12.0]** (2026-10-16) — Karpathy behavioral guardrails become always-on discipline, and skill routing gains an ECC single-harness inventory plus a Jev-style advisory router.
|
|
415
|
-
- **[0.11.6]** (2026-10-16) — Live Antigravity account model discovery now serves the actual account's available models instead of assuming static availability.
|
|
416
416
|
|
|
417
417
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
418
418
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -414,10 +414,10 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
414
414
|
|
|
415
415
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
416
416
|
- **[Unreleased]**
|
|
417
|
+
- **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
|
|
417
418
|
- **[0.12.2]** (2026-10-16) — Select Argon through the official agy full agent, with its own live catalog and session, separately from jeo's Cloud Code Assist backend.
|
|
418
419
|
- **[0.12.1]** (2026-10-16) — Logged-in OpenAI subscription models were invisible on proxy-configured machines.
|
|
419
420
|
- **[0.12.0]** (2026-10-16) — Karpathy behavioral guardrails become always-on discipline, and skill routing gains an ECC single-harness inventory plus a Jev-style advisory router.
|
|
420
|
-
- **[0.11.6]** (2026-10-16) — Live Antigravity account model discovery now serves the actual account's available models instead of assuming static availability.
|
|
421
421
|
|
|
422
422
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
423
423
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
package/src/agent/task-tool.ts
CHANGED
|
@@ -433,6 +433,10 @@ export async function runSubagentOnce(
|
|
|
433
433
|
// "false positive -> try a different model" design this implements.
|
|
434
434
|
const safetyFallbackAvailable = (): boolean => safetyFallbackCandidates().length > 0;
|
|
435
435
|
|
|
436
|
+
// Failover (reroute to an equivalent model) is part of routing: it only runs when
|
|
437
|
+
// `routing.enabled` is on. `launch.ts` passes the parent session's EFFECTIVE state
|
|
438
|
+
// (`/route on|off` override ?? config) in `config.routing.enabled`.
|
|
439
|
+
const failoverEnabled = !!opts.config.routing?.enabled;
|
|
436
440
|
let activeModel = initialModel;
|
|
437
441
|
emit({ role: role.id, kind: "start", detail: taskText, maxSteps, model: activeModel });
|
|
438
442
|
const runOnce = () => {
|
|
@@ -448,8 +452,7 @@ export async function runSubagentOnce(
|
|
|
448
452
|
signal,
|
|
449
453
|
steer,
|
|
450
454
|
tools: subagentToolset(role),
|
|
451
|
-
rateLimitFallbackAvailable,
|
|
452
|
-
safetyFallbackAvailable,
|
|
455
|
+
...(failoverEnabled ? { rateLimitFallbackAvailable, safetyFallbackAvailable } : {}),
|
|
453
456
|
events: {
|
|
454
457
|
onStep: n => { currentStep = n; },
|
|
455
458
|
// Live reasoning preview (native extended-thinking models only — the JSON-protocol
|
|
@@ -494,7 +497,10 @@ export async function runSubagentOnce(
|
|
|
494
497
|
};
|
|
495
498
|
|
|
496
499
|
let result = await runOnce();
|
|
497
|
-
|
|
500
|
+
// `routing.enabled` off = the model is FIXED: no failover reroute at all (the
|
|
501
|
+
// engine bail predicates above are withheld too, so a 429 rides its normal
|
|
502
|
+
// backoff ladder instead of bailing early with nowhere to go).
|
|
503
|
+
for (let attempt = 0; failoverEnabled && attempt < MAX_SUBAGENT_REROUTES; attempt++) {
|
|
498
504
|
const rateLimited = !result.done && isRateLimitError(new Error(result.doneReason ?? ""));
|
|
499
505
|
const safetyBail = !result.done && !rateLimited && isSafetyFallbackBail(result.doneReason);
|
|
500
506
|
if (!rateLimited && !safetyBail) break;
|
|
@@ -55,7 +55,11 @@ export function findCatalogEntry(id: string): ModelCatalogEntry | undefined {
|
|
|
55
55
|
const direct = findCatalogModel(id);
|
|
56
56
|
if (direct) return adapt(direct);
|
|
57
57
|
const n = normalizeModelId(id);
|
|
58
|
-
const hit = MODEL_CATALOG.find(m =>
|
|
58
|
+
const hit = MODEL_CATALOG.find(m =>
|
|
59
|
+
normalizeModelId(m.canonical) === n
|
|
60
|
+
|| normalizeModelId(`${m.provider}/${m.providerModel}`) === n
|
|
61
|
+
|| (!m.canonical.startsWith(`${m.provider}/`) && normalizeModelId(m.providerModel) === n),
|
|
62
|
+
);
|
|
59
63
|
return hit ? adapt(hit) : undefined;
|
|
60
64
|
}
|
|
61
65
|
|
package/src/ai/model-catalog.ts
CHANGED
|
@@ -80,7 +80,9 @@ const STD: ThinkLevel[] = ["low", "medium", "high"];
|
|
|
80
80
|
* (`cheapestCredentialed`/`strongestCredentialed`/`compareStrengthAscending`)
|
|
81
81
|
* now tiebreak on `releaseDate` directly rather than relying on position. */
|
|
82
82
|
export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
83
|
-
// Anthropic (newest first: sonnet-5-5 > opus-5-5 > sonnet-5 > fable-5 >
|
|
83
|
+
// Anthropic (newest first: sonnet-5-5 > opus-5-5 > sonnet-5 > fable-5 > mythos-5).
|
|
84
|
+
// claude-4.x and below removed: live API discovery (https://api.anthropic.com/v1/models)
|
|
85
|
+
// returns the account's actual available models; the static catalog is fallback-only.
|
|
84
86
|
// 5.5 generation (platform.claude.com/docs/en/models/{opus-5-5,sonnet-5-5}/overview): dateless pinned wire ids
|
|
85
87
|
// `claude-opus-5-5` (released 2026-09-22) / `claude-sonnet-5-5` (2026-09-28), 1M ctx / 128k sync max output, adaptive
|
|
86
88
|
// thinking (Opus 5.5 always on; Sonnet 5.5 on by default), text+image input. Canonical == wire id; anthropic.ts already
|
|
@@ -92,17 +94,7 @@ export const MODEL_CATALOG: readonly CatalogModel[] = [
|
|
|
92
94
|
// Mythos 5 is limited-availability (Project Glasswing) but callable by id for approved accounts.
|
|
93
95
|
{ canonical: "claude-fable-5", provider: "anthropic", providerModel: "claude-fable-5", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-06" },
|
|
94
96
|
{ canonical: "claude-mythos-5", provider: "anthropic", providerModel: "claude-mythos-5", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, limitedAvailability: true, releaseDate: "2026-06" },
|
|
95
|
-
|
|
96
|
-
// opus 4.7/4.8 OMIT visible thought unless the request opts into `display: "summarized"` —
|
|
97
|
-
// anthropic.ts sets that on the adaptive transport so reasoning streams again (gjc parity).
|
|
98
|
-
// The nativizable path still replays signature-only thinking blocks for cross-turn continuity.
|
|
99
|
-
// The 4.6 generation onward is 1M context / 128k sync max output (Anthropic docs:
|
|
100
|
-
// Opus 4.8 & Sonnet 5 comparison table; Opus 4.6/4.7, Sonnet 4.6 share the gen's
|
|
101
|
-
// dateless-snapshot 1M/128k + 300k batch-output beta).
|
|
102
|
-
{ canonical: "claude-opus-4-8", provider: "anthropic", providerModel: "claude-opus-4-8", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-05" },
|
|
103
|
-
{ canonical: "claude-opus-4-7", provider: "anthropic", providerModel: "claude-opus-4-7", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-04" },
|
|
104
|
-
{ canonical: "claude-sonnet-4-6", provider: "anthropic", providerModel: "claude-sonnet-4-6", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-02" },
|
|
105
|
-
{ canonical: "claude-opus-4-6", provider: "anthropic", providerModel: "claude-opus-4-6", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-02" },
|
|
97
|
+
|
|
106
98
|
// OpenAI (newest first: gpt-5.6 > gpt-5.5 > gpt-5.4 > o4-mini/o3/gpt-4.1 > o3-mini > gpt-4o-mini > gpt-4o)
|
|
107
99
|
{ canonical: "gpt-5.6", provider: "openai", providerModel: "gpt-5.6", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-06" },
|
|
108
100
|
{ canonical: "gpt-5.5", provider: "openai", providerModel: "gpt-5.5", contextTokens: 400_000, maxOutputTokens: 128_000, thinking: FULL, images: true, releaseDate: "2026-04" },
|
|
@@ -526,7 +518,11 @@ export function formatTokens(n: number): string {
|
|
|
526
518
|
/** Exact lookup by canonical id or provider model id. */
|
|
527
519
|
export function findCatalogModel(idOrModel: string): CatalogModel | undefined {
|
|
528
520
|
const q = idOrModel.trim();
|
|
529
|
-
return MODEL_CATALOG.find(m =>
|
|
521
|
+
return MODEL_CATALOG.find(m =>
|
|
522
|
+
m.canonical === q
|
|
523
|
+
|| `${m.provider}/${m.providerModel}` === q
|
|
524
|
+
|| (!m.canonical.startsWith(`${m.provider}/`) && m.providerModel === q),
|
|
525
|
+
);
|
|
530
526
|
}
|
|
531
527
|
|
|
532
528
|
/**
|
|
@@ -653,7 +649,10 @@ export function catalogMetadata(modelId: string): CatalogModel | undefined {
|
|
|
653
649
|
if (direct) return direct;
|
|
654
650
|
// Tolerate provider-prefixed or bare provider model ids.
|
|
655
651
|
const bare = modelId.replace(/^[a-z-]+\//, "");
|
|
656
|
-
const hit = MODEL_CATALOG.find(m =>
|
|
652
|
+
const hit = MODEL_CATALOG.find(m =>
|
|
653
|
+
m.canonical === bare
|
|
654
|
+
|| (!m.canonical.startsWith(`${m.provider}/`) && (m.providerModel === bare || m.providerModel.endsWith(`/${bare}`))),
|
|
655
|
+
);
|
|
657
656
|
if (hit) return hit;
|
|
658
657
|
// Last resort: infer capabilities from the model family so a brand-new
|
|
659
658
|
// revision still surfaces reasoning/thinking like its catalogued siblings.
|
package/src/ai/model-registry.ts
CHANGED
|
@@ -9,18 +9,18 @@ export interface ModelAliases {
|
|
|
9
9
|
export const BUILTIN_ALIASES: ModelAliases = {
|
|
10
10
|
fast: "ollama/qwen2.5:0.5b",
|
|
11
11
|
local: "ollama/qwen2.5:0.5b",
|
|
12
|
-
sonnet: "claude-sonnet-
|
|
13
|
-
opus: "claude-opus-
|
|
12
|
+
sonnet: "claude-sonnet-5-5",
|
|
13
|
+
opus: "claude-opus-5-5",
|
|
14
14
|
gpt: "gpt-5.5",
|
|
15
15
|
flash: "gemini-2.5-flash",
|
|
16
16
|
grok: "grok-4.3",
|
|
17
17
|
kimi: "kimi-k2-0711-preview",
|
|
18
|
-
// Anthropic 5.5 generation: spellings
|
|
19
|
-
// 4.6 ids (pinned backward-compat); users opt into 5.5 by naming the version.
|
|
18
|
+
// Anthropic 5.5 generation: versioned spellings as convenience aliases.
|
|
20
19
|
"opus-5.5": "claude-opus-5-5",
|
|
21
20
|
"sonnet-5.5": "claude-sonnet-5-5",
|
|
22
21
|
"opus5.5": "claude-opus-5-5",
|
|
23
22
|
"sonnet5.5": "claude-sonnet-5-5",
|
|
23
|
+
|
|
24
24
|
// Antigravity's backend DEPRECATED this wire id (deprecatedModelIds:
|
|
25
25
|
// { "gemini-3.1-pro-high": { newModelId: "gemini-pro-agent" } }) — keep
|
|
26
26
|
// existing configs/roles pinned to the old id working on the successor.
|
|
@@ -44,6 +44,11 @@ export interface RouteSlashResult {
|
|
|
44
44
|
lines: string[];
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** Off means a FIXED model: no prompt routing AND no equivalent-model failover. */
|
|
48
|
+
function stateLabel(enabled: boolean): string {
|
|
49
|
+
return enabled ? "routing: on (this session)" : "routing: off (this session) — model fixed, no failover";
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
const USAGE = "Usage: /route [status|on|off|why|history [n]|save|on save|off save]";
|
|
48
53
|
|
|
49
54
|
function isRealDecision(decision: RouteDecision | { note: string } | null): decision is RouteDecision {
|
|
@@ -92,7 +97,7 @@ export async function runRouteSlash(input: string, ctx: RouteSlashCtx): Promise<
|
|
|
92
97
|
const effective = ctx.sessionRouteOverride ?? ctx.routingConfigEnabled;
|
|
93
98
|
|
|
94
99
|
if (!sub || sub === "status") {
|
|
95
|
-
const lines = [
|
|
100
|
+
const lines = [stateLabel(effective)];
|
|
96
101
|
if (isRealDecision(ctx.lastRouteDecision)) {
|
|
97
102
|
lines.push(`last decision: ${ctx.lastRouteDecision.tier} → ${ctx.lastRouteDecision.model} (${ctx.lastRouteDecision.source}: ${ctx.lastRouteDecision.signals.join(", ") || "none"}, confidence ${ctx.lastRouteDecision.confidence.toFixed(2)})`);
|
|
98
103
|
}
|
|
@@ -113,9 +118,9 @@ export async function runRouteSlash(input: string, ctx: RouteSlashCtx): Promise<
|
|
|
113
118
|
const enabled = sub === "on";
|
|
114
119
|
if (arg === "save") {
|
|
115
120
|
await saveConfigPatch(raw => ({ routing: { ...raw.routing, enabled } }));
|
|
116
|
-
return { sessionRouteOverride: enabled, lines: [
|
|
121
|
+
return { sessionRouteOverride: enabled, lines: [`${stateLabel(enabled)} — saved to ~/.jeo/config.json`] };
|
|
117
122
|
}
|
|
118
|
-
return { sessionRouteOverride: enabled, lines: [
|
|
123
|
+
return { sessionRouteOverride: enabled, lines: [stateLabel(enabled)] };
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
// Bare `/route save` persists whatever is CURRENTLY effective (a prior
|
package/src/commands/launch.ts
CHANGED
|
@@ -1659,7 +1659,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1659
1659
|
const fullTools = {
|
|
1660
1660
|
...DEFAULT_TOOLS,
|
|
1661
1661
|
task: createTaskTool({
|
|
1662
|
-
|
|
1662
|
+
// Subagents inherit the parent's EFFECTIVE routing state (`/route on|off`
|
|
1663
|
+
// override ?? config) — task-tool.ts gates its failover reroute on it.
|
|
1664
|
+
config: { ...turnConfig, defaultModel: activeModel, routing: { ...turnConfig.routing, enabled: routingEnabled } },
|
|
1665
|
+
|
|
1663
1666
|
signal: ac.signal,
|
|
1664
1667
|
steer: drainSteer,
|
|
1665
1668
|
registry: subagentRegistry,
|
|
@@ -1710,12 +1713,13 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1710
1713
|
// mutated in place (`.add`) as the fallback loop below tries more models — this
|
|
1711
1714
|
// closure always reads its live contents, never a stale snapshot.
|
|
1712
1715
|
const attemptedRouteModels = new Set<string>([activeModel]);
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1715
|
-
//
|
|
1716
|
-
//
|
|
1717
|
-
//
|
|
1718
|
-
//
|
|
1716
|
+
// The synthesized decision below is only the BASE for `equivalentRouteFallback`'s
|
|
1717
|
+
// tier/pool search when the pre-call router produced no decision (routing on but
|
|
1718
|
+
// fail-open, or a /model pin with `/route on`). It is inert when routing is off:
|
|
1719
|
+
// the reroute loop and the engine's bail predicates are both gated on
|
|
1720
|
+
// `routingEnabled`, so `/route off` means a FIXED model with no failover.
|
|
1721
|
+
// (`inferTierForModel` gives the same-size-class tier/pool to search.)
|
|
1722
|
+
|
|
1719
1723
|
let fallbackBaseDecision: RouteDecision = routed ?? {
|
|
1720
1724
|
model: activeModel,
|
|
1721
1725
|
thinking: activeThinking,
|
|
@@ -1785,8 +1789,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1785
1789
|
signal: ac.signal,
|
|
1786
1790
|
sessionKey: cacheKey,
|
|
1787
1791
|
steer: drainSteer,
|
|
1788
|
-
|
|
1789
|
-
|
|
1792
|
+
// Withheld when routing is off: the bail predicates only make sense paired
|
|
1793
|
+
// with the reroute loop below, which `routingEnabled` also gates.
|
|
1794
|
+
...(routingEnabled ? { rateLimitFallbackAvailable, safetyFallbackAvailable } : {}),
|
|
1795
|
+
|
|
1790
1796
|
events: wrapEvents(withStepPersistence({ ...withToolDetailCapture(tui ? tui.events() : streamEvents), onBeforeDone }, persistTurnTail), opik),
|
|
1791
1797
|
});
|
|
1792
1798
|
const routeFailureReason = (doneReason: unknown): string | null => {
|
|
@@ -1866,7 +1872,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1866
1872
|
const routeFallbackMax = Number.isFinite(routeFallbackMaxRaw) && routeFallbackMaxRaw >= 0
|
|
1867
1873
|
? Math.floor(routeFallbackMaxRaw)
|
|
1868
1874
|
: 3;
|
|
1869
|
-
for (let routeFallbackAttempt = 0; routeFallbackAttempt < routeFallbackMax; routeFallbackAttempt++) {
|
|
1875
|
+
for (let routeFallbackAttempt = 0; routingEnabled && routeFallbackAttempt < routeFallbackMax; routeFallbackAttempt++) {
|
|
1876
|
+
|
|
1870
1877
|
// A loop guard is a local safety boundary, not a provider failure. A
|
|
1871
1878
|
// fresh loop on an equivalent model loses the guard state and could
|
|
1872
1879
|
// re-execute a previously skipped mutation.
|
|
@@ -1962,10 +1969,20 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1962
1969
|
}
|
|
1963
1970
|
// A completed turn with an empty done-reason must NOT masquerade as a step-limit
|
|
1964
1971
|
// failure ("reached the 3-step limit" after the model called done at step 3).
|
|
1965
|
-
const
|
|
1972
|
+
const baseReply = stripSafetyFallbackTag(result.doneReason ?? "")
|
|
1966
1973
|
|| (result.done
|
|
1967
1974
|
? `(done in ${result.steps} step${result.steps === 1 ? "" : "s"} — the model returned no summary)`
|
|
1968
1975
|
: `(reached the ${result.steps}-step limit without signaling done)`);
|
|
1976
|
+
// Routing off = the model is fixed and nothing fails over. When the turn died on
|
|
1977
|
+
// an account-level limit, say so and name the one switch that re-enables failover.
|
|
1978
|
+
const failoverHint = !routingEnabled && !result.done && (() => {
|
|
1979
|
+
const failure = new Error(result.doneReason ?? "");
|
|
1980
|
+
return isRateLimitError(failure) || isUsageLimitError(failure);
|
|
1981
|
+
})()
|
|
1982
|
+
? "\n(routing is off — the model is fixed and no equivalent-model failover was attempted. Run '/route on' to allow failover.)"
|
|
1983
|
+
: "";
|
|
1984
|
+
const reply = `${baseReply}${failoverHint}`;
|
|
1985
|
+
|
|
1969
1986
|
if (sessionNotifyEndpoint) {
|
|
1970
1987
|
const usageStr = result.usage ? formatUsage(result.usage) : undefined;
|
|
1971
1988
|
if (notifyRedact) {
|