From af27c5dd3cea449b751066e6ed003ac183879d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Fri, 13 Mar 2026 10:23:31 -0600 Subject: [PATCH] fix: provider-aware model resolution for per-phase preferences (#149) (#198) Auto mode's model resolution used `allModels.find(m => m.id === modelId)` which returns the first match regardless of provider. With 30+ duplicate model IDs across providers, user preferences silently resolved to the wrong provider. Three fixes: - Use `getAvailable()` instead of `getAll()` so only authenticated models are considered - Support `provider/model` format (e.g. "google/gemini-2.5-pro") for explicit provider targeting - For bare IDs, prefer the current session's provider, then first available match, with an ambiguity warning - Store and restore original model provider instead of hardcoding "anthropic" when restoring the user's model after auto-mode Co-authored-by: Claude Opus 4.6 --- src/resources/extensions/gsd/auto.ts | 45 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 1f979ea27..59db2abda 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -152,6 +152,7 @@ let currentMilestoneId: string | null = null; /** Model the user had selected before auto-mode started */ let originalModelId: string | null = null; +let originalModelProvider: string | null = null; /** Progress-aware timeout supervision */ let unitTimeoutHandle: ReturnType | null = null; @@ -277,10 +278,11 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi ctx?.ui.setFooter(undefined); // Restore the user's original model - if (pi && ctx && originalModelId) { - const original = ctx.modelRegistry.find("anthropic", originalModelId); + if (pi && ctx && originalModelId && originalModelProvider) { + const original = ctx.modelRegistry.find(originalModelProvider, originalModelId); if (original) await pi.setModel(original); originalModelId = null; + originalModelProvider = null; } cmdCtx = null; @@ -464,6 +466,7 @@ export async function startAuto( currentUnit = null; currentMilestoneId = state.activeMilestone?.id ?? null; originalModelId = ctx.model?.id ?? null; + originalModelProvider = ctx.model?.provider ?? null; // Initialize metrics — loads existing ledger from disk initMetrics(base); @@ -1439,14 +1442,46 @@ async function dispatchNextUnit( // Try primary model, then fallbacks in order if setting fails const modelConfig = resolveModelWithFallbacksForUnit(unitType); if (modelConfig) { - const allModels = ctx.modelRegistry.getAll(); + const availableModels = ctx.modelRegistry.getAvailable(); const modelsToTry = [modelConfig.primary, ...modelConfig.fallbacks]; let modelSet = false; for (const modelId of modelsToTry) { - const model = allModels.find(m => m.id === modelId); + // Support "provider/model" format for explicit provider targeting + const slashIdx = modelId.indexOf("/"); + let model; + if (slashIdx !== -1) { + const provider = modelId.substring(0, slashIdx); + const id = modelId.substring(slashIdx + 1); + model = availableModels.find( + m => m.provider.toLowerCase() === provider.toLowerCase() + && m.id.toLowerCase() === id.toLowerCase(), + ); + } else { + // For bare IDs, prefer the current session's provider, then first available match + const currentProvider = ctx.model?.provider; + const exactProviderMatch = availableModels.find( + m => m.id === modelId && m.provider === currentProvider, + ); + const anyMatch = availableModels.find(m => m.id === modelId); + model = exactProviderMatch ?? anyMatch; + + // Warn if the ID is ambiguous across providers + if (anyMatch && !exactProviderMatch) { + const providers = availableModels + .filter(m => m.id === modelId) + .map(m => m.provider); + if (providers.length > 1) { + ctx.ui.notify( + `Model ID "${modelId}" exists in multiple providers (${providers.join(", ")}). ` + + `Resolved to ${anyMatch.provider}. Use "provider/model" format for explicit targeting.`, + "warning", + ); + } + } + } if (!model) { - ctx.ui.notify(`Model ${modelId} not found in registry, trying fallback.`, "warning"); + ctx.ui.notify(`Model ${modelId} not found in available models, trying fallback.`, "warning"); continue; }