Preserve Claude model and thinking preferences (#2912)

* fix(agents): preserve Claude thinking selections

Model switches now resolve thinking against the target model's saved preference instead of resetting to the catalog default. Claude owns its configured-model defaults and legacy Fable alias handling, while persisted permission modes remain unchanged.

* chore(claude): tag Fable compatibility shim

* fix(app): resolve persisted model aliases

* fix(models): preserve aliases for older clients

* fix(models): hide compatibility aliases from listings

* fix(settings): hide compatibility model aliases

* test(claude): cover legacy catalog entry

* test(app): expect mock thinking controls in schedules

* fix(app): preserve queued thinking preferences

* fix(app): reconcile schedule provider snapshots

* fix(app): preserve schedule model selections

* fix(claude): preserve exact configured model ids

* fix(app): preserve committed thinking preferences

* fix(app): preserve aliased schedule thinking
This commit is contained in:
Mohamed Boudra
2026-08-06 11:33:18 +02:00
committed by GitHub
parent 73ba37a07a
commit f2054cc701
43 changed files with 1369 additions and 220 deletions
@@ -9,6 +9,7 @@ import {
type FormPreferences,
type ProviderPreferences,
} from "@/hooks/use-form-preferences";
import { findModelByReference } from "./model-catalog";
export interface FormInitialValues {
serverId?: string | null;
@@ -98,6 +99,7 @@ export type AgentFormAction =
type: "SET_MODEL_FROM_USER";
modelId: string;
availableModels: AgentModelDefinition[] | null;
providerPrefs: ProviderPrefs | undefined;
}
| { type: "CLEAR_PROVIDER_SELECTION_FROM_USER" }
| { type: "SET_THINKING_OPTION_FROM_USER"; thinkingOptionId: string }
@@ -123,17 +125,36 @@ export function resolveDefaultModelId(availableModels: AgentModelDefinition[] |
return resolveDefaultModel(availableModels)?.id ?? "";
}
function resolveCanonicalModelId(
availableModels: AgentModelDefinition[] | null,
modelId: string,
): string {
const normalizedModelId = normalizeSelectedModelId(modelId);
if (!normalizedModelId || !availableModels) return normalizedModelId;
return findModelByReference(availableModels, normalizedModelId)?.id ?? "";
}
export function resolveEffectiveModel(
availableModels: AgentModelDefinition[] | null,
modelId: string,
): AgentModelDefinition | null {
if (!availableModels || availableModels.length === 0) return null;
const normalizedModelId = modelId.trim();
if (!normalizedModelId) return null;
return (
availableModels.find((model) => model.id === normalizedModelId) ??
resolveDefaultModel(availableModels)
);
if (!normalizeSelectedModelId(modelId)) return null;
return findModelByReference(availableModels, modelId) ?? resolveDefaultModel(availableModels);
}
function resolvePreferredThinkingOptionId(input: {
availableModels: AgentModelDefinition[] | null;
providerPrefs: ProviderPrefs | undefined;
modelId: string;
}): string {
const model = findModelByReference(input.availableModels, input.modelId);
const modelReferences = model ? [model.id, ...(model.aliases ?? [])] : [input.modelId];
for (const modelReference of modelReferences) {
const thinkingOptionId = input.providerPrefs?.thinkingByModel?.[modelReference]?.trim();
if (thinkingOptionId) return thinkingOptionId;
}
return "";
}
export function resolveThinkingOptionId(args: {
@@ -313,15 +334,18 @@ function resolveModelField(input: {
input;
if (userModified) return currentModel;
if (!provider) return "";
const isValidModel = (m: string) => availableModels?.some((am) => am.id === m) ?? false;
const initialModel = normalizeSelectedModelId(initialValues?.model);
const preferredModel = normalizeSelectedModelId(providerPrefs?.model);
const defaultModelId = resolveDefaultModelId(availableModels);
if (initialModel) {
return !availableModels || isValidModel(initialModel) ? initialModel : defaultModelId;
return !availableModels
? initialModel
: resolveCanonicalModelId(availableModels, initialModel) || defaultModelId;
}
if (preferredModel) {
return !availableModels || isValidModel(preferredModel) ? preferredModel : defaultModelId;
return !availableModels
? preferredModel
: resolveCanonicalModelId(availableModels, preferredModel) || defaultModelId;
}
return "";
}
@@ -333,19 +357,28 @@ function resolveThinkingOption(input: {
modelId: string;
initialValues: FormInitialValues | undefined;
providerPrefs: ProviderPrefs | undefined;
availableModels: AgentModelDefinition[] | null;
}): string {
const { provider, userModified, currentThinkingOptionId, modelId, initialValues, providerPrefs } =
input;
const {
provider,
userModified,
currentThinkingOptionId,
modelId,
initialValues,
providerPrefs,
availableModels,
} = input;
if (!provider) return "";
if (userModified) return currentThinkingOptionId;
const initialThinkingOptionId =
typeof initialValues?.thinkingOptionId === "string"
? initialValues.thinkingOptionId.trim()
: "";
const effectiveModelId = modelId.trim();
const preferredThinking = effectiveModelId
? (providerPrefs?.thinkingByModel?.[effectiveModelId]?.trim() ?? "")
: "";
const preferredThinking = resolvePreferredThinkingOptionId({
availableModels,
providerPrefs,
modelId,
});
if (initialThinkingOptionId.length > 0) return initialThinkingOptionId;
if (preferredThinking.length > 0) return preferredThinking;
return "";
@@ -399,6 +432,7 @@ export function resolveFormState(
modelId: result.model,
initialValues,
providerPrefs,
availableModels,
});
if (result.provider && availableModels) {
@@ -455,11 +489,11 @@ function pickNextModelForProvider(input: {
providerPrefs: ProviderPrefs | undefined;
}): string {
const { providerModels, providerPrefs } = input;
const isValidModel = (m: string) => providerModels?.some((am) => am.id === m) ?? false;
const preferredModel = normalizeSelectedModelId(providerPrefs?.model);
const defaultModelId = resolveDefaultModelId(providerModels);
if (preferredModel && (!providerModels || isValidModel(preferredModel))) {
return preferredModel;
if (preferredModel) {
if (!providerModels) return preferredModel;
return resolveCanonicalModelId(providerModels, preferredModel) || defaultModelId;
}
return defaultModelId;
}
@@ -496,9 +530,11 @@ function pickNextThinkingOptionForProvider(input: {
modelId: string;
}): string {
const { providerModels, providerPrefs, modelId } = input;
const preferredThinking = modelId
? (providerPrefs?.thinkingByModel?.[modelId]?.trim() ?? "")
: "";
const preferredThinking = resolvePreferredThinkingOptionId({
availableModels: providerModels,
providerPrefs,
modelId,
});
return resolveThinkingOptionId({
availableModels: providerModels,
modelId,
@@ -506,6 +542,30 @@ function pickNextThinkingOptionForProvider(input: {
});
}
function pickNextThinkingOptionForTarget(input: {
availableModels: AgentModelDefinition[] | null;
providerPrefs: ProviderPrefs | undefined;
modelId: string;
currentModelId: string;
currentThinkingOptionId: string;
isSameProvider: boolean;
}): string {
const requestedThinkingOptionId =
input.isSameProvider &&
resolveCanonicalModelId(input.availableModels, input.currentModelId) === input.modelId
? input.currentThinkingOptionId
: resolvePreferredThinkingOptionId({
availableModels: input.availableModels,
providerPrefs: input.providerPrefs,
modelId: input.modelId,
});
return resolveThinkingOptionId({
availableModels: input.availableModels,
modelId: input.modelId,
requestedThinkingOptionId,
});
}
function completeResolution(
state: AgentFormReducerState,
action: CompleteResolutionAction,
@@ -579,12 +639,15 @@ export function resolveAgentForm(
}
case "SET_PROVIDER_AND_MODEL_FROM_USER": {
const normalizedModelId = normalizeSelectedModelId(action.modelId);
const normalizedModelId = resolveCanonicalModelId(action.providerModels, action.modelId);
const nextModelId = normalizedModelId || resolveDefaultModelId(action.providerModels);
const nextThinkingOptionId = resolveThinkingOptionId({
const nextThinkingOptionId = pickNextThinkingOptionForTarget({
availableModels: action.providerModels,
modelId: nextModelId,
requestedThinkingOptionId: "",
providerPrefs: action.providerPrefs,
currentModelId: state.form.model,
currentThinkingOptionId: state.form.thinkingOptionId,
isSameProvider: state.form.provider === action.provider,
});
const nextModeId = pickNextModeForProviderAndModel({
currentProvider: state.form.provider,
@@ -614,14 +677,15 @@ export function resolveAgentForm(
};
case "SET_MODEL_FROM_USER": {
const normalizedModelId = normalizeSelectedModelId(action.modelId);
const normalizedModelId = resolveCanonicalModelId(action.availableModels, action.modelId);
const nextModelId = normalizedModelId || resolveDefaultModelId(action.availableModels);
const nextThinkingOptionId = resolveThinkingOptionId({
const nextThinkingOptionId = pickNextThinkingOptionForTarget({
availableModels: action.availableModels,
modelId: nextModelId,
requestedThinkingOptionId: state.userModified.thinkingOptionId
? state.form.thinkingOptionId
: "",
providerPrefs: action.providerPrefs,
currentModelId: state.form.model,
currentThinkingOptionId: state.form.thinkingOptionId,
isSameProvider: true,
});
return {
...state,