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:
@@ -12,6 +12,8 @@ import {
|
||||
buildSelectableProviderSelectorProviders,
|
||||
type ProviderSelectorProvider,
|
||||
} from "@/provider-selection/provider-selection";
|
||||
import { filterSelectableModels } from "@/provider-selection/model-catalog";
|
||||
import { OptimisticFormPreferences } from "@/create-agent-preferences/optimistic-preferences";
|
||||
import { useProvidersSnapshot } from "./use-providers-snapshot";
|
||||
import {
|
||||
useFormPreferences,
|
||||
@@ -156,7 +158,7 @@ function buildAllProviderModels(
|
||||
): Map<string, AgentModelDefinition[]> {
|
||||
const map = new Map<string, AgentModelDefinition[]>();
|
||||
for (const entry of snapshotEntries ?? []) {
|
||||
map.set(entry.provider, entry.models ?? []);
|
||||
map.set(entry.provider, filterSelectableModels(entry.models ?? []) ?? []);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -166,7 +168,7 @@ function buildProviderModelsByProvider(
|
||||
): ProviderModelsByProvider {
|
||||
const map: ProviderModelsByProvider = new Map();
|
||||
for (const entry of snapshotEntries ?? []) {
|
||||
map.set(entry.provider, entry.models ?? null);
|
||||
map.set(entry.provider, filterSelectableModels(entry.models ?? null));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -177,7 +179,7 @@ async function persistProviderPreferences(input: {
|
||||
availableModels: AgentModelDefinition[] | null;
|
||||
updatePreferences: (
|
||||
updates: Partial<FormPreferences> | ((current: FormPreferences) => FormPreferences),
|
||||
) => Promise<void>;
|
||||
) => Promise<FormPreferences>;
|
||||
}): Promise<void> {
|
||||
const { provider, formState, availableModels, updatePreferences } = input;
|
||||
const resolvedModel = resolveEffectiveModel(availableModels, formState.model);
|
||||
@@ -208,6 +210,28 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
} = options;
|
||||
|
||||
const { preferences, isLoading: isPreferencesLoading, updatePreferences } = useFormPreferences();
|
||||
const preferenceOverlayRef = useRef(new OptimisticFormPreferences(preferences));
|
||||
|
||||
useEffect(() => {
|
||||
preferenceOverlayRef.current.reconcile(preferences);
|
||||
}, [preferences]);
|
||||
|
||||
const updateCurrentPreferences = useCallback(
|
||||
async (
|
||||
updates: Partial<FormPreferences> | ((current: FormPreferences) => FormPreferences),
|
||||
): Promise<FormPreferences> => {
|
||||
const pendingId = preferenceOverlayRef.current.begin(updates);
|
||||
try {
|
||||
const persisted = await updatePreferences(updates);
|
||||
preferenceOverlayRef.current.commit(pendingId, persisted);
|
||||
return persisted;
|
||||
} catch (error) {
|
||||
preferenceOverlayRef.current.reject(pendingId);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[updatePreferences],
|
||||
);
|
||||
|
||||
const daemons = useHosts();
|
||||
|
||||
@@ -294,7 +318,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
: null,
|
||||
[formState.provider, snapshotEntries],
|
||||
);
|
||||
const snapshotSelectedProviderModels = snapshotSelectedEntry?.models ?? null;
|
||||
const snapshotSelectedProviderModels = filterSelectableModels(
|
||||
snapshotSelectedEntry?.models ?? null,
|
||||
);
|
||||
const selectedProviderIsLoading = snapshotSelectedEntry?.status === "loading";
|
||||
const snapshotSelectedProviderModes = resolveSelectedProviderModes({
|
||||
selectedEntry: snapshotSelectedEntry,
|
||||
@@ -404,7 +430,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
}
|
||||
const providerModels = allProviderModels.get(provider) ?? null;
|
||||
const providerDef = selectableProviderDefinitionMap.get(provider);
|
||||
const providerPrefs = preferences?.providerPreferences?.[provider];
|
||||
const providerPrefs = preferenceOverlayRef.current.current().providerPreferences?.[provider];
|
||||
|
||||
dispatch({
|
||||
type: "SET_PROVIDER_FROM_USER",
|
||||
@@ -413,14 +439,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
providerDef,
|
||||
providerPrefs,
|
||||
});
|
||||
void updatePreferences({ provider });
|
||||
void updateCurrentPreferences({ provider });
|
||||
},
|
||||
[
|
||||
allProviderModels,
|
||||
preferences?.providerPreferences,
|
||||
selectableProviderDefinitionMap,
|
||||
updatePreferences,
|
||||
],
|
||||
[allProviderModels, selectableProviderDefinitionMap, updateCurrentPreferences],
|
||||
);
|
||||
|
||||
const setProviderAndModelFromUser = useCallback(
|
||||
@@ -430,7 +451,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
}
|
||||
const providerDef = selectableProviderDefinitionMap.get(provider);
|
||||
const providerModels = allProviderModels.get(provider) ?? null;
|
||||
const providerPrefs = preferences?.providerPreferences?.[provider];
|
||||
const providerPrefs = preferenceOverlayRef.current.current().providerPreferences?.[provider];
|
||||
const normalizedModelId = normalizeSelectedModelId(modelId);
|
||||
const nextModelId = normalizedModelId || resolveDefaultModelId(providerModels);
|
||||
|
||||
@@ -442,7 +463,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
providerModels,
|
||||
providerPrefs,
|
||||
});
|
||||
void updatePreferences((current) =>
|
||||
void updateCurrentPreferences((current) =>
|
||||
mergeSelectedComposerPreferences({
|
||||
preferences: current,
|
||||
provider,
|
||||
@@ -452,12 +473,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
}),
|
||||
);
|
||||
},
|
||||
[
|
||||
allProviderModels,
|
||||
preferences?.providerPreferences,
|
||||
selectableProviderDefinitionMap,
|
||||
updatePreferences,
|
||||
],
|
||||
[allProviderModels, selectableProviderDefinitionMap, updateCurrentPreferences],
|
||||
);
|
||||
|
||||
const clearProviderSelectionFromUser = useCallback(() => {
|
||||
@@ -469,7 +485,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
dispatch({ type: "SET_MODE_FROM_USER", modeId });
|
||||
const provider = reducerStateRef.current.form.provider;
|
||||
if (provider) {
|
||||
void updatePreferences((current) =>
|
||||
void updateCurrentPreferences((current) =>
|
||||
mergeSelectedComposerPreferences({
|
||||
preferences: current,
|
||||
provider,
|
||||
@@ -480,17 +496,25 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
);
|
||||
}
|
||||
},
|
||||
[updatePreferences],
|
||||
[updateCurrentPreferences],
|
||||
);
|
||||
|
||||
const setModelFromUser = useCallback(
|
||||
(modelId: string) => {
|
||||
dispatch({ type: "SET_MODEL_FROM_USER", modelId, availableModels });
|
||||
const provider = reducerStateRef.current.form.provider;
|
||||
const providerPrefs = provider
|
||||
? preferenceOverlayRef.current.current().providerPreferences?.[provider]
|
||||
: undefined;
|
||||
dispatch({
|
||||
type: "SET_MODEL_FROM_USER",
|
||||
modelId,
|
||||
availableModels,
|
||||
providerPrefs,
|
||||
});
|
||||
if (provider) {
|
||||
const normalizedModelId = normalizeSelectedModelId(modelId);
|
||||
const nextModelId = normalizedModelId || resolveDefaultModelId(availableModels);
|
||||
void updatePreferences((current) =>
|
||||
void updateCurrentPreferences((current) =>
|
||||
mergeSelectedComposerPreferences({
|
||||
preferences: current,
|
||||
provider,
|
||||
@@ -501,7 +525,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
);
|
||||
}
|
||||
},
|
||||
[availableModels, updatePreferences],
|
||||
[availableModels, updateCurrentPreferences],
|
||||
);
|
||||
|
||||
const setThinkingOptionFromUser = useCallback(
|
||||
@@ -509,7 +533,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
dispatch({ type: "SET_THINKING_OPTION_FROM_USER", thinkingOptionId });
|
||||
const { provider, model: modelId } = reducerStateRef.current.form;
|
||||
if (provider && modelId) {
|
||||
void updatePreferences((current) =>
|
||||
void updateCurrentPreferences((current) =>
|
||||
mergeSelectedComposerPreferences({
|
||||
preferences: current,
|
||||
provider,
|
||||
@@ -522,7 +546,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
);
|
||||
}
|
||||
},
|
||||
[updatePreferences],
|
||||
[updateCurrentPreferences],
|
||||
);
|
||||
|
||||
const setWorkingDir = useCallback((value: string) => {
|
||||
@@ -556,9 +580,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
provider: formState.provider,
|
||||
formState,
|
||||
availableModels,
|
||||
updatePreferences,
|
||||
updatePreferences: updateCurrentPreferences,
|
||||
});
|
||||
}, [availableModels, formState, updatePreferences]);
|
||||
}, [availableModels, formState, updateCurrentPreferences]);
|
||||
|
||||
const agentDefinition = formState.provider
|
||||
? providerDefinitionMap.get(formState.provider)
|
||||
|
||||
@@ -29,7 +29,7 @@ async function loadFormPreferences(): Promise<FormPreferences> {
|
||||
export interface UseFormPreferencesReturn {
|
||||
preferences: FormPreferences;
|
||||
isLoading: boolean;
|
||||
updatePreferences: (updates: FormPreferenceUpdate) => Promise<void>;
|
||||
updatePreferences: (updates: FormPreferenceUpdate) => Promise<FormPreferences>;
|
||||
}
|
||||
|
||||
export function useFormPreferences(): UseFormPreferencesReturn {
|
||||
@@ -47,6 +47,7 @@ export function useFormPreferences(): UseFormPreferencesReturn {
|
||||
async (updates: FormPreferenceUpdate) => {
|
||||
const next = await createAgentPreferencesService.update(updates);
|
||||
queryClient.setQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY, next);
|
||||
return next;
|
||||
},
|
||||
[queryClient],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user