fix(gsd): restore isAutoMode plumbing and workflow-logger catch in auto-model-selection

CI on #4141 failed because threading an explicit flatRateCtx parameter
through resolvePreferredModelConfig broke two contracts the test suite
locks in:

  1. interactive-routing-bypass (#3962) asserts that
     resolvePreferredModelConfig is invoked with exactly three positional
     arguments and that its `if (!isAutoMode) return undefined` guard
     lives within the first 600 chars of the function body. The new
     flatRateCtx param + JSDoc pushed the guard past that window and
     lengthened the call site.

  2. silent-catch-diagnostics (#3348) requires migrated files to route
     through workflow-logger instead of leaving empty catch blocks. The
     new buildFlatRateContext() swallowed registry lookup errors with a
     comment-only catch.

Fix both without regressing flat-rate detection:

- Hang the flat-rate context off autoModeStartModel itself via an
  optional `flatRateCtx` field. selectAndApplyModel now enriches
  autoModeStartModel up front (preserving the variable name) and
  resolvePreferredModelConfig reads autoModeStartModel.flatRateCtx —
  signature shrinks back to three params, call site returns to the
  3-arg form the test anchors on.
- Replace the empty catch in buildFlatRateContext() with a
  logWarning("dispatch", ...) that surfaces the lookup failure while
  still falling through with authMode undefined, matching the
  fail-closed policy everywhere else in the file.
This commit is contained in:
Jeremy 2026-04-13 16:00:01 -05:00
parent 9a93563a64
commit 8cf8d2bcf2

View file

@ -15,6 +15,7 @@ import { resolveModelForComplexity, escalateTier, getEligibleModels, loadCapabil
import { getLedger, getProjectTotals } from "./metrics.js";
import { unitPhaseLabel } from "./auto-dashboard.js";
import { getSessionModelOverride } from "./session-model-override.js";
import { logWarning } from "./workflow-logger.js";
export interface ModelSelectionResult {
/** Routing metadata for metrics recording */
@ -25,14 +26,8 @@ export interface ModelSelectionResult {
export function resolvePreferredModelConfig(
unitType: string,
autoModeStartModel: { provider: string; id: string } | null,
/** When false, only return explicit per-phase model configs do not
* synthesize a routing ceiling from dynamic_routing.tier_models (#3962). */
autoModeStartModel: { provider: string; id: string; flatRateCtx?: FlatRateContext } | null,
isAutoMode = true,
/** Optional flat-rate context for the start model's provider. Used to
* extend flat-rate detection beyond the built-in list (user
* `flat_rate_providers` preference + externalCli auto-detection). */
flatRateCtx?: FlatRateContext,
) {
const explicitConfig = resolveModelWithFallbacksForUnit(unitType);
if (explicitConfig) return explicitConfig;
@ -45,7 +40,7 @@ export function resolvePreferredModelConfig(
if (!routingConfig.enabled || !routingConfig.tier_models) return undefined;
// Don't synthesize a routing config for flat-rate providers (#3453).
if (autoModeStartModel && isFlatRateProvider(autoModeStartModel.provider, flatRateCtx)) return undefined;
if (autoModeStartModel && isFlatRateProvider(autoModeStartModel.provider, autoModeStartModel.flatRateCtx)) return undefined;
const ceilingModel = routingConfig.tier_models.heavy
?? (autoModeStartModel ? `${autoModeStartModel.provider}/${autoModeStartModel.id}` : undefined);
@ -72,7 +67,7 @@ export async function selectAndApplyModel(
basePath: string,
prefs: GSDPreferences | undefined,
verbose: boolean,
autoModeStartModel: { provider: string; id: string } | null,
autoModeStartModel: { provider: string; id: string; flatRateCtx?: FlatRateContext } | null,
retryContext?: { isRetry: boolean; previousTier?: string },
/** When false (interactive/guided-flow), skip dynamic routing and use the session model.
* Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */
@ -83,17 +78,20 @@ export async function selectAndApplyModel(
const effectiveSessionModelOverride = sessionModelOverride === undefined
? getSessionModelOverride(ctx.sessionManager.getSessionId())
: (sessionModelOverride ?? undefined);
// Build a flat-rate context for the start model's provider up front so
// routing synthesis and the dispatch-time guard see the same signals
// (built-in list + user `flat_rate_providers` preference + externalCli
// auto-detection). The dispatch-time primary-model check below builds
// its own per-provider context when it has a resolved primary model.
const startModelFlatRateCtx = autoModeStartModel
? buildFlatRateContext(autoModeStartModel.provider, ctx, prefs)
: undefined;
// Enrich the start model with a flat-rate context up front so routing
// synthesis and the dispatch-time guard see the same signals (built-in
// list + user `flat_rate_providers` preference + externalCli auto-
// detection). The dispatch-time primary-model check below builds its
// own per-provider context when it has a resolved primary model.
if (autoModeStartModel) {
autoModeStartModel = {
...autoModeStartModel,
flatRateCtx: buildFlatRateContext(autoModeStartModel.provider, ctx, prefs),
};
}
const modelConfig = effectiveSessionModelOverride
? undefined
: resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode, startModelFlatRateCtx);
: resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode);
let routing: { tier: string; modelDowngraded: boolean } | null = null;
let appliedModel: Model<Api> | null = null;
@ -124,7 +122,7 @@ export async function selectAndApplyModel(
routingConfig.enabled = false;
}
} else if (
(autoModeStartModel && isFlatRateProvider(autoModeStartModel.provider, startModelFlatRateCtx))
(autoModeStartModel && isFlatRateProvider(autoModeStartModel.provider, autoModeStartModel.flatRateCtx))
|| (ctx.model?.provider && isFlatRateProvider(
ctx.model.provider,
buildFlatRateContext(ctx.model.provider, ctx, prefs),
@ -483,8 +481,13 @@ export function buildFlatRateContext(
if (mode === "apiKey" || mode === "oauth" || mode === "externalCli" || mode === "none") {
authMode = mode;
}
} catch {
// Registry lookup failure must never break flat-rate detection.
} catch (err) {
// Registry lookup failure must never break flat-rate detection —
// fall through with authMode undefined and surface the cause.
logWarning(
"dispatch",
`flat-rate auth-mode lookup failed for ${provider}: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
return {