Skip to content

Commit 06c6484

Browse files
committed
fix(models): canonicalize the reported utility ref and dedupe route diagnostics
utilityModel accepts aliases, so status now reports the resolved provider/model instead of the raw alias, and identical route issues from overlapping primary/utility refs collapse to one entry (Codex round 5). Formatting verified remotely via Testbox format:check.
1 parent 7aa8749 commit 06c6484

2 files changed

Lines changed: 45 additions & 9 deletions

File tree

src/commands/models/list.status-command.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ export async function modelsStatusCommand(
498498
// plain API path, so the ref gets full route analysis without inheriting
499499
// the primary's codex-runtime fallback.
500500
addProviderUse(utilityModelRef, false, "text");
501+
// Display the canonical provider/model: utilityModel accepts aliases, and
502+
// route issues/probes always report the resolved ref.
503+
const resolvedUtilityRef = utilityModelRef ? resolveStatusModelRef(utilityModelRef) : undefined;
504+
const utilityModelDisplayRef = resolvedUtilityRef
505+
? modelKey(normalizeProviderId(resolvedUtilityRef.provider), resolvedUtilityRef.model)
506+
: (utilityModelRef ?? null);
501507

502508
const providersFromEnv = new Set<string>();
503509
// Use the shared provider-env registry so `models status` stays aligned with
@@ -975,6 +981,18 @@ export async function modelsStatusCommand(
975981
},
976982
];
977983
});
984+
// Utility (or duplicate fallback) refs can repeat a configured model;
985+
// identical diagnostics collapse while genuinely different evaluations
986+
// for the same model (e.g. codex-fallback vs plain route) stay separate.
987+
const seenRouteIssues = new Set<string>();
988+
const dedupedModelRouteIssues = modelRouteIssues.filter((issue) => {
989+
const key = JSON.stringify(issue);
990+
if (seenRouteIssues.has(key)) {
991+
return false;
992+
}
993+
seenRouteIssues.add(key);
994+
return true;
995+
});
978996
const missingProvidersInUse = Array.from(
979997
new Set(
980998
providerUses
@@ -1149,7 +1167,7 @@ export async function modelsStatusCommand(
11491167
};
11501168
const routeAuthHealth = new Set(providerUses.map(resolveRouteAuthHealth));
11511169
const hasExpiredOrMissing =
1152-
modelRouteIssues.some(
1170+
dedupedModelRouteIssues.some(
11531171
(issue) => issue.kind === "incompatible" || issue.kind === "indeterminate",
11541172
) ||
11551173
routeAuthHealth.has("missing") ||
@@ -1175,7 +1193,7 @@ export async function modelsStatusCommand(
11751193
fallbacks,
11761194
imageModel: imageModel || null,
11771195
imageFallbacks,
1178-
utilityModel: { ref: utilityModelRef ?? null, source: utilityModelSource },
1196+
utilityModel: { ref: utilityModelDisplayRef, source: utilityModelSource },
11791197
...(agentId
11801198
? {
11811199
modelConfig: {
@@ -1194,7 +1212,7 @@ export async function modelsStatusCommand(
11941212
},
11951213
providersWithOAuth: providersWithOauth,
11961214
missingProvidersInUse,
1197-
modelRouteIssues,
1215+
modelRouteIssues: dedupedModelRouteIssues,
11981216
runtimeAuthRoutes,
11991217
providers: providerAuth,
12001218
unusableProfiles,
@@ -1256,9 +1274,9 @@ export async function modelsStatusCommand(
12561274
runtime.log(
12571275
`${label("Utility model")}${colorize(rich, theme.muted, ":")} ${colorize(
12581276
rich,
1259-
utilityModelRef ? theme.success : theme.muted,
1260-
utilityModelRef
1261-
? `${utilityModelRef}${utilityModelSource === "provider-default" ? " (provider default)" : ""}`
1277+
utilityModelDisplayRef ? theme.success : theme.muted,
1278+
utilityModelDisplayRef
1279+
? `${utilityModelDisplayRef}${utilityModelSource === "provider-default" ? " (provider default)" : ""}`
12621280
: utilityModelSource === "disabled"
12631281
? "off"
12641282
: "-",
@@ -1409,10 +1427,10 @@ export async function modelsStatusCommand(
14091427
}
14101428
}
14111429

1412-
if (modelRouteIssues.length > 0) {
1430+
if (dedupedModelRouteIssues.length > 0) {
14131431
runtime.log("");
14141432
runtime.log(colorize(rich, theme.heading, "Model route issues"));
1415-
for (const issue of modelRouteIssues) {
1433+
for (const issue of dedupedModelRouteIssues) {
14161434
const modelRef = `${issue.provider}/${issue.model}`;
14171435
if (issue.kind === "incompatible") {
14181436
runtime.log(`- ${theme.heading(modelRef)} [${issue.code}] ${issue.message}`);
@@ -1434,7 +1452,7 @@ export async function modelsStatusCommand(
14341452
runtime.log("");
14351453
runtime.log(colorize(rich, theme.heading, "Missing auth"));
14361454
for (const provider of missingProvidersInUse) {
1437-
const requiresSubscription = modelRouteIssues.some(
1455+
const requiresSubscription = dedupedModelRouteIssues.some(
14381456
(issue) =>
14391457
issue.kind === "missing-auth" &&
14401458
issue.provider === provider &&

src/commands/models/list.status.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,24 @@ describe("modelsStatusCommand auth overview", () => {
644644
expect.objectContaining({ provider: "mistral", model: "mistral-small" }),
645645
]),
646646
);
647+
648+
// Aliases are valid utilityModel input; the report shows the canonical ref.
649+
mocks.loadConfig.mockReturnValue({
650+
...baseConfig,
651+
agents: {
652+
defaults: {
653+
...baseConfig.agents.defaults,
654+
models: { "anthropic/claude-opus-4-6": { alias: "Opus" } },
655+
utilityModel: "Opus",
656+
},
657+
},
658+
});
659+
const aliasRuntime = createRuntime();
660+
await modelsStatusCommand({ json: true }, aliasRuntime as never);
661+
expect(parseFirstJsonLog(aliasRuntime).utilityModel).toEqual({
662+
ref: "anthropic/claude-opus-4-6",
663+
source: "config",
664+
});
647665
} finally {
648666
if (originalLoadConfig) {
649667
mocks.loadConfig.mockImplementation(originalLoadConfig);

0 commit comments

Comments
 (0)