fix(app): select available host model by default

This commit is contained in:
Matt Cowger
2026-11-07 13:27:25 -08:00
parent 19848899d0
commit 20120d111b
3 changed files with 38 additions and 7 deletions
+3
View File
@@ -47,6 +47,9 @@ The component renders state and dispatches intent. That is all it does.
4. **Resolution is explicit model state, per host** (`idle | pending |
complete`), keyed off the opened snapshot's serverId. Waiting for data is a
state you can render, not an effect race.
Create-agent model preferences are shared across hosts, so resolve remembered
models against the selected host's ready catalog before applying them. Keep
explicit initial values and user selections intact.
5. **Displays are owned state.** The selected option's label is captured at
selection/seed time (`setProject(value, display)`), never re-derived from a
live options list — list churn must not flicker or blank a selection.
@@ -131,6 +131,34 @@ describe("resolveDefaultModel", () => {
});
});
describe("remembered model availability", () => {
it("uses the selected host's default when the remembered model belongs to another host", () => {
const preferences = {
provider: "codex" as const,
providerPreferences: { codex: { model: "remote/model" } },
};
const models: AgentModelDefinition[] = [
{ provider: "codex", id: "local/first", label: "First" },
{ provider: "codex", id: "local/default", label: "Default", isDefault: true },
];
const resolve = (availableModels: AgentModelDefinition[] | null) =>
resolveFormState(
undefined,
preferences,
availableModels,
INITIAL_USER_MODIFIED,
makeState().form,
codexProviderMap,
).model;
expect(resolve(null)).toBe("remote/model");
expect(resolve(models)).toBe("local/default");
expect(resolve([{ provider: "codex", id: "remote/model", label: "Remote" }])).toBe(
"remote/model",
);
});
});
describe("model aliases", () => {
it("canonicalizes a retired preferred model and restores thinking from its alias key", () => {
const resolved = resolveFormState(
@@ -791,17 +819,16 @@ describe("resolveFormState", () => {
});
});
it("keeps the explicit model when a refreshed catalogue no longer lists it", () => {
it("keeps a user-selected model when a refreshed catalog no longer lists it", () => {
const resolved = resolveFormState(
undefined,
{ provider: "codex", providerPreferences: { codex: { model: "gpt-6-astra" } } },
{ provider: "codex" },
CODEX_MODELS,
INITIAL_USER_MODIFIED,
makeState().form,
{ ...INITIAL_USER_MODIFIED, model: true },
makeState({ provider: "codex", model: "gpt-6-astra" }).form,
codexProviderMap,
);
expect(resolved.model).toBe("gpt-6-astra");
// Label/persistence lookup must not reinterpret the submitted ID as another model.
expect(resolveEffectiveModel(CODEX_MODELS, resolved.model)).toBeNull();
});
@@ -317,9 +317,10 @@ function resolveModelField(input: {
: resolveCanonicalModelId(availableModels, initialModel) || initialModel;
}
if (preferredModel) {
return !availableModels
return !availableModels?.length
? preferredModel
: resolveCanonicalModelId(availableModels, preferredModel) || preferredModel;
: resolveCanonicalModelId(availableModels, preferredModel) ||
resolveDefaultModelId(availableModels);
}
return "";
}