fix(gsd): handle bare model IDs in resolveDefaultSessionModel (#3517)
resolveDefaultSessionModel() previously only returned a result for provider/model format strings, silently ignoring valid bare model IDs like "gpt-5.4". This meant preferences could fail to override stale settings.json defaults when users configured models without explicit provider prefixes. Now accepts sessionProvider param (ctx.model?.provider) to resolve bare IDs. Also handles object configs without explicit provider field.
This commit is contained in:
parent
fbcd722cf4
commit
70c76d9a1a
4 changed files with 62 additions and 13 deletions
|
|
@ -161,7 +161,7 @@ export async function bootstrapAutoSession(
|
|||
// (#3517). The session model (ctx.model) comes from findInitialModel() which
|
||||
// reads defaultProvider/defaultModel from ~/.gsd/agent/settings.json. When
|
||||
// the user has explicit model preferences in PREFERENCES.md, those should win.
|
||||
const preferredModel = resolveDefaultSessionModel();
|
||||
const preferredModel = resolveDefaultSessionModel(ctx.model?.provider);
|
||||
const startModelSnapshot = preferredModel
|
||||
?? (ctx.model
|
||||
? { provider: ctx.model.provider, id: ctx.model.id }
|
||||
|
|
|
|||
|
|
@ -116,10 +116,19 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
|
|||
* we treat that as the session default. Falls back through execution →
|
||||
* planning → first configured model.
|
||||
*
|
||||
* Returns `{ provider, id }` parsed from the `provider/model` format,
|
||||
* or `undefined` if no model preference is configured.
|
||||
* Accepts an optional `sessionProvider` for bare model IDs that don't
|
||||
* include an explicit provider prefix (e.g. `gpt-5.4` instead of
|
||||
* `openai-codex/gpt-5.4`). When a bare ID is found and sessionProvider
|
||||
* is available, the session provider is used. Without sessionProvider,
|
||||
* bare IDs are still returned with provider set to the bare ID itself
|
||||
* so downstream resolution (resolveModelId) can match it.
|
||||
*
|
||||
* Returns `{ provider, id }` or `undefined` if no model preference is
|
||||
* configured.
|
||||
*/
|
||||
export function resolveDefaultSessionModel(): { provider: string; id: string } | undefined {
|
||||
export function resolveDefaultSessionModel(
|
||||
sessionProvider?: string,
|
||||
): { provider: string; id: string } | undefined {
|
||||
const prefs = loadEffectiveGSDPreferences();
|
||||
if (!prefs?.preferences.models) return undefined;
|
||||
|
||||
|
|
@ -138,15 +147,38 @@ export function resolveDefaultSessionModel(): { provider: string; id: string } |
|
|||
|
||||
for (const cfg of candidates) {
|
||||
if (!cfg) continue;
|
||||
const modelStr = typeof cfg === "string"
|
||||
? cfg
|
||||
: cfg.provider && !cfg.model.includes("/")
|
||||
? `${cfg.provider}/${cfg.model}`
|
||||
: cfg.model;
|
||||
|
||||
if (modelStr.includes("/")) {
|
||||
const slashIdx = modelStr.indexOf("/");
|
||||
return { provider: modelStr.slice(0, slashIdx), id: modelStr.slice(slashIdx + 1) };
|
||||
// Normalize to provider + id from the various config shapes
|
||||
let provider: string | undefined;
|
||||
let id: string;
|
||||
|
||||
if (typeof cfg === "string") {
|
||||
const slashIdx = cfg.indexOf("/");
|
||||
if (slashIdx !== -1) {
|
||||
provider = cfg.slice(0, slashIdx);
|
||||
id = cfg.slice(slashIdx + 1);
|
||||
} else {
|
||||
// Bare model ID (e.g. "gpt-5.4") — use session provider as context
|
||||
provider = sessionProvider;
|
||||
id = cfg;
|
||||
}
|
||||
} else {
|
||||
// Object config: { model, provider?, fallbacks? }
|
||||
if (cfg.provider) {
|
||||
provider = cfg.provider;
|
||||
} else if (cfg.model.includes("/")) {
|
||||
const slashIdx = cfg.model.indexOf("/");
|
||||
provider = cfg.model.slice(0, slashIdx);
|
||||
id = cfg.model.slice(slashIdx + 1);
|
||||
return { provider, id };
|
||||
} else {
|
||||
provider = sessionProvider;
|
||||
}
|
||||
id = cfg.model;
|
||||
}
|
||||
|
||||
if (provider && id) {
|
||||
return { provider, id };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,13 @@ test("bootstrapAutoSession restores autoModeStartModel from the early snapshot (
|
|||
|
||||
test("bootstrapAutoSession prefers GSD PREFERENCES.md over settings.json for start model (#3517)", () => {
|
||||
// resolveDefaultSessionModel() should be called before the snapshot is built
|
||||
const preferredIdx = source.indexOf("const preferredModel = resolveDefaultSessionModel()");
|
||||
const preferredIdx = source.indexOf("const preferredModel = resolveDefaultSessionModel(");
|
||||
assert.ok(preferredIdx > -1, "auto-start.ts should call resolveDefaultSessionModel()");
|
||||
|
||||
// Session provider should be passed for bare model ID resolution
|
||||
const withProviderIdx = source.indexOf("resolveDefaultSessionModel(ctx.model?.provider)");
|
||||
assert.ok(withProviderIdx > -1, "auto-start.ts should pass ctx.model?.provider for bare ID resolution");
|
||||
|
||||
const snapshotIdx = source.indexOf("const startModelSnapshot = preferredModel");
|
||||
assert.ok(snapshotIdx > -1, "startModelSnapshot should use preferredModel when available");
|
||||
|
||||
|
|
|
|||
|
|
@ -200,6 +200,19 @@ describe("GSD preferences override settings.json for session model (#3517)", ()
|
|||
"should be null when neither preferences nor ctx.model exist");
|
||||
});
|
||||
|
||||
it("bare model ID uses session provider when available", () => {
|
||||
// Simulates: PREFERENCES.md has "gpt-5.4" (no provider), session is openai-codex
|
||||
const preferredModel = { provider: "openai-codex", id: "gpt-5.4" }; // from resolveDefaultSessionModel("openai-codex")
|
||||
const ctxModel = { provider: "openai-codex", id: "claude-sonnet-4-6" };
|
||||
|
||||
const startModelSnapshot = preferredModel
|
||||
?? { provider: ctxModel.provider, id: ctxModel.id };
|
||||
|
||||
assert.equal(startModelSnapshot.provider, "openai-codex");
|
||||
assert.equal(startModelSnapshot.id, "gpt-5.4",
|
||||
"bare model ID from preferences should still override ctx.model");
|
||||
});
|
||||
|
||||
it("stale settings.json does not leak when preferences are set", () => {
|
||||
// Scenario: settings.json has claude-code, PREFERENCES.md has openai-codex
|
||||
const settingsJsonDefault = { provider: "claude-code", id: "claude-sonnet-4-6" };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue